Add the environment config files
continuous-integration/drone/push Build is passing Details

pull/13/head
Colin FRIZOT 11 months ago
parent 6a6fc064ef
commit d86f2779a5

@ -16,14 +16,27 @@
"outputPath": "dist/sandkasten", "outputPath": "dist/sandkasten",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "browser": "src/main.ts",
"polyfills": ["zone.js"], "polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"], "assets": [
"styles": ["src/styles.scss"], "src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [] "scripts": []
}, },
"configurations": { "configurations": {
"production": { "production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
@ -39,6 +52,7 @@
"outputHashing": "all" "outputHashing": "all"
}, },
"development": { "development": {
"fileReplacements": [],
"optimization": false, "optimization": false,
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true "sourceMap": true
@ -67,10 +81,18 @@
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"polyfills": ["zone.js", "zone.js/testing"], "polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"assets": ["src/favicon.ico", "src/assets"], "assets": [
"styles": ["src/styles.scss"], "src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [], "scripts": [],
"karmaConfig": "karma.conf.js" "karmaConfig": "karma.conf.js"
} }
@ -78,7 +100,10 @@
"lint": { "lint": {
"builder": "@angular-eslint/builder:lint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] "lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
} }
} }
} }
@ -86,6 +111,8 @@
}, },
"cli": { "cli": {
"analytics": false, "analytics": false,
"schematicCollections": ["@angular-eslint/schematics"] "schematicCollections": [
"@angular-eslint/schematics"
]
} }
} }

@ -1,221 +1,227 @@
import { Component, Input, ViewChild } from '@angular/core'; import {Component, Input, ViewChild} from '@angular/core';
import { BackendService } from 'src/app/services/backendService.service'; import {BackendService} from 'src/app/services/backendService.service';
import { Compartment, StateEffect } from '@codemirror/state'; import {Compartment, StateEffect} from '@codemirror/state';
import { CodeMirrorComponent } from '@sandkasten/codemirror6-editor'; import {CodeMirrorComponent} from '@sandkasten/codemirror6-editor';
import { LanguageDescription } from '@codemirror/language'; import {LanguageDescription} from '@codemirror/language';
import { CODE_DEFAULTS, LANGUAGES } from '../languages'; import {CODE_DEFAULTS, LANGUAGES} from '../languages';
import { SafeHTMLPipe } from '../../safe-html.pipe'; import {SafeHTMLPipe} from '../../safe-html.pipe';
import { ReactiveFormsModule, FormsModule } from '@angular/forms'; import {ReactiveFormsModule, FormsModule} from '@angular/forms';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
import { import {
keymap, keymap,
highlightSpecialChars, highlightSpecialChars,
drawSelection, drawSelection,
highlightActiveLine, highlightActiveLine,
dropCursor, dropCursor,
rectangularSelection, rectangularSelection,
crosshairCursor, crosshairCursor,
lineNumbers, lineNumbers,
highlightActiveLineGutter, highlightActiveLineGutter,
gutter, gutter,
} from '@codemirror/view'; } from '@codemirror/view';
import { Extension, EditorState } from '@codemirror/state'; import {Extension, EditorState} from '@codemirror/state';
import { import {
defaultHighlightStyle, defaultHighlightStyle,
syntaxHighlighting, syntaxHighlighting,
indentOnInput, indentOnInput,
bracketMatching, bracketMatching,
foldGutter, foldGutter,
foldKeymap, foldKeymap,
} from '@codemirror/language'; } from '@codemirror/language';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; import {defaultKeymap, history, historyKeymap} from '@codemirror/commands';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search'; import {searchKeymap, highlightSelectionMatches} from '@codemirror/search';
import { import {
autocompletion, autocompletion,
completionKeymap, completionKeymap,
closeBrackets, closeBrackets,
closeBracketsKeymap, closeBracketsKeymap,
} from '@codemirror/autocomplete'; } from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint'; import {lintKeymap} from '@codemirror/lint';
import { import {
Connection, Connection,
getDocument, getDocument,
peerExtension, peerExtension,
} from '../../services/connection.service'; } from '../../services/connection.service';
import {environment} from "../../../environments/environment";
const basicSetup: Extension = (() => [ const basicSetup: Extension = (() => [
highlightActiveLineGutter(), highlightActiveLineGutter(),
highlightSpecialChars(), highlightSpecialChars(),
history(), history(),
foldGutter(), foldGutter(),
drawSelection(), drawSelection(),
dropCursor(), dropCursor(),
EditorState.allowMultipleSelections.of(true), EditorState.allowMultipleSelections.of(true),
indentOnInput(), indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }), syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
bracketMatching(), bracketMatching(),
closeBrackets(), closeBrackets(),
autocompletion(), autocompletion(),
rectangularSelection(), rectangularSelection(),
crosshairCursor(), crosshairCursor(),
highlightActiveLine(), highlightActiveLine(),
highlightSelectionMatches(), highlightSelectionMatches(),
keymap.of([ keymap.of([
...closeBracketsKeymap, ...closeBracketsKeymap,
...defaultKeymap, ...defaultKeymap,
...searchKeymap, ...searchKeymap,
...historyKeymap, ...historyKeymap,
...foldKeymap, ...foldKeymap,
...completionKeymap, ...completionKeymap,
...lintKeymap, ...lintKeymap,
]), ]),
])(); ])();
@Component({ @Component({
selector: 'app-editor', selector: 'app-editor',
templateUrl: './editor.component.html', templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss'], styleUrls: ['./editor.component.scss'],
standalone: true, standalone: true,
imports: [ imports: [
CodeMirrorComponent, CodeMirrorComponent,
ReactiveFormsModule, ReactiveFormsModule,
FormsModule, FormsModule,
SafeHTMLPipe, SafeHTMLPipe,
], ],
}) })
export class EditorComponent { export class EditorComponent {
isLoaded: boolean = false; // Pour vérifier si le chargement est terminé isLoaded: boolean = false; // Pour vérifier si le chargement est terminé
readonly languages: LanguageDescription[] = LANGUAGES; readonly languages: LanguageDescription[] = LANGUAGES;
// Mode par défaut // Mode par défaut
private _selectedLanguage = this.languages.find( private _selectedLanguage = this.languages.find(
(lang) => lang.name === 'JavaScript' (lang) => lang.name === 'JavaScript'
)!; )!;
get selectedLanguage(): LanguageDescription { get selectedLanguage(): LanguageDescription {
return this._selectedLanguage; return this._selectedLanguage;
}
set selectedLanguage(value: LanguageDescription) {
this._selectedLanguage = value;
if (value.name in CODE_DEFAULTS) {
this.editorContent =
CODE_DEFAULTS[value.name as keyof typeof CODE_DEFAULTS];
} }
this.selectedLanguage.load().then((language) => {
this.codemirror.editor?.dispatch({ set selectedLanguage(value: LanguageDescription) {
effects: this.languageCompartment.reconfigure(language), this._selectedLanguage = value;
}); if (value.name in CODE_DEFAULTS) {
}); this.editorContent =
} CODE_DEFAULTS[value.name as keyof typeof CODE_DEFAULTS];
private _linesNumbers: boolean = true; }
get linesNumbers() { this.selectedLanguage.load().then((language) => {
return this._linesNumbers; this.codemirror.editor?.dispatch({
} effects: this.languageCompartment.reconfigure(language),
set linesNumbers(lines: boolean) { });
this._linesNumbers = lines; });
this.codemirror.editor?.dispatch({ }
effects: this.gutterCompartment.reconfigure(
lines ? lineNumbers() : gutter({}) private _linesNumbers: boolean = true;
), get linesNumbers() {
}); return this._linesNumbers;
} }
// Contenu de l'éditeur que l'on passera au serveur set linesNumbers(lines: boolean) {
editorContent: string = this._linesNumbers = lines;
CODE_DEFAULTS[this.selectedLanguage.name as keyof typeof CODE_DEFAULTS]; this.codemirror.editor?.dispatch({
resultContent: string = ''; effects: this.gutterCompartment.reconfigure(
lines ? lineNumbers() : gutter({})
// Message d'erreur ),
errorMessage: string = ''; });
}
@ViewChild(CodeMirrorComponent) private codemirror!: CodeMirrorComponent;
// Contenu de l'éditeur que l'on passera au serveur
private readonly languageCompartment = new Compartment(); editorContent: string =
private readonly gutterCompartment = new Compartment(); CODE_DEFAULTS[this.selectedLanguage.name as keyof typeof CODE_DEFAULTS];
protected readonly extensions: Extension[] = [ resultContent: string = '';
basicSetup,
this.gutterCompartment.of(lineNumbers()), // Message d'erreur
this.languageCompartment.of(this.selectedLanguage.support!), errorMessage: string = '';
];
@ViewChild(CodeMirrorComponent) private codemirror!: CodeMirrorComponent;
private client: WebSocket | undefined;
@Input() private readonly languageCompartment = new Compartment();
set idRoom(idRoom: string) { private readonly gutterCompartment = new Compartment();
if (idRoom === undefined) { protected readonly extensions: Extension[] = [
return; basicSetup,
this.gutterCompartment.of(lineNumbers()),
this.languageCompartment.of(this.selectedLanguage.support!),
];
private client: WebSocket | undefined;
@Input()
set idRoom(idRoom: string) {
if (idRoom === undefined) {
return;
}
// this.client = new WebSocket(`ws://127.0.0.1:3000/live/${idRoom}`);
this.client = new WebSocket(`${environment.webSocketUrl}/live/${idRoom}`);
this.client.addEventListener('open', async () => {
let conn = new Connection(this.client!);
let {version, doc} = await getDocument(conn);
this.codemirror.editor?.dispatch({
changes: {
from: 0,
to: this.codemirror.editor.state.doc.length,
insert: doc,
},
});
this.codemirror.editor?.dispatch({
effects: StateEffect.appendConfig.of([peerExtension(version, conn)]),
});
});
}
constructor(
private router: Router,
private backendService: BackendService
) {
backendService.getResult().subscribe((msg) => {
if (msg.type === 'stdout' || msg.type === 'stderr') {
this.resultContent += msg.text;
}
});
}
// Efface le contenu de l'éditeur
clear(): void {
this.editorContent = '';
}
async onCreateRoomButtonClicked() {
const idRoom = await this.backendService.createRoom(this.editorContent);
await this.router.navigate([`./editor-live/${idRoom}`]);
}
onRunButtonClicked() {
// Le code à exécuter est le contenu de l'éditeur
const codeToExecute = this.editorContent;
this.backendService.executeCode(codeToExecute, this.selectedLanguage.name);
this.resultContent = '';
}
loadFromFile(event: Event) {
const file = (event.target as HTMLInputElement).files![0];
for (const language of this.languages) {
if (language.extensions.some((ext) => file.name.endsWith(`.${ext}`))) {
this.selectedLanguage = language;
const reader = new FileReader();
reader.onload = (event) => {
this.editorContent = event.target!.result as string;
this.errorMessage = '';
};
reader.readAsText(file);
return;
}
}
const extensions = this.languages.flatMap((lang) => lang.extensions);
this.errorMessage = `Unsupported language. Please select one of the following languages: ${extensions.join(', ')}.`;
console.error(this.errorMessage);
} }
this.client = new WebSocket(`ws://127.0.0.1:3000/live/${idRoom}`); saveToFile() {
this.client.addEventListener('open', async () => { const blob = new Blob([this.editorContent], {type: 'text/plain'});
let conn = new Connection(this.client!); const a = document.createElement('a');
let { version, doc } = await getDocument(conn); a.download = `code.${this.selectedLanguage.extensions![0]}`;
a.href = URL.createObjectURL(blob);
this.codemirror.editor?.dispatch({ a.click();
changes: {
from: 0,
to: this.codemirror.editor.state.doc.length,
insert: doc,
},
});
this.codemirror.editor?.dispatch({
effects: StateEffect.appendConfig.of([peerExtension(version, conn)]),
});
});
}
constructor(
private router: Router,
private backendService: BackendService
) {
backendService.getResult().subscribe((msg) => {
if (msg.type === 'stdout' || msg.type === 'stderr') {
this.resultContent += msg.text;
}
});
}
// Efface le contenu de l'éditeur
clear(): void {
this.editorContent = '';
}
async onCreateRoomButtonClicked() {
const idRoom = await this.backendService.createRoom(this.editorContent);
await this.router.navigate([`./editor-live/${idRoom}`]);
}
onRunButtonClicked() {
// Le code à exécuter est le contenu de l'éditeur
const codeToExecute = this.editorContent;
this.backendService.executeCode(codeToExecute, this.selectedLanguage.name);
this.resultContent = '';
}
loadFromFile(event: Event) {
const file = (event.target as HTMLInputElement).files![0];
for (const language of this.languages) {
if (language.extensions.some((ext) => file.name.endsWith(`.${ext}`))) {
this.selectedLanguage = language;
const reader = new FileReader();
reader.onload = (event) => {
this.editorContent = event.target!.result as string;
this.errorMessage = '';
};
reader.readAsText(file);
return;
}
} }
const extensions = this.languages.flatMap((lang) => lang.extensions);
this.errorMessage = `Unsupported language. Please select one of the following languages: ${extensions.join(', ')}.`;
console.error(this.errorMessage);
}
saveToFile() {
const blob = new Blob([this.editorContent], { type: 'text/plain' });
const a = document.createElement('a');
a.download = `code.${this.selectedLanguage.extensions![0]}`;
a.href = URL.createObjectURL(blob);
a.click();
}
} }

@ -1,56 +1,59 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { SSE } from 'sse.js'; import {SSE} from 'sse.js';
import { Observable, Subject } from 'rxjs'; import {Observable, Subject} from 'rxjs';
import {environment} from "../../environments/environment";
export type ExecutionMessage = { export type ExecutionMessage = {
type: 'stdout' | 'stderr' | 'exit'; type: 'stdout' | 'stderr' | 'exit';
text: string; text: string;
}; };
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class BackendService { export class BackendService {
private apiUrl = 'http://localhost:3000'; private apiUrl = environment.apiUrl;
private resultSubject = new Subject<ExecutionMessage>();
private resultSubject = new Subject<ExecutionMessage>();
constructor() {}
constructor() {
async createRoom(code: string) { }
const reponse = await fetch(`${this.apiUrl}/live`, {
method: 'POST', async createRoom(code: string) {
headers: { const reponse = await fetch(`${this.apiUrl}/live`, {
'Content-Type': 'application/json', method: 'POST',
}, headers: {
body: JSON.stringify({ code }), 'Content-Type': 'application/json',
}); },
return reponse.text(); body: JSON.stringify({code}),
} });
return reponse.text();
executeCode(code: string, language: string) { }
const sse = new SSE(`${this.apiUrl}/run`, {
method: 'POST', executeCode(code: string, language: string) {
headers: { const sse = new SSE(`${this.apiUrl}/run`, {
'Content-Type': 'application/json', method: 'POST',
Accept: 'text/event-stream', headers: {
}, 'Content-Type': 'application/json',
payload: JSON.stringify({ code, language }), Accept: 'text/event-stream',
}); },
payload: JSON.stringify({code, language}),
sse.addEventListener('message', (event: MessageEvent<string>) => { });
const result = event.data;
sse.addEventListener('message', (event: MessageEvent<string>) => {
// @ts-expect-error The type is not declared although present const result = event.data;
const type = event.id;
const text = decodeURIComponent(result.replace(/%00/g, '')); // @ts-expect-error The type is not declared although present
if (type === 'end') { const type = event.id;
sse.close(); const text = decodeURIComponent(result.replace(/%00/g, ''));
} if (type === 'end') {
this.resultSubject.next({ type, text }); sse.close();
}); }
} this.resultSubject.next({type, text});
});
getResult(): Observable<ExecutionMessage> { }
return this.resultSubject.asObservable();
} getResult(): Observable<ExecutionMessage> {
return this.resultSubject.asObservable();
}
} }

@ -0,0 +1,5 @@
export const environment = {
production: true,
apiUrl: 'https://tododomain.com',
webSocketUrl: 'ws://tododomain.com',
};

@ -0,0 +1,5 @@
export const environment = {
production: false,
apiUrl: 'http://localhost:3000',
webSocketUrl: 'ws://localhost:3000',
};
Loading…
Cancel
Save