Format the code
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
d86f2779a5
commit
1b96aced93
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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<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();
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: 'http://localhost:3000',
|
||||
webSocketUrl: 'ws://localhost:3000',
|
||||
production: false,
|
||||
apiUrl: 'http://localhost:3000',
|
||||
webSocketUrl: 'ws://localhost:3000',
|
||||
};
|
Loading…
Reference in new issue