Add some settings to the editor
continuous-integration/drone/push Build is passing Details

editor-settings
Colin FRIZOT 11 months ago
parent 90f83ec698
commit f67de3d682

@ -1,107 +1,126 @@
<div id="editor"> <div id="editor">
<div id="editor-bar-header"> <div id="editor-bar-header">
<div class="editor-section-bar-header"> <div class="editor-section-bar-header">
<div class="param-editor"> <div class="param-editor">
<label for="fileInput"> <label for="fileInput">
<svg <svg
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g> <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g <g
id="SVGRepo_tracerCarrier" id="SVGRepo_tracerCarrier"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round"></g> stroke-linejoin="round"></g>
<g id="SVGRepo_iconCarrier"> <g id="SVGRepo_iconCarrier">
<path <path
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M1 5C1 3.34315 2.34315 2 4 2H8.55848C9.84977 2 10.9962 2.82629 11.4045 4.05132L11.7208 5H20C21.1046 5 22 5.89543 22 7V9.00961C23.1475 9.12163 23.9808 10.196 23.7695 11.3578L22.1332 20.3578C21.9603 21.3087 21.132 22 20.1654 22H3C1.89543 22 1 21.1046 1 20V5ZM20 9V7H11.7208C10.8599 7 10.0956 6.44914 9.82339 5.63246L9.50716 4.68377C9.37105 4.27543 8.98891 4 8.55848 4H4C3.44772 4 3 4.44772 3 5V12.2709L3.35429 10.588C3.54913 9.66249 4.36562 9 5.31139 9H20ZM3.36634 20C3.41777 19.9109 3.4562 19.8122 3.47855 19.706L5.31139 11L21 11H21.8018L20.1654 20L3.36634 20Z" d="M1 5C1 3.34315 2.34315 2 4 2H8.55848C9.84977 2 10.9962 2.82629 11.4045 4.05132L11.7208 5H20C21.1046 5 22 5.89543 22 7V9.00961C23.1475 9.12163 23.9808 10.196 23.7695 11.3578L22.1332 20.3578C21.9603 21.3087 21.132 22 20.1654 22H3C1.89543 22 1 21.1046 1 20V5ZM20 9V7H11.7208C10.8599 7 10.0956 6.44914 9.82339 5.63246L9.50716 4.68377C9.37105 4.27543 8.98891 4 8.55848 4H4C3.44772 4 3 4.44772 3 5V12.2709L3.35429 10.588C3.54913 9.66249 4.36562 9 5.31139 9H20ZM3.36634 20C3.41777 19.9109 3.4562 19.8122 3.47855 19.706L5.31139 11L21 11H21.8018L20.1654 20L3.36634 20Z"
fill="#000000"></path> fill="#000000"></path>
</g> </g>
</svg> </svg>
</label> </label>
<input <input
style="display: none" style="display: none"
type="file" type="file"
id="fileInput" id="fileInput"
(change)="loadFromFile($event)" /> (change)="loadFromFile($event)"/>
</div> </div>
<div class="param-editor"> <div class="param-editor">
<button class="button-icon" type="button" (click)="saveToFile()"> <button class="button-icon" type="button" (click)="saveToFile()">
<svg <svg
width="800px" width="800px"
height="800px" height="800px"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<path <path
d="M3 15C3 17.8284 3 19.2426 3.87868 20.1213C4.75736 21 6.17157 21 9 21H15C17.8284 21 19.2426 21 20.1213 20.1213C21 19.2426 21 17.8284 21 15" d="M3 15C3 17.8284 3 19.2426 3.87868 20.1213C4.75736 21 6.17157 21 9 21H15C17.8284 21 19.2426 21 20.1213 20.1213C21 19.2426 21 17.8284 21 15"
stroke="#1C274C" stroke="#1C274C"
stroke-width="1.5" stroke-width="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" /> stroke-linejoin="round"/>
<path <path
d="M12 3V16M12 16L16 11.625M12 16L8 11.625" d="M12 3V16M12 16L16 11.625M12 16L8 11.625"
stroke="#1C274C" stroke="#1C274C"
stroke-width="1.5" stroke-width="1.5"
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" /> stroke-linejoin="round"/>
</svg> </svg>
</button> </button>
</div> </div>
</div> </div>
<div class="editor-section-bar-header"> <div class="editor-section-bar-header">
@if (errorMessage) { @if (errorMessage) {
<div class="param-editor"> <div class="param-editor">
<p style="color: red">{{ errorMessage }}</p> <p style="color: red">{{ errorMessage }}</p>
</div> </div>
} }
<select id="language" [(ngModel)]="selectedLanguage"> <select id="language" [(ngModel)]="selectedLanguage">
@for (language of languages; track language.name) { @for (language of languages; track language.name) {
<option [ngValue]="language">{{ language.name }}</option> <option [ngValue]="language">{{ language.name }}</option>
} }
</select> </select>
<div class="param-editor">
<button
class="button-icon button-run"
type="button"
(click)="onRunButtonClicked()"
[disabled]="isLoaded">
<div>RUN</div>
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="#000000">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path
d="M2.78 2L2 2.41v12l.78.42 9-6V8l-9-6zM3 13.48V3.35l7.6 5.07L3 13.48z"></path>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 14.683l8.78-5.853V8L6 2.147V3.35l7.6 5.07L6 13.48v1.203z"></path>
</g>
</svg>
</button>
</div>
</div>
</div>
<div class="editor-center">
<div class="editor-child-element">
<codemirror6-editor [(ngModel)]="editorContent" [extensions]="extensions">
</codemirror6-editor>
</div>
<div class="editor-child-element">
<pre id="resultDiv" [innerHTML]="resultContent | safeHTML"></pre>
</div>
</div>
<div class="editor-bottom">
<div>
<label for="showLines">Afficher les numéros de lignes</label>
<input type="checkbox" [(ngModel)]="linesNumbers" id="showLines"/>
</div>
<div>
<label for="indentUnit">Nombre d'espaces à l'indentation</label>
<input type="number" [(ngModel)]="indentUnit" id="indentUnit" min="0" max="20"/>
</div>
<div>
<label for="wrapLines">Line Wrapping</label>
<input type="checkbox" [(ngModel)]="lineWrapping" id="wrapLines"/>
</div>
</div>
<div class="param-editor">
<button
class="button-icon button-run"
type="button"
(click)="onRunButtonClicked()"
[disabled]="isLoaded">
<div>RUN</div>
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
fill="#000000">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path
d="M2.78 2L2 2.41v12l.78.42 9-6V8l-9-6zM3 13.48V3.35l7.6 5.07L3 13.48z"></path>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6 14.683l8.78-5.853V8L6 2.147V3.35l7.6 5.07L6 13.48v1.203z"></path>
</g>
</svg>
</button>
</div>
</div>
</div>
<div class="editor-center">
<div class="editor-child-element">
<codemirror6-editor [(ngModel)]="editorContent" [extensions]="extensions">
</codemirror6-editor>
</div>
<div class="editor-child-element">
<pre id="resultDiv" [innerHTML]="resultContent | safeHTML"></pre>
</div>
</div>
</div> </div>

