diff --git a/src/app/components/editor/editor.component.ts b/src/app/components/editor/editor.component.ts index 026bf68..d2da79a 100644 --- a/src/app/components/editor/editor.component.ts +++ b/src/app/components/editor/editor.component.ts @@ -1,227 +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"; +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; + 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]; } - - 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.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; } - 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(); + // 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); + } + + 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(); + } } diff --git a/src/app/services/backendService.service.ts b/src/app/services/backendService.service.ts index f7a5bb4..d6808f4 100644 --- a/src/app/services/backendService.service.ts +++ b/src/app/services/backendService.service.ts @@ -1,59 +1,58 @@ -import {Injectable} from '@angular/core'; -import {SSE} from 'sse.js'; -import {Observable, Subject} from 'rxjs'; -import {environment} from "../../environments/environment"; +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 = environment.apiUrl; - - private resultSubject = new Subject(); - - 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) => { - 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 { - return this.resultSubject.asObservable(); - } + private apiUrl = environment.apiUrl; + + private resultSubject = new Subject(); + + 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) => { + 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 { + return this.resultSubject.asObservable(); + } } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 105ffc9..0900c39 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,5 +1,5 @@ export const environment = { - production: true, - apiUrl: 'https://tododomain.com', - webSocketUrl: 'ws://tododomain.com', + production: true, + apiUrl: 'https://tododomain.com', + webSocketUrl: 'ws://tododomain.com', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index e0e903a..0411da0 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,5 +1,5 @@ export const environment = { - production: false, - apiUrl: 'http://localhost:3000', - webSocketUrl: 'ws://localhost:3000', -}; \ No newline at end of file + production: false, + apiUrl: 'http://localhost:3000', + webSocketUrl: 'ws://localhost:3000', +};