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

@ -1,221 +1,227 @@
import { Component, Input, ViewChild } from '@angular/core';
import { BackendService } from 'src/app/services/backendService.service';
import { Compartment, StateEffect } from '@codemirror/state';
import { CodeMirrorComponent } from '@sandkasten/codemirror6-editor';
import { LanguageDescription } from '@codemirror/language';
import { CODE_DEFAULTS, LANGUAGES } from '../languages';
import { SafeHTMLPipe } from '../../safe-html.pipe';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import {Component, Input, ViewChild} from '@angular/core';
import {BackendService} from 'src/app/services/backendService.service';
import {Compartment, StateEffect} from '@codemirror/state';
import {CodeMirrorComponent} from '@sandkasten/codemirror6-editor';
import {LanguageDescription} from '@codemirror/language';
import {CODE_DEFAULTS, LANGUAGES} from '../languages';
import {SafeHTMLPipe} from '../../safe-html.pipe';
import {ReactiveFormsModule, FormsModule} from '@angular/forms';
import {Router} from '@angular/router';
import {
keymap,
highlightSpecialChars,
drawSelection,
highlightActiveLine,
dropCursor,
rectangularSelection,
crosshairCursor,
lineNumbers,
highlightActiveLineGutter,
gutter,
keymap,
highlightSpecialChars,
drawSelection,
highlightActiveLine,
dropCursor,
rectangularSelection,
crosshairCursor,
lineNumbers,
highlightActiveLineGutter,
gutter,
} from '@codemirror/view';
import { Extension, EditorState } from '@codemirror/state';
import {Extension, EditorState} from '@codemirror/state';
import {
defaultHighlightStyle,
syntaxHighlighting,
indentOnInput,
bracketMatching,
foldGutter,
foldKeymap,
defaultHighlightStyle,
syntaxHighlighting,
indentOnInput,
bracketMatching,
foldGutter,
foldKeymap,
} from '@codemirror/language';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
import {defaultKeymap, history, historyKeymap} from '@codemirror/commands';
import {searchKeymap, highlightSelectionMatches} from '@codemirror/search';
import {
autocompletion,
completionKeymap,
closeBrackets,
closeBracketsKeymap,
autocompletion,
completionKeymap,
closeBrackets,
closeBracketsKeymap,
} from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint';
import {lintKeymap} from '@codemirror/lint';
import {
Connection,
getDocument,
peerExtension,
Connection,
getDocument,
peerExtension,
} from '../../services/connection.service';
import {environment} from "../../../environments/environment";
const basicSetup: Extension = (() => [
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...lintKeymap,
]),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...lintKeymap,
]),
])();
@Component({
selector: 'app-editor',
templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss'],
standalone: true,
imports: [
CodeMirrorComponent,
ReactiveFormsModule,
FormsModule,
SafeHTMLPipe,
],
selector: 'app-editor',
templateUrl: './editor.component.html',
styleUrls: ['./editor.component.scss'],
standalone: true,
imports: [
CodeMirrorComponent,
ReactiveFormsModule,
FormsModule,
SafeHTMLPipe,
],
})
export class EditorComponent {
isLoaded: boolean = false; // Pour vérifier si le chargement est terminé
readonly languages: LanguageDescription[] = LANGUAGES;
// Mode par défaut
private _selectedLanguage = this.languages.find(
(lang) => lang.name === 'JavaScript'
)!;
get selectedLanguage(): LanguageDescription {
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];
isLoaded: boolean = false; // Pour vérifier si le chargement est terminé
readonly languages: LanguageDescription[] = LANGUAGES;
// Mode par défaut
private _selectedLanguage = this.languages.find(
(lang) => lang.name === 'JavaScript'
)!;
get selectedLanguage(): LanguageDescription {
return this._selectedLanguage;
}
this.selectedLanguage.load().then((language) => {
this.codemirror.editor?.dispatch({
effects: this.languageCompartment.reconfigure(language),
});
});
}
private _linesNumbers: boolean = true;
get linesNumbers() {
return this._linesNumbers;
}
set linesNumbers(lines: boolean) {
this._linesNumbers = lines;
this.codemirror.editor?.dispatch({
effects: this.gutterCompartment.reconfigure(
lines ? lineNumbers() : gutter({})
),
});
}
// Contenu de l'éditeur que l'on passera au serveur
editorContent: string =
CODE_DEFAULTS[this.selectedLanguage.name as keyof typeof CODE_DEFAULTS];
resultContent: string = '';
// Message d'erreur
errorMessage: string = '';
@ViewChild(CodeMirrorComponent) private codemirror!: CodeMirrorComponent;
private readonly languageCompartment = new Compartment();
private readonly gutterCompartment = new Compartment();
protected readonly extensions: Extension[] = [
basicSetup,
this.gutterCompartment.of(lineNumbers()),
this.languageCompartment.of(this.selectedLanguage.support!),
];
private client: WebSocket | undefined;
@Input()
set idRoom(idRoom: string) {
if (idRoom === undefined) {
return;
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({
effects: this.languageCompartment.reconfigure(language),
});
});
}
private _linesNumbers: boolean = true;
get linesNumbers() {
return this._linesNumbers;
}
set linesNumbers(lines: boolean) {
this._linesNumbers = lines;
this.codemirror.editor?.dispatch({
effects: this.gutterCompartment.reconfigure(
lines ? lineNumbers() : gutter({})
),
});
}
// Contenu de l'éditeur que l'on passera au serveur
editorContent: string =
CODE_DEFAULTS[this.selectedLanguage.name as keyof typeof CODE_DEFAULTS];
resultContent: string = '';
// Message d'erreur
errorMessage: string = '';
@ViewChild(CodeMirrorComponent) private codemirror!: CodeMirrorComponent;
private readonly languageCompartment = new Compartment();
private readonly gutterCompartment = new Compartment();
protected readonly extensions: Extension[] = [
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}`);
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;
}
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();
}
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 { SSE } from 'sse.js';
import { Observable, Subject } from 'rxjs';
import {Injectable} from '@angular/core';
import {SSE} from 'sse.js';
import {Observable, Subject} from 'rxjs';
import {environment} from "../../environments/environment";
export type ExecutionMessage = {
type: 'stdout' | 'stderr' | 'exit';
text: string;
type: 'stdout' | 'stderr' | 'exit';
text: string;
};
@Injectable({
providedIn: 'root',
providedIn: 'root',
})
export class BackendService {
private apiUrl = 'http://localhost:3000';
private resultSubject = new Subject<ExecutionMessage>();
constructor() {}
async createRoom(code: string) {
const reponse = await fetch(`${this.apiUrl}/live`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code }),
});
return reponse.text();
}
executeCode(code: string, language: string) {
const sse = new SSE(`${this.apiUrl}/run`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'text/event-stream',
},
payload: JSON.stringify({ code, language }),
});
sse.addEventListener('message', (event: MessageEvent<string>) => {
const result = event.data;
// @ts-expect-error The type is not declared although present
const type = event.id;
const text = decodeURIComponent(result.replace(/%00/g, ''));
if (type === 'end') {
sse.close();
}
this.resultSubject.next({ type, text });
});
}
getResult(): Observable<ExecutionMessage> {
return this.resultSubject.asObservable();
}
private apiUrl = environment.apiUrl;
private resultSubject = new Subject<ExecutionMessage>();
constructor() {
}
async createRoom(code: string) {
const reponse = await fetch(`${this.apiUrl}/live`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({code}),
});
return reponse.text();
}
executeCode(code: string, language: string) {
const sse = new SSE(`${this.apiUrl}/run`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'text/event-stream',
},
payload: JSON.stringify({code, language}),
});
sse.addEventListener('message', (event: MessageEvent<string>) => {
const result = event.data;
// @ts-expect-error The type is not declared although present
const type = event.id;
const text = decodeURIComponent(result.replace(/%00/g, ''));
if (type === 'end') {
sse.close();
}
this.resultSubject.next({type, text});
});
}
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