@ -1,179 +1,216 @@
import { Component, ViewChild } from '@angular/core'; import {Component, ViewChild} from '@angular/core';
import { CodeExecutionService } from 'src/app/services/codeExecution.service'; import {CodeExecutionService} from 'src/app/services/codeExecution.service';
import { Compartment } 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 {Extension, EditorState, Compartment} from '@codemirror/state';
import { import {
keymap, keymap,
highlightSpecialChars, highlightSpecialChars,
drawSelection, drawSelection,
highlightActiveLine, highlightActiveLine,
dropCursor, dropCursor,
rectangularSelection, rectangularSelection,
crosshairCursor, crosshairCursor,
lineNumbers, lineNumbers,
highlightActiveLineGutter, highlightActiveLineGutter,
gutter, gutter,
EditorView,
} from '@codemirror/view'; } from '@codemirror/view';
import { Extension, EditorState } from '@codemirror/state';
import { import {
defaultHighlightStyle, defaultHighlightStyle,
syntaxHighlighting, syntaxHighlighting,
indentOnInput, indentOnInput,
bracketMatching, bracketMatching,
foldGutter, foldGutter,
foldKeymap, indentUnit,
foldKeymap,
} from '@codemirror/language'; } from '@codemirror/language';
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; import {defaultKeymap, history, historyKeymap, indentWithTab} 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';
const DEFAULT_INDENT_UNIT = 4;
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,
]), indentWithTab,
]),
])(); ])();
@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; set selectedLanguage(value: LanguageDescription) {
if (value.name in CODE_DEFAULTS) { this._selectedLanguage = value;
this.editorContent = if (value.name in CODE_DEFAULTS) {
CODE_DEFAULTS[value.name as keyof typeof 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({})
),
});
}
private _indentUnit = DEFAULT_INDENT_UNIT;
get indentUnit() {
return this._indentUnit;
}
set indentUnit(unit: number) {
this._indentUnit = unit;
this.codemirror.editor?.dispatch({
effects: this.indentUnitCompartment.reconfigure(
unit ? indentUnit.of(" ".repeat(unit)) : indentUnit.of(" ".repeat(DEFAULT_INDENT_UNIT))),
});
}
private _lineWrapping = true;
get lineWrapping() {
return this._lineWrapping;
}
set lineWrapping(wrap: boolean) {
this._lineWrapping = wrap;
this.codemirror.editor?.dispatch({
effects: this.lineWrappingCompartment.reconfigure(wrap ? EditorView.lineWrapping : []),
});
} }
this.selectedLanguage.load().then((language) => {
this.codemirror.editor?.dispatch({ // Contenu de l'éditeur que l'on passera au serveur
effects: this.languageCompartment.reconfigure(language), editorContent: string = CODE_DEFAULTS[this.selectedLanguage.name as keyof typeof CODE_DEFAULTS];
}); resultContent: string = '';
});
} // Message d'erreur
private _linesNumbers: boolean = true; errorMessage: string = '';
get linesNumbers() {
return this._linesNumbers; @ViewChild(CodeMirrorComponent) private codemirror!: CodeMirrorComponent;
}
set linesNumbers(lines: boolean) { private readonly languageCompartment = new Compartment();
this._linesNumbers = lines; private readonly gutterCompartment = new Compartment();
this.codemirror.editor?.dispatch({ private readonly indentUnitCompartment = new Compartment();
effects: this.gutterCompartment.reconfigure( private readonly lineWrappingCompartment = new Compartment();
lines ? lineNumbers() : gutter({})
), protected readonly extensions: Extension[] = [
}); basicSetup,
} this.gutterCompartment.of(lineNumbers()),
this.languageCompartment.of(this.selectedLanguage.support!),
// Contenu de l'éditeur que l'on passera au serveur this.indentUnitCompartment.of(indentUnit.of(" ".repeat(this._indentUnit))),
editorContent: string = this.lineWrappingCompartment.of(EditorView.lineWrapping),
CODE_DEFAULTS[this.selectedLanguage.name as keyof typeof CODE_DEFAULTS]; ];
resultContent: string = '';
constructor(private codeExecutionService: CodeExecutionService) {
// Message d'erreur }
errorMessage: string = '';
// Efface le contenu de l'éditeur
@ViewChild(CodeMirrorComponent) private codemirror!: CodeMirrorComponent; clear(): void {
this.editorContent = '';
private readonly languageCompartment = new Compartment(); }
private readonly gutterCompartment = new Compartment();
protected readonly extensions: Extension[] = [ onRunButtonClicked() {
basicSetup, // Le code à exécuter est le contenu de l'éditeur
this.gutterCompartment.of(lineNumbers()), const codeToExecute = this.editorContent;
this.languageCompartment.of(this.selectedLanguage.support!),
]; this.codeExecutionService.executeCode(
codeToExecute,
constructor(private codeExecutionService: CodeExecutionService) {} this.selectedLanguage.name
);
// Efface le contenu de l'éditeur
clear(): void { this.resultContent = '';
this.editorContent = ''; }
}
loadFromFile(event: Event) {
onRunButtonClicked() { const file = (event.target as HTMLInputElement).files![0];
// Le code à exécuter est le contenu de l'éditeur for (const language of this.languages) {
const codeToExecute = this.editorContent; if (language.extensions.some((ext) => file.name.endsWith(`.${ext}`))) {
this.selectedLanguage = language;
this.codeExecutionService.executeCode( const reader = new FileReader();
codeToExecute, reader.onload = (event) => {
this.selectedLanguage.name this.editorContent = event.target!.result as string;
); this.errorMessage = '';
};
this.resultContent = ''; reader.readAsText(file);
} return;
}
loadFromFile(event: Event) { }
const file = (event.target as HTMLInputElement).files![0]; const extensions = this.languages.flatMap((lang) => lang.extensions);
for (const language of this.languages) { this.errorMessage = `Unsupported language. Please select one of the following languages: ${extensions.join(', ')}.`;
if (language.extensions.some((ext) => file.name.endsWith(`.${ext}`))) { console.error(this.errorMessage);
this.selectedLanguage = language; }
const reader = new FileReader();
reader.onload = (event) => { saveToFile() {
this.editorContent = event.target!.result as string; const blob = new Blob([this.editorContent], {type: 'text/plain'});
this.errorMessage = ''; const a = document.createElement('a');
}; a.download = `code.${this.selectedLanguage.extensions![0]}`;
reader.readAsText(file); a.href = URL.createObjectURL(blob);
return; 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();
}
} }

Loading…
Cancel
Save