From 5902a5898ea9c36ef57d21fee5673117c7a693e2 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 21:38:31 +0200 Subject: [PATCH 01/23] Make Monaco standalone --- .../hestia/git-diff-report/git-diff-report.module.ts | 4 ++-- .../testwise-coverage-report.module.ts | 4 ++-- .../manage/programming-exercise-management.module.ts | 4 ++-- .../manage/update/programming-exercise-update.module.ts | 4 ++-- .../programming/shared/code-editor/code-editor.module.ts | 4 ++-- .../exercise-hint/shared/exercise-hint-shared.module.ts | 4 ++-- .../app/shared/markdown-editor/markdown-editor.module.ts | 4 ++-- .../shared/monaco-editor/monaco-diff-editor.component.ts | 1 + .../app/shared/monaco-editor/monaco-editor.component.ts | 1 + .../webapp/app/shared/monaco-editor/monaco-editor.module.ts | 6 ++---- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts index d72ed6f72de0..17b5682de4cb 100644 --- a/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts +++ b/src/main/webapp/app/exercises/programming/hestia/git-diff-report/git-diff-report.module.ts @@ -6,12 +6,12 @@ import { GitDiffFileComponent } from 'app/exercises/programming/hestia/git-diff- import { GitDiffReportModalComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-report-modal.component'; import { GitDiffFilePanelComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel.component'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { GitDiffFilePanelTitleComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file-panel-title.component'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component'; @NgModule({ - imports: [ArtemisSharedModule, NgbAccordionModule, MonacoEditorModule, ArtemisSharedComponentModule], + imports: [ArtemisSharedModule, NgbAccordionModule, MonacoDiffEditorComponent, ArtemisSharedComponentModule], declarations: [GitDiffFilePanelComponent, GitDiffFilePanelTitleComponent, GitDiffReportComponent, GitDiffFileComponent, GitDiffReportModalComponent, GitDiffLineStatComponent], exports: [GitDiffReportComponent, GitDiffReportModalComponent, GitDiffLineStatComponent], }) diff --git a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts index 04e5a60b0ad6..c37bc7a255da 100644 --- a/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts +++ b/src/main/webapp/app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.module.ts @@ -4,10 +4,10 @@ import { TestwiseCoverageReportModalComponent } from 'app/exercises/programming/ import { TestwiseCoverageReportComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-report.component'; import { TestwiseCoverageFileComponent } from 'app/exercises/programming/hestia/testwise-coverage-report/testwise-coverage-file.component'; import { MatExpansionModule } from '@angular/material/expansion'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; +import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; @NgModule({ - imports: [ArtemisSharedModule, MatExpansionModule, MonacoEditorModule], + imports: [ArtemisSharedModule, MatExpansionModule, MonacoEditorComponent], declarations: [TestwiseCoverageFileComponent, TestwiseCoverageReportComponent, TestwiseCoverageReportModalComponent], exports: [TestwiseCoverageFileComponent, TestwiseCoverageReportModalComponent, TestwiseCoverageReportComponent], }) diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts index 1cb726111a17..ce2056d24a44 100644 --- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts +++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management.module.ts @@ -28,7 +28,7 @@ import { ArtemisCodeEditorModule } from 'app/exercises/programming/shared/code-e import { DetailModule } from 'app/detail-overview-list/detail.module'; import { IrisModule } from 'app/iris/iris.module'; import { ArtemisExerciseModule } from 'app/exercises/shared/exercise/exercise.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; +import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; @NgModule({ imports: [ @@ -56,7 +56,7 @@ import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.modul ArtemisExerciseModule, DetailModule, IrisModule, - MonacoEditorModule, + MonacoEditorComponent, ], declarations: [ ProgrammingExerciseDetailComponent, diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts index 7ac399d6daeb..b9878c9e2719 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.module.ts @@ -32,7 +32,7 @@ import { FormsModule } from 'app/forms/forms.module'; import { ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-build-plan-checkout-directories.component'; import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component'; import { ProgrammingExerciseTheiaComponent } from 'app/exercises/programming/manage/update/update-components/theia/programming-exercise-theia.component'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; +import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; @NgModule({ imports: [ @@ -57,7 +57,7 @@ import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.modul FormsModule, ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent, ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent, - MonacoEditorModule, + MonacoEditorComponent, ProgrammingExerciseTheiaComponent, ], declarations: [ diff --git a/src/main/webapp/app/exercises/programming/shared/code-editor/code-editor.module.ts b/src/main/webapp/app/exercises/programming/shared/code-editor/code-editor.module.ts index 7b23b4959793..49583cbc95d4 100644 --- a/src/main/webapp/app/exercises/programming/shared/code-editor/code-editor.module.ts +++ b/src/main/webapp/app/exercises/programming/shared/code-editor/code-editor.module.ts @@ -21,9 +21,9 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TreeviewModule } from 'app/exercises/programming/shared/code-editor/treeview/treeview.module'; import { CodeEditorHeaderComponent } from 'app/exercises/programming/shared/code-editor/header/code-editor-header.component'; import { CodeEditorFileBrowserBadgeComponent } from 'app/exercises/programming/shared/code-editor/file-browser/code-editor-file-browser-badge.component'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { CodeEditorMonacoComponent } from 'app/exercises/programming/shared/code-editor/monaco/code-editor-monaco.component'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; +import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; @NgModule({ imports: [ @@ -33,7 +33,7 @@ import { ArtemisSharedComponentModule } from 'app/shared/components/shared-compo TreeviewModule.forRoot(), ArtemisProgrammingExerciseInstructionsEditorModule, ArtemisProgrammingManualAssessmentModule, - MonacoEditorModule, + MonacoEditorComponent, ArtemisSharedComponentModule, ], declarations: [ diff --git a/src/main/webapp/app/exercises/shared/exercise-hint/shared/exercise-hint-shared.module.ts b/src/main/webapp/app/exercises/shared/exercise-hint/shared/exercise-hint-shared.module.ts index ef5dd4dce389..08a2b1c8ee07 100644 --- a/src/main/webapp/app/exercises/shared/exercise-hint/shared/exercise-hint-shared.module.ts +++ b/src/main/webapp/app/exercises/shared/exercise-hint/shared/exercise-hint-shared.module.ts @@ -4,10 +4,10 @@ import { CastToCodeHintPipe } from 'app/exercises/shared/exercise-hint/services/ import { SolutionEntryComponent } from 'app/exercises/shared/exercise-hint/shared/solution-entry.component'; import { CodeHintContainerComponent } from 'app/exercises/shared/exercise-hint/shared/code-hint-container.component'; import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; +import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; @NgModule({ - imports: [ArtemisSharedModule, ArtemisMarkdownModule, MonacoEditorModule], + imports: [ArtemisSharedModule, ArtemisMarkdownModule, MonacoEditorComponent], declarations: [SolutionEntryComponent, CodeHintContainerComponent, CastToCodeHintPipe], exports: [SolutionEntryComponent, CodeHintContainerComponent, CastToCodeHintPipe], }) diff --git a/src/main/webapp/app/shared/markdown-editor/markdown-editor.module.ts b/src/main/webapp/app/shared/markdown-editor/markdown-editor.module.ts index 198dad328789..a911ddf74ceb 100644 --- a/src/main/webapp/app/shared/markdown-editor/markdown-editor.module.ts +++ b/src/main/webapp/app/shared/markdown-editor/markdown-editor.module.ts @@ -5,11 +5,11 @@ import { ArtemisSharedModule } from 'app/shared/shared.module'; import { MatMenuModule } from '@angular/material/menu'; import { MatButtonModule } from '@angular/material/button'; import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { DragDropModule } from '@angular/cdk/drag-drop'; +import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; @NgModule({ - imports: [ArtemisSharedModule, MonacoEditorModule, FormsModule, ArtemisColorSelectorModule, MatMenuModule, MatButtonModule, DragDropModule], + imports: [ArtemisSharedModule, MonacoEditorComponent, FormsModule, ArtemisColorSelectorModule, MatMenuModule, MatButtonModule, DragDropModule], declarations: [MarkdownEditorMonacoComponent], exports: [MarkdownEditorMonacoComponent], }) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index dd5b624d6554..1bdd6d1f742a 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -9,6 +9,7 @@ export type MonacoEditorDiffText = { original: string; modified: string }; @Component({ selector: 'jhi-monaco-diff-editor', template: '', + standalone: true, styleUrls: ['monaco-diff-editor.component.scss'], encapsulation: ViewEncapsulation.None, }) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index a0e28544e914..d6da9b5f6013 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -18,6 +18,7 @@ export const MAX_TAB_SIZE = 8; @Component({ selector: 'jhi-monaco-editor', template: '', + standalone: true, styleUrls: ['monaco-editor.component.scss'], encapsulation: ViewEncapsulation.None, }) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts index 88f841d9407a..42c8868449c2 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts @@ -1,6 +1,4 @@ import { NgModule } from '@angular/core'; -import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component'; import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; import * as monaco from 'monaco-editor'; @@ -10,7 +8,7 @@ monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MA monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); @NgModule({ - declarations: [MonacoEditorComponent, MonacoDiffEditorComponent], - exports: [MonacoEditorComponent, MonacoDiffEditorComponent], + declarations: [], + exports: [], }) export class MonacoEditorModule {} From f6e0e26f90a58c3527577b3547856d3f6104fac5 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 21:48:17 +0200 Subject: [PATCH 02/23] Use inject function --- .../monaco-diff-editor.component.ts | 16 +++++++------- .../monaco-editor/monaco-editor.component.ts | 22 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index 1bdd6d1f742a..ddb702448efb 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewEncapsulation } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewEncapsulation, inject } from '@angular/core'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; import * as monaco from 'monaco-editor'; @@ -30,17 +30,17 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { @Output() onReadyForDisplayChange = new EventEmitter(); - constructor( - private themeService: ThemeService, - elementRef: ElementRef, - renderer: Renderer2, - ) { + private readonly themeService = inject(ThemeService); + private readonly elementRef = inject(ElementRef); + private readonly renderer = inject(Renderer2); + + constructor() { /* * The constructor injects the editor along with its container into the empty template of this component. * This makes the editor available immediately (not just after ngOnInit), preventing errors when the methods * of this component are called. */ - this.monacoDiffEditorContainerElement = renderer.createElement('div'); + this.monacoDiffEditorContainerElement = this.renderer.createElement('div'); this._editor = monaco.editor.createDiffEditor(this.monacoDiffEditorContainerElement, { glyphMargin: true, minimap: { enabled: false }, @@ -61,7 +61,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { }, fontSize: 12, }); - renderer.appendChild(elementRef.nativeElement, this.monacoDiffEditorContainerElement); + this.renderer.appendChild(this.elementRef.nativeElement, this.monacoDiffEditorContainerElement); this.setupDiffListener(); this.setupContentHeightListeners(); } diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index d6da9b5f6013..fa14e12a38ca 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewEncapsulation } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewEncapsulation, inject } from '@angular/core'; import * as monaco from 'monaco-editor'; import { Subscription } from 'rxjs'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; @@ -40,20 +40,20 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { */ private static readonly DEFAULT_LINE_DECORATION_BUTTON_WIDTH = '2.3ch'; - constructor( - private readonly themeService: ThemeService, - elementRef: ElementRef, - private readonly renderer: Renderer2, - private readonly translateService: TranslateService, - ) { + private readonly themeService = inject(ThemeService); + private readonly renderer = inject(Renderer2); + private readonly translateService = inject(TranslateService); + private readonly elementRef = inject(ElementRef); + + constructor() { /* * The constructor injects the editor along with its container into the empty template of this component. * This makes the editor available immediately (not just after ngOnInit), preventing errors when the methods * of this component are called. */ - this.monacoEditorContainerElement = renderer.createElement('div'); - renderer.addClass(this.monacoEditorContainerElement, 'monaco-editor-container'); - renderer.addClass(this.monacoEditorContainerElement, 'monaco-shrink-to-fit'); + this.monacoEditorContainerElement = this.renderer.createElement('div'); + this.renderer.addClass(this.monacoEditorContainerElement, 'monaco-editor-container'); + this.renderer.addClass(this.monacoEditorContainerElement, 'monaco-shrink-to-fit'); this._editor = monaco.editor.create(this.monacoEditorContainerElement, { value: '', glyphMargin: true, @@ -67,7 +67,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { }); this._editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF); this.textEditorAdapter = new MonacoTextEditorAdapter(this._editor); - renderer.appendChild(elementRef.nativeElement, this.monacoEditorContainerElement); + this.renderer.appendChild(this.elementRef.nativeElement, this.monacoEditorContainerElement); } @Input() From 1c4cbfc55796680c182430af8a4d400c15cb4785 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 21:56:03 +0200 Subject: [PATCH 03/23] Add monaco editor service --- .../monaco-diff-editor.component.ts | 3 +++ .../monaco-editor/monaco-editor.component.ts | 3 +++ .../shared/monaco-editor/monaco-editor.module.ts | 7 ------- .../monaco-editor/monaco-editor.service.ts | 16 ++++++++++++++++ 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index ddb702448efb..2fcae9b96717 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -4,6 +4,7 @@ import { Theme, ThemeService } from 'app/core/theme/theme.service'; import * as monaco from 'monaco-editor'; import { Subscription } from 'rxjs'; import { Disposable } from 'app/shared/monaco-editor/model/actions/monaco-editor.util'; +import { MonacoEditorService } from './monaco-editor.service'; export type MonacoEditorDiffText = { original: string; modified: string }; @Component({ @@ -33,6 +34,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { private readonly themeService = inject(ThemeService); private readonly elementRef = inject(ElementRef); private readonly renderer = inject(Renderer2); + private readonly monacoEditorService = inject(MonacoEditorService); constructor() { /* @@ -64,6 +66,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { this.renderer.appendChild(this.elementRef.nativeElement, this.monacoDiffEditorContainerElement); this.setupDiffListener(); this.setupContentHeightListeners(); + this.monacoEditorService.foo(); } ngOnInit(): void { diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index fa14e12a38ca..334781053d24 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -12,6 +12,7 @@ import { TranslateService } from '@ngx-translate/core'; import { MonacoEditorOptionPreset } from 'app/shared/monaco-editor/model/monaco-editor-option-preset.model'; import { Disposable, EditorPosition, EditorRange, MonacoEditorTextModel } from 'app/shared/monaco-editor/model/actions/monaco-editor.util'; import { MonacoTextEditorAdapter } from 'app/shared/monaco-editor/model/actions/adapter/monaco-text-editor.adapter'; +import { MonacoEditorService } from 'app/shared/monaco-editor/monaco-editor.service'; export const MAX_TAB_SIZE = 8; @@ -44,6 +45,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { private readonly renderer = inject(Renderer2); private readonly translateService = inject(TranslateService); private readonly elementRef = inject(ElementRef); + private readonly monacoEditorService = inject(MonacoEditorService); constructor() { /* @@ -68,6 +70,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { this._editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF); this.textEditorAdapter = new MonacoTextEditorAdapter(this._editor); this.renderer.appendChild(this.elementRef.nativeElement, this.monacoEditorContainerElement); + this.monacoEditorService.foo(); } @Input() diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts index 42c8868449c2..cfa64e4e21b1 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts @@ -1,11 +1,4 @@ import { NgModule } from '@angular/core'; -import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; - -import * as monaco from 'monaco-editor'; - -monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID }); -monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG); -monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); @NgModule({ declarations: [], diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts new file mode 100644 index 000000000000..ab24f4999b04 --- /dev/null +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import * as monaco from 'monaco-editor'; +import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; + +@Injectable({ providedIn: 'root' }) +export class MonacoEditorService { + constructor() { + monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID }); + monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG); + monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); + } + + foo() { + // TODO: remove + } +} From 4ea9f2471a5e6e9c7a528e3a8c5de7195b903082 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 22:46:10 +0200 Subject: [PATCH 04/23] Use input and output signals --- .../monaco-diff-editor.component.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index 2fcae9b96717..2b3394372eaf 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewEncapsulation, inject } from '@angular/core'; +import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; import * as monaco from 'monaco-editor'; @@ -21,15 +21,8 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { listeners: Disposable[] = []; resizeObserver?: ResizeObserver; - @Input() - set allowSplitView(value: boolean) { - this._editor.updateOptions({ - renderSideBySide: value, - }); - } - - @Output() - onReadyForDisplayChange = new EventEmitter(); + allowSplitView = input(); + onReadyForDisplayChange = output(); private readonly themeService = inject(ThemeService); private readonly elementRef = inject(ElementRef); @@ -67,6 +60,12 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { this.setupDiffListener(); this.setupContentHeightListeners(); this.monacoEditorService.foo(); + + effect(() => { + this._editor.updateOptions({ + renderSideBySide: this.allowSplitView(), + }); + }); } ngOnInit(): void { From dc1694822da3bc78057559e6f6794f37af93ce35 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 23:02:24 +0200 Subject: [PATCH 05/23] Use input and output signals, part 2 --- .../monaco-editor/monaco-editor.component.ts | 66 ++++++++----------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index 334781053d24..8a49f217b8f5 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewEncapsulation, inject } from '@angular/core'; +import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; import * as monaco from 'monaco-editor'; import { Subscription } from 'rxjs'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; @@ -40,6 +40,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { * @private */ private static readonly DEFAULT_LINE_DECORATION_BUTTON_WIDTH = '2.3ch'; + private static readonly SHRINK_TO_FIT_CLASS = 'monaco-shrink-to-fit'; private readonly themeService = inject(ThemeService); private readonly renderer = inject(Renderer2); @@ -55,12 +56,11 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { */ this.monacoEditorContainerElement = this.renderer.createElement('div'); this.renderer.addClass(this.monacoEditorContainerElement, 'monaco-editor-container'); - this.renderer.addClass(this.monacoEditorContainerElement, 'monaco-shrink-to-fit'); + this.renderer.addClass(this.monacoEditorContainerElement, MonacoEditorComponent.SHRINK_TO_FIT_CLASS); this._editor = monaco.editor.create(this.monacoEditorContainerElement, { value: '', glyphMargin: true, minimap: { enabled: false }, - readOnly: this._readOnly, lineNumbersMinChars: 4, scrollBeyondLastLine: false, scrollbar: { @@ -71,46 +71,37 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { this.textEditorAdapter = new MonacoTextEditorAdapter(this._editor); this.renderer.appendChild(this.elementRef.nativeElement, this.monacoEditorContainerElement); this.monacoEditorService.foo(); - } - - @Input() - textChangedEmitDelay?: number; - // TODO: The CSS class below allows the editor to shrink in the CodeEditorContainerComponent. We should eventually remove this class and handle the editor size differently in the code editor grid. - @Input() - set shrinkToFit(value: boolean) { - if (value) { - this.renderer.addClass(this.monacoEditorContainerElement, 'monaco-shrink-to-fit'); - } else { - this.renderer.removeClass(this.monacoEditorContainerElement, 'monaco-shrink-to-fit'); - } - } + effect(() => { + // TODO: The CSS class below allows the editor to shrink in the CodeEditorContainerComponent. We should eventually remove this class and handle the editor size differently in the code editor grid. + if (this.shrinkToFit()) { + this.renderer.addClass(this.monacoEditorContainerElement, MonacoEditorComponent.SHRINK_TO_FIT_CLASS); + } else { + this.renderer.removeClass(this.monacoEditorContainerElement, MonacoEditorComponent.SHRINK_TO_FIT_CLASS); + } + }); - @Input() - set stickyScroll(value: boolean) { - this._editor.updateOptions({ - stickyScroll: { enabled: value }, + effect(() => { + this._editor.updateOptions({ + stickyScroll: { enabled: this.stickyScroll() }, + }); }); - } - @Input() - set readOnly(value: boolean) { - this._readOnly = value; - this._editor.updateOptions({ - readOnly: value, + effect(() => { + this._editor.updateOptions({ + readOnly: this.readOnly(), + }); }); } - private _readOnly: boolean = false; - - @Output() - textChanged = new EventEmitter(); - - @Output() - contentHeightChanged = new EventEmitter(); + textChangedEmitDelay = input(); + shrinkToFit = input(true); + stickyScroll = input(false); + readOnly = input(false); - @Output() - onBlurEditor = new EventEmitter(); + textChanged = output(); + contentHeightChanged = output(); + onBlurEditor = output(); private contentHeightListener?: Disposable; private textChangedListener?: Disposable; @@ -151,7 +142,8 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { private emitTextChangeEvent() { const newValue = this.getText(); - if (!this.textChangedEmitDelay) { + const delay = this.textChangedEmitDelay(); + if (!delay) { this.textChanged.emit(newValue); } else { if (this.textChangedEmitTimeout) { @@ -160,7 +152,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { } this.textChangedEmitTimeout = setTimeout(() => { this.textChanged.emit(newValue); - }, this.textChangedEmitDelay); + }, delay); } } From 89cf3fdb44891b6ddb866d9403b4c356524eef99 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 23:14:01 +0200 Subject: [PATCH 06/23] Reorganize attributes --- .../monaco-editor/monaco-editor.component.ts | 68 +++++++++++-------- .../monaco-editor.component.spec.ts | 20 +++--- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index 8a49f217b8f5..d75c443e72b8 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -24,24 +24,52 @@ export const MAX_TAB_SIZE = 8; encapsulation: ViewEncapsulation.None, }) export class MonacoEditorComponent implements OnInit, OnDestroy { - private _editor: monaco.editor.IStandaloneCodeEditor; - private textEditorAdapter: MonacoTextEditorAdapter; - private monacoEditorContainerElement: HTMLElement; - themeSubscription?: Subscription; + /** + * The default width of the line decoration button in the editor. We use the ch unit to avoid fixed pixel sizes. + * @private + */ + private static readonly DEFAULT_LINE_DECORATION_BUTTON_WIDTH = '2.3ch'; + private static readonly SHRINK_TO_FIT_CLASS = 'monaco-shrink-to-fit'; + + private readonly _editor: monaco.editor.IStandaloneCodeEditor; + private readonly textEditorAdapter: MonacoTextEditorAdapter; + private readonly monacoEditorContainerElement: HTMLElement; + + /* + * Elements, models, and actions of the editor. + */ models: MonacoEditorTextModel[] = []; lineWidgets: MonacoEditorLineWidget[] = []; - editorBuildAnnotations: MonacoEditorBuildAnnotation[] = []; + buildAnnotations: MonacoEditorBuildAnnotation[] = []; lineHighlights: MonacoEditorLineHighlight[] = []; actions: TextEditorAction[] = []; lineDecorationsHoverButton?: MonacoEditorLineDecorationsHoverButton; - /** - * The default width of the line decoration button in the editor. We use the ch unit to avoid fixed pixel sizes. - * @private + /* + * Inputs and outputs. */ - private static readonly DEFAULT_LINE_DECORATION_BUTTON_WIDTH = '2.3ch'; - private static readonly SHRINK_TO_FIT_CLASS = 'monaco-shrink-to-fit'; + textChangedEmitDelay = input(); + shrinkToFit = input(true); + stickyScroll = input(false); + readOnly = input(false); + + textChanged = output(); + contentHeightChanged = output(); + onBlurEditor = output(); + + /* + * Disposable listeners, subscriptions, and timeouts. + */ + private contentHeightListener?: Disposable; + private textChangedListener?: Disposable; + private blurEditorWidgetListener?: Disposable; + private textChangedEmitTimeout?: NodeJS.Timeout; + private themeSubscription?: Subscription; + + /* + * Injected services and elements. + */ private readonly themeService = inject(ThemeService); private readonly renderer = inject(Renderer2); private readonly translateService = inject(TranslateService); @@ -94,20 +122,6 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { }); } - textChangedEmitDelay = input(); - shrinkToFit = input(true); - stickyScroll = input(false); - readOnly = input(false); - - textChanged = output(); - contentHeightChanged = output(); - onBlurEditor = output(); - - private contentHeightListener?: Disposable; - private textChangedListener?: Disposable; - private blurEditorWidgetListener?: Disposable; - private textChangedEmitTimeout?: NodeJS.Timeout; - ngOnInit(): void { const resizeObserver = new ResizeObserver(() => { this._editor.layout(); @@ -264,10 +278,10 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { } disposeAnnotations() { - this.editorBuildAnnotations.forEach((o) => { + this.buildAnnotations.forEach((o) => { o.dispose(); }); - this.editorBuildAnnotations = []; + this.buildAnnotations = []; } disposeLineHighlights(): void { @@ -321,7 +335,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { ); editorBuildAnnotation.addToEditor(); editorBuildAnnotation.setOutdatedAndUpdate(outdated); - this.editorBuildAnnotations.push(editorBuildAnnotation); + this.buildAnnotations.push(editorBuildAnnotation); } } diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts index ae34f743a3a7..404d0095df04 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts @@ -118,9 +118,9 @@ describe('MonacoEditorComponent', () => { comp.setAnnotations(buildAnnotationArray, false); comp.setText(multiLineText); const element = document.getElementById(buildAnnotationId); - expect(comp.editorBuildAnnotations).toHaveLength(1); + expect(comp.buildAnnotations).toHaveLength(1); expect(element).not.toBeNull(); - expect(element).toEqual(comp.editorBuildAnnotations[0].getGlyphMarginDomNode()); + expect(element).toEqual(comp.buildAnnotations[0].getGlyphMarginDomNode()); }); it('should not display build annotations that are out of bounds', () => { @@ -130,28 +130,28 @@ describe('MonacoEditorComponent', () => { comp.setAnnotations(buildAnnotationArray, false); comp.setText(singleLineText); const element = document.getElementById(buildAnnotationId); - expect(comp.editorBuildAnnotations).toHaveLength(1); + expect(comp.buildAnnotations).toHaveLength(1); // Ensure that the element is actually there, but not displayed in the DOM. expect(element).toBeNull(); - expect(comp.editorBuildAnnotations[0].getGlyphMarginDomNode().id).toBe(buildAnnotationId); + expect(comp.buildAnnotations[0].getGlyphMarginDomNode().id).toBe(buildAnnotationId); }); it('should mark build annotations as outdated if specified', () => { fixture.detectChanges(); comp.setText(multiLineText); comp.setAnnotations(buildAnnotationArray, true); - expect(comp.editorBuildAnnotations).toHaveLength(1); - expect(comp.editorBuildAnnotations[0].isOutdated()).toBeTrue(); + expect(comp.buildAnnotations).toHaveLength(1); + expect(comp.buildAnnotations[0].isOutdated()).toBeTrue(); }); it('should mark build annotations as outdated when a keyboard input is made', () => { fixture.detectChanges(); comp.setText(multiLineText); comp.setAnnotations(buildAnnotationArray, false); - expect(comp.editorBuildAnnotations).toHaveLength(1); - expect(comp.editorBuildAnnotations[0].isOutdated()).toBeFalse(); + expect(comp.buildAnnotations).toHaveLength(1); + expect(comp.buildAnnotations[0].isOutdated()).toBeFalse(); comp.triggerKeySequence('typing'); - expect(comp.editorBuildAnnotations[0].isOutdated()).toBeTrue(); + expect(comp.buildAnnotations[0].isOutdated()).toBeTrue(); }); it('should highlight line ranges with the specified classnames', () => { @@ -218,7 +218,7 @@ describe('MonacoEditorComponent', () => { comp.addLineWidget(1, 'widget', document.createElement('div')); comp.setLineDecorationsHoverButton('testClass', jest.fn()); comp.highlightLines(1, 1); - const disposeAnnotationSpy = jest.spyOn(comp.editorBuildAnnotations[0], 'dispose'); + const disposeAnnotationSpy = jest.spyOn(comp.buildAnnotations[0], 'dispose'); const disposeWidgetSpy = jest.spyOn(comp.lineWidgets[0], 'dispose'); const disposeHoverButtonSpy = jest.spyOn(comp.lineDecorationsHoverButton!, 'dispose'); const disposeLineHighlightSpy = jest.spyOn(comp.lineHighlights[0], 'dispose'); From aaf368cffcbfca03d467dcbe41d1f3c887a75fce Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 23:16:05 +0200 Subject: [PATCH 07/23] Reorganize attributes, part 2 --- .../monaco-editor/monaco-diff-editor.component.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index 2b3394372eaf..b0c65cdc2d73 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -17,13 +17,20 @@ export type MonacoEditorDiffText = { original: string; modified: string }; export class MonacoDiffEditorComponent implements OnInit, OnDestroy { private _editor: monaco.editor.IStandaloneDiffEditor; monacoDiffEditorContainerElement: HTMLElement; - themeSubscription?: Subscription; - listeners: Disposable[] = []; - resizeObserver?: ResizeObserver; allowSplitView = input(); onReadyForDisplayChange = output(); + /* + * Subscriptions and listeners that need to be disposed of when this component is destroyed. + */ + themeSubscription?: Subscription; + listeners: Disposable[] = []; + resizeObserver?: ResizeObserver; + + /* + * Injected services and elements. + */ private readonly themeService = inject(ThemeService); private readonly elementRef = inject(ElementRef); private readonly renderer = inject(Renderer2); From 5380595b37e03867827b2e058909e6c8f0ef140e Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 23:31:23 +0200 Subject: [PATCH 08/23] Fix aeolus tests --- ...custom-aeolus-build-plan.component.spec.ts | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts index 8b59cc5e8c6b..99331f8c23aa 100644 --- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts @@ -8,8 +8,7 @@ import { ProgrammingExercise, ProgrammingLanguage, ProjectType } from 'app/entit import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { Course } from 'app/entities/course.model'; import { ProgrammingExerciseCustomAeolusBuildPlanComponent } from 'app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-aeolus-build-plan.component'; -import { ElementRef, Renderer2 } from '@angular/core'; -import { ThemeService } from 'app/core/theme/theme.service'; +import { runInInjectionContext } from '@angular/core'; import { MockComponent } from 'ng-mocks'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { HelpIconComponent } from 'app/shared/components/help-icon.component'; @@ -18,10 +17,8 @@ import { AeolusService } from 'app/exercises/programming/shared/service/aeolus.s import { PROFILE_AEOLUS } from 'app/app.constants'; import { Observable } from 'rxjs'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { TranslateService } from '@ngx-translate/core'; describe('ProgrammingExercise Aeolus Custom Build Plan', () => { - let mockThemeService: ThemeService; let comp: ProgrammingExerciseCustomAeolusBuildPlanComponent; const course = { id: 123 } as Course; @@ -33,8 +30,7 @@ describe('ProgrammingExercise Aeolus Custom Build Plan', () => { let cleanBuildAction: ScriptAction = new ScriptAction(); let platformAction: PlatformAction = new PlatformAction(); let mockAeolusService: AeolusService; - let renderer2: Renderer2; - let translateService: TranslateService; + let monacoEditorComponent: MonacoEditorComponent; beforeEach(() => { programmingExercise = new ProgrammingExercise(course, undefined); @@ -75,16 +71,16 @@ describe('ProgrammingExercise Aeolus Custom Build Plan', () => { .compileComponents() .then(() => { mockAeolusService = TestBed.inject(AeolusService); - mockThemeService = TestBed.inject(ThemeService); }); const fixture = TestBed.createComponent(ProgrammingExerciseCustomAeolusBuildPlanComponent); - // These are not directly injected into the component, but are needed for the tests. - renderer2 = fixture.debugElement.injector.get(Renderer2); - translateService = fixture.debugElement.injector.get(TranslateService); comp = fixture.componentInstance; comp.programmingExercise = programmingExercise; + + runInInjectionContext(fixture.debugElement.injector, () => { + monacoEditorComponent = new MonacoEditorComponent(); + }); }); afterEach(() => { @@ -123,11 +119,13 @@ describe('ProgrammingExercise Aeolus Custom Build Plan', () => { expect(programmingExercise.buildConfig?.windfile?.actions.length).toBe(size! + 1); }); - it('should accept editor', () => { - const elementRef: ElementRef = new ElementRef(document.createElement('div')); + it('should accept and setup editor', () => { + const setTextStub = jest.spyOn(monacoEditorComponent, 'setText').mockImplementation(); + comp.code = 'void'; expect(comp.editor).toBeUndefined(); - comp.editor = new MonacoEditorComponent(mockThemeService, elementRef, renderer2, translateService); - expect(comp.editor).toBeDefined(); + comp.editor = monacoEditorComponent; + expect(comp.editor).toBe(monacoEditorComponent); + expect(setTextStub).toHaveBeenCalledExactlyOnceWith(comp.code); }); it('should change code of active action', () => { @@ -175,8 +173,7 @@ describe('ProgrammingExercise Aeolus Custom Build Plan', () => { }); it('should set editor text', () => { - const elementRef: ElementRef = new ElementRef(document.createElement('div')); - comp.editor = new MonacoEditorComponent(mockThemeService, elementRef, renderer2, translateService); + comp.editor = monacoEditorComponent; comp.changeActiveAction('gradle'); expect(comp.editor?.getText()).toBe(gradleBuildAction.script); }); From e5fe810db0e245c40176a83e5e3370329ce47298 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 23:35:24 +0200 Subject: [PATCH 09/23] Fix monaco tests --- .../shared/monaco-editor/monaco-editor.component.ts | 1 - .../monaco-editor/monaco-editor.component.spec.ts | 13 +++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index d75c443e72b8..6762251082ad 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -48,7 +48,6 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { /* * Inputs and outputs. */ - textChangedEmitDelay = input(); shrinkToFit = input(true); stickyScroll = input(false); diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts index 404d0095df04..4dc8064bfcb0 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts @@ -1,6 +1,5 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { ArtemisTestModule } from '../../../test.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; @@ -23,9 +22,7 @@ describe('MonacoEditorComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], - declarations: [MonacoEditorComponent], - providers: [], + imports: [ArtemisTestModule, MonacoEditorComponent], }) .compileComponents() .then(() => { @@ -59,7 +56,7 @@ describe('MonacoEditorComponent', () => { it('should only send a notification once per delay interval', fakeAsync(() => { const delay = 1000; const valueCallbackStub = jest.fn(); - comp.textChangedEmitDelay = delay; + fixture.componentRef.setInput('textChangedEmitDelay', delay); fixture.detectChanges(); comp.textChanged.subscribe(valueCallbackStub); comp.setText('too early'); @@ -70,10 +67,10 @@ describe('MonacoEditorComponent', () => { })); it('should be set to readOnly depending on the input', () => { - comp.readOnly = true; + fixture.componentRef.setInput('readOnly', true); fixture.detectChanges(); expect(comp.isReadOnly()).toBeTrue(); - comp.readOnly = false; + fixture.componentRef.setInput('readOnly', false); fixture.detectChanges(); expect(comp.isReadOnly()).toBeFalse(); }); @@ -205,7 +202,7 @@ describe('MonacoEditorComponent', () => { }); it('should not allow editing in readonly mode', () => { - comp.readOnly = true; + fixture.componentRef.setInput('readOnly', true); fixture.detectChanges(); comp.setText(singleLineText); comp.triggerKeySequence('some ignored input'); From b3a6e45579be5b7c6fc9488d25b6c7266b631712 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Sun, 22 Sep 2024 23:47:26 +0200 Subject: [PATCH 10/23] Fix build error --- ...ercise-custom-build-plan.component.spec.ts | 35 ++++++++++--------- .../monaco-editor.component.spec.ts | 2 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts index b2ec21d46f82..5d0df867b58a 100644 --- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts @@ -1,4 +1,4 @@ -import { TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { BuildAction, PlatformAction, ScriptAction } from 'app/entities/programming/build.action'; import { DockerConfiguration } from 'app/entities/programming/docker.configuration'; import { WindFile } from 'app/entities/programming/wind.file'; @@ -7,8 +7,7 @@ import { ArtemisTestModule } from '../../test.module'; import { ProgrammingExercise, ProgrammingLanguage, ProjectType } from 'app/entities/programming/programming-exercise.model'; import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { Course } from 'app/entities/course.model'; -import { ElementRef, Renderer2 } from '@angular/core'; -import { ThemeService } from 'app/core/theme/theme.service'; +import { Injector, Renderer2, runInInjectionContext } from '@angular/core'; import { MockComponent } from 'ng-mocks'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { HelpIconComponent } from 'app/shared/components/help-icon.component'; @@ -18,10 +17,9 @@ import { ProgrammingExerciseCustomBuildPlanComponent } from 'app/exercises/progr import { PROFILE_LOCALCI } from 'app/app.constants'; import { Observable } from 'rxjs'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; -import { TranslateService } from '@ngx-translate/core'; describe('ProgrammingExercise Custom Build Plan', () => { - let mockThemeService: ThemeService; + let fixture: ComponentFixture; let comp: ProgrammingExerciseCustomBuildPlanComponent; const course = { id: 123 } as Course; @@ -33,8 +31,17 @@ describe('ProgrammingExercise Custom Build Plan', () => { let cleanBuildAction: ScriptAction = new ScriptAction(); let platformAction: PlatformAction = new PlatformAction(); let mockAeolusService: AeolusService; - let renderer2: Renderer2; - let translateService: TranslateService; + + const createMonacoEditorComponent = (injector: Injector): MonacoEditorComponent => { + let editor: MonacoEditorComponent | undefined = undefined; + runInInjectionContext(injector, () => { + editor = new MonacoEditorComponent(); + }); + if (!editor) { + throw new Error('Failed to create MonacoEditorComponent'); + } + return editor; + }; beforeEach(() => { programmingExercise = new ProgrammingExercise(course, undefined); @@ -68,14 +75,10 @@ describe('ProgrammingExercise Custom Build Plan', () => { .compileComponents() .then(() => { mockAeolusService = TestBed.inject(AeolusService); - mockThemeService = TestBed.inject(ThemeService); }); - const fixture = TestBed.createComponent(ProgrammingExerciseCustomBuildPlanComponent); + fixture = TestBed.createComponent(ProgrammingExerciseCustomBuildPlanComponent); comp = fixture.componentInstance; - // These are not directly injected into the component, but are needed for the tests. - renderer2 = fixture.debugElement.injector.get(Renderer2); - translateService = fixture.debugElement.injector.get(TranslateService); comp.programmingExercise = programmingExercise; }); @@ -92,9 +95,8 @@ describe('ProgrammingExercise Custom Build Plan', () => { }); it('should accept editor', () => { - const elementRef: ElementRef = new ElementRef(document.createElement('div')); expect(comp.editor).toBeUndefined(); - comp.editor = new MonacoEditorComponent(mockThemeService, elementRef, renderer2, translateService); + comp.editor = createMonacoEditorComponent(fixture.debugElement.injector); expect(comp.editor).toBeDefined(); }); @@ -224,15 +226,14 @@ describe('ProgrammingExercise Custom Build Plan', () => { it('should accept editor for existing exercise', () => { comp.programmingExercise.id = 1; - const elementRef: ElementRef = new ElementRef(document.createElement('div')); comp.programmingExercise.buildConfig!.buildScript = 'buildscript'; - const editor = new MonacoEditorComponent(mockThemeService, elementRef, renderer2, translateService); + const editor = createMonacoEditorComponent(fixture.debugElement.injector); expect(comp.editor).toBeUndefined(); comp.editor = editor; expect(comp.code).toBe('buildscript'); expect(comp.editor).toBeDefined(); comp.programmingExercise.buildConfig!.buildScript = undefined; - comp.editor = new MonacoEditorComponent(mockThemeService, elementRef, renderer2, translateService); + comp.editor = createMonacoEditorComponent(fixture.debugElement.injector); expect(comp.code).toBe(''); }); diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts index 4dc8064bfcb0..cddb2c7c32df 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts @@ -91,7 +91,7 @@ describe('MonacoEditorComponent', () => { const themeSubject = new BehaviorSubject(Theme.LIGHT); const subscribeStub = jest.spyOn(mockThemeService, 'getCurrentThemeObservable').mockReturnValue(themeSubject.asObservable()); fixture.detectChanges(); - const unsubscribeStub = jest.spyOn(comp.themeSubscription!, 'unsubscribe').mockImplementation(); + const unsubscribeStub = jest.spyOn(comp['themeSubscription']!, 'unsubscribe').mockImplementation(); comp.ngOnDestroy(); expect(subscribeStub).toHaveBeenCalledOnce(); expect(unsubscribeStub).toHaveBeenCalledOnce(); From 5c04869d1bdb3fb6a3215a321dea6f1dfe480c58 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Mon, 23 Sep 2024 00:06:58 +0200 Subject: [PATCH 11/23] Delete MonacoEditorModule --- .../app/shared/monaco-editor/monaco-editor.module.ts | 7 ------- .../code-editor/code-editor-monaco.component.spec.ts | 10 ++-------- .../git-diff-report/git-diff-file.component.spec.ts | 4 ++-- .../monaco-editor/monaco-diff-editor.component.spec.ts | 5 +---- .../monaco-editor-action-quiz.integration.spec.ts | 5 +---- .../monaco-editor-action.integration.spec.ts | 5 +---- ...aco-editor-communication-action.integration.spec.ts | 3 +-- ...aco-editor-grading-instructions.integration.spec.ts | 4 +--- .../code-editor-container.integration.spec.ts | 4 ++-- 9 files changed, 11 insertions(+), 36 deletions(-) delete mode 100644 src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts deleted file mode 100644 index cfa64e4e21b1..000000000000 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { NgModule } from '@angular/core'; - -@NgModule({ - declarations: [], - exports: [], -}) -export class MonacoEditorModule {} diff --git a/src/test/javascript/spec/component/code-editor/code-editor-monaco.component.spec.ts b/src/test/javascript/spec/component/code-editor/code-editor-monaco.component.spec.ts index e6a35ac15468..df3b885c4a93 100644 --- a/src/test/javascript/spec/component/code-editor/code-editor-monaco.component.spec.ts +++ b/src/test/javascript/spec/component/code-editor/code-editor-monaco.component.spec.ts @@ -4,7 +4,6 @@ import { ArtemisTestModule } from '../../test.module'; import { Annotation, CodeEditorMonacoComponent } from 'app/exercises/programming/shared/code-editor/monaco/code-editor-monaco.component'; import { MockComponent } from 'ng-mocks'; import { CodeEditorTutorAssessmentInlineFeedbackComponent } from 'app/exercises/programming/assess/code-editor-tutor-assessment-inline-feedback.component'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; import { CodeEditorFileService } from 'app/exercises/programming/shared/code-editor/service/code-editor-file.service'; @@ -48,13 +47,8 @@ describe('CodeEditorMonacoComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], - declarations: [ - CodeEditorMonacoComponent, - MockComponent(CodeEditorTutorAssessmentInlineFeedbackComponent), - MockComponent(CodeEditorHeaderComponent), - MonacoEditorComponent, - ], + imports: [ArtemisTestModule, MonacoEditorComponent], + declarations: [CodeEditorMonacoComponent, MockComponent(CodeEditorTutorAssessmentInlineFeedbackComponent), MockComponent(CodeEditorHeaderComponent)], providers: [ CodeEditorFileService, { provide: CodeEditorRepositoryFileService, useClass: MockCodeEditorRepositoryFileService }, diff --git a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts index dbef6bd7eb72..47d3e75710b4 100644 --- a/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts +++ b/src/test/javascript/spec/component/hestia/git-diff-report/git-diff-file.component.spec.ts @@ -1,8 +1,8 @@ import { ArtemisTestModule } from '../../../test.module'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { GitDiffFileComponent } from 'app/exercises/programming/hestia/git-diff-report/git-diff-file.component'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; +import { MonacoDiffEditorComponent } from '../../../../../../main/webapp/app/shared/monaco-editor/monaco-diff-editor.component'; function getDiffEntryWithPaths(previousFilePath?: string, filePath?: string) { return { @@ -17,7 +17,7 @@ describe('GitDiffFileComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], + imports: [ArtemisTestModule, MonacoDiffEditorComponent], declarations: [GitDiffFileComponent], providers: [], }).compileComponents(); diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts index 350a6ed235d3..c9e7e433b8c1 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts @@ -1,7 +1,6 @@ import { Theme, ThemeService } from 'app/core/theme/theme.service'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTestModule } from '../../../test.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component'; import { BehaviorSubject } from 'rxjs'; @@ -13,9 +12,7 @@ describe('MonacoDiffEditorComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], - declarations: [MonacoDiffEditorComponent], - providers: [], + imports: [ArtemisTestModule, MonacoDiffEditorComponent], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action-quiz.integration.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action-quiz.integration.spec.ts index 06d99f3ae996..a759b1da24fc 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action-quiz.integration.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action-quiz.integration.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { ArtemisTestModule } from '../../../test.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; import { InsertShortAnswerOptionAction } from 'app/shared/monaco-editor/model/actions/quiz/insert-short-answer-option.action'; import { InsertShortAnswerSpotAction } from 'app/shared/monaco-editor/model/actions/quiz/insert-short-answer-spot.action'; @@ -20,9 +19,7 @@ describe('MonacoEditorActionQuizIntegration', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], - declarations: [MonacoEditorComponent], - providers: [], + imports: [ArtemisTestModule, MonacoEditorComponent], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts index 730f84f110f1..f4fe90f07a2c 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts @@ -1,7 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { ArtemisTestModule } from '../../../test.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; import { BoldAction } from 'app/shared/monaco-editor/model/actions/bold.action'; import { TextEditorAction } from 'app/shared/monaco-editor/model/actions/text-editor-action.model'; @@ -29,9 +28,7 @@ describe('MonacoEditorActionIntegration', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], - declarations: [MonacoEditorComponent], - providers: [], + imports: [ArtemisTestModule, MonacoEditorComponent], }) .compileComponents() .then(() => { diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-communication-action.integration.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-communication-action.integration.spec.ts index 9fa5c2b391c5..862de1bdc8a7 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-communication-action.integration.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-communication-action.integration.spec.ts @@ -43,6 +43,7 @@ describe('MonacoEditorCommunicationActionIntegration', () => { beforeEach(() => { return TestBed.configureTestingModule({ + imports: [MonacoEditorComponent], providers: [ { provide: MetisService, useClass: MockMetisService }, { provide: TranslateService, useClass: MockTranslateService }, @@ -51,7 +52,6 @@ describe('MonacoEditorCommunicationActionIntegration', () => { MockProvider(CourseManagementService), MockProvider(ChannelService), ], - declarations: [MonacoEditorComponent], }) .compileComponents() .then(() => { @@ -60,7 +60,6 @@ describe('MonacoEditorCommunicationActionIntegration', () => { }); fixture = TestBed.createComponent(MonacoEditorComponent); comp = fixture.componentInstance; - // debugElement = fixture.debugElement; metisService = TestBed.inject(MetisService); courseManagementService = TestBed.inject(CourseManagementService); lectureService = TestBed.inject(LectureService); diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts index 8d608dc314b6..ab99555cc7d6 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-grading-instructions.integration.spec.ts @@ -1,6 +1,5 @@ import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { ArtemisTestModule } from '../../../test.module'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { GradingInstructionAction } from 'app/shared/monaco-editor/model/actions/grading-criteria/grading-instruction.action'; @@ -17,8 +16,7 @@ describe('MonacoEditorActionGradingInstructionsIntegration', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule], - declarations: [MonacoEditorComponent], + imports: [ArtemisTestModule, MonacoEditorComponent], providers: [], }) .compileComponents() diff --git a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts index d8f9e0bb8126..866a8ba7d7af 100644 --- a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts +++ b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts @@ -73,8 +73,8 @@ import { TreeviewItemComponent } from 'app/exercises/programming/shared/code-edi import { CodeEditorHeaderComponent } from 'app/exercises/programming/shared/code-editor/header/code-editor-header.component'; import { AlertService } from 'app/core/util/alert.service'; import { MockResizeObserver } from '../../helpers/mocks/service/mock-resize-observer'; -import { MonacoEditorModule } from 'app/shared/monaco-editor/monaco-editor.module'; import { CodeEditorMonacoComponent } from 'app/exercises/programming/shared/code-editor/monaco/code-editor-monaco.component'; +import { MonacoEditorComponent } from '../../../../../main/webapp/app/shared/monaco-editor/monaco-editor.component'; describe('CodeEditorContainerIntegration', () => { let container: CodeEditorContainerComponent; @@ -100,7 +100,7 @@ describe('CodeEditorContainerIntegration', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MonacoEditorModule, MockDirective(NgbDropdown), MockModule(NgbTooltipModule)], + imports: [ArtemisTestModule, MonacoEditorComponent, MockDirective(NgbDropdown), MockModule(NgbTooltipModule)], declarations: [ CodeEditorContainerComponent, MockComponent(CodeEditorGridComponent), From da4ea63eb68e04fe90ead3fac287d9a09b0fc6f5 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 25 Sep 2024 11:44:24 +0200 Subject: [PATCH 12/23] WIP themes --- .../model/themes/monaco-editor-theme.model.ts | 103 ++++++++++++++++++ .../monaco-editor/monaco-editor.service.ts | 2 + 2 files changed, 105 insertions(+) create mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts new file mode 100644 index 000000000000..e1ef44a2e972 --- /dev/null +++ b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts @@ -0,0 +1,103 @@ +import * as monaco from 'monaco-editor'; + +export class MonacoEditorTheme { + constructor( + private readonly name: string, + private readonly themeDefinition: MonacoEditorThemeDefinition, + ) {} + + register(): void { + const colors = this.themeDefinition.colors; + const rawColors: Record = { + 'editor.lineHighlightBackground': colors.activeLineHighlightBackground, + 'editor.lineHighlightBorder': colors.activeLineHighlightBorder, + 'diffEditor.insertedTextBackground': colors.insertedTextBackground, + 'diffEditor.removedTextBackground': colors.removedTextBackground, + 'diffEditor.insertedLineBackground': colors.insertedLineBackground, + 'diffEditor.removedLineBackground': colors.removedLineBackground, + 'diffEditor.diagonalFill': colors.diagonalFill, + 'diffEditorGutter.insertedLineBackground': colors.gutterInsertedLineBackground, + 'diffEditorGutter.removedLineBackground': colors.gutterRemovedLineBackground, + }; + + // Define a second object that contains only defined colors + const definedColors: Record = {}; + for (const key in rawColors) { + if (rawColors[key]) { + definedColors[key] = rawColors[key]!; + } + } + + /*monaco.editor.defineTheme(this.name, { + base: this.themeDefinition.base, + inherit: this.themeDefinition.inherit, + rules: [], + colors: definedColors, + });*/ + } + + apply(): void { + monaco.editor.setTheme(this.name); + } +} + +export const CUSTOM_DARK = new MonacoEditorTheme('custom-dark', { + base: 'vs-dark', + inherit: true, + colors: { + insertedTextBackground: '#2ea04340', + removedTextBackground: '#f8514940', + insertedLineBackground: '#3fb95010', + removedLineBackground: '#f8514910', + gutterInsertedLineBackground: '#2ea0437d', + gutterRemovedLineBackground: '#f851497d', + diagonalFill: '#00000000', + activeLineHighlightBorder: '#00000000', + activeLineHighlightBackground: '#282a2e', + }, +}); + +export interface MonacoEditorThemeDefinition { + base: 'vs' | 'vs-dark'; + inherit: boolean; + colors: MonacoEditorThemeColors; +} + +interface MonacoEditorThemeColors { + /** + * Background color for the text inserted on a line in the diff editor. Note that this will overlap with the `insertedLineBackground`. + */ + insertedTextBackground?: string; + /** + * Background color for the text removed on a line in the diff editor. Note that this will overlap with the `removedLineBackground`. + */ + removedTextBackground?: string; + /** + * Background color for an inserted line in the diff editor. + */ + insertedLineBackground?: string; + /** + * Background color for a removed line in the diff editor. + */ + removedLineBackground?: string; + /** + * Background color for the gutter in the diff editor on an inserted line. + */ + gutterInsertedLineBackground?: string; + /** + * Background color for the gutter in the diff editor on a removed line. + */ + gutterRemovedLineBackground?: string; + /** + * Color of the diagonal fill (zigzag pattern) in the diff editor. + */ + diagonalFill?: string; + /** + * Color of the border around the currently active line in the Monaco editor. + */ + activeLineHighlightBorder?: string; + /** + * Background color for the currently active line in the Monaco editor. + */ + activeLineHighlightBackground?: string; +} diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts index ab24f4999b04..c9337803d317 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import * as monaco from 'monaco-editor'; import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; +import { CUSTOM_DARK } from 'app/shared/monaco-editor/model/themes/monaco-editor-theme.model'; @Injectable({ providedIn: 'root' }) export class MonacoEditorService { @@ -8,6 +9,7 @@ export class MonacoEditorService { monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID }); monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG); monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); + CUSTOM_DARK.register(); } foo() { From d378c63f1b34dcf2803f656cb991503a860a8909 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Tue, 1 Oct 2024 21:33:08 +0200 Subject: [PATCH 13/23] Revert "WIP themes" This reverts commit da4ea63eb68e04fe90ead3fac287d9a09b0fc6f5. --- .../model/themes/monaco-editor-theme.model.ts | 103 ------------------ .../monaco-editor/monaco-editor.service.ts | 2 - 2 files changed, 105 deletions(-) delete mode 100644 src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts diff --git a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts b/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts deleted file mode 100644 index e1ef44a2e972..000000000000 --- a/src/main/webapp/app/shared/monaco-editor/model/themes/monaco-editor-theme.model.ts +++ /dev/null @@ -1,103 +0,0 @@ -import * as monaco from 'monaco-editor'; - -export class MonacoEditorTheme { - constructor( - private readonly name: string, - private readonly themeDefinition: MonacoEditorThemeDefinition, - ) {} - - register(): void { - const colors = this.themeDefinition.colors; - const rawColors: Record = { - 'editor.lineHighlightBackground': colors.activeLineHighlightBackground, - 'editor.lineHighlightBorder': colors.activeLineHighlightBorder, - 'diffEditor.insertedTextBackground': colors.insertedTextBackground, - 'diffEditor.removedTextBackground': colors.removedTextBackground, - 'diffEditor.insertedLineBackground': colors.insertedLineBackground, - 'diffEditor.removedLineBackground': colors.removedLineBackground, - 'diffEditor.diagonalFill': colors.diagonalFill, - 'diffEditorGutter.insertedLineBackground': colors.gutterInsertedLineBackground, - 'diffEditorGutter.removedLineBackground': colors.gutterRemovedLineBackground, - }; - - // Define a second object that contains only defined colors - const definedColors: Record = {}; - for (const key in rawColors) { - if (rawColors[key]) { - definedColors[key] = rawColors[key]!; - } - } - - /*monaco.editor.defineTheme(this.name, { - base: this.themeDefinition.base, - inherit: this.themeDefinition.inherit, - rules: [], - colors: definedColors, - });*/ - } - - apply(): void { - monaco.editor.setTheme(this.name); - } -} - -export const CUSTOM_DARK = new MonacoEditorTheme('custom-dark', { - base: 'vs-dark', - inherit: true, - colors: { - insertedTextBackground: '#2ea04340', - removedTextBackground: '#f8514940', - insertedLineBackground: '#3fb95010', - removedLineBackground: '#f8514910', - gutterInsertedLineBackground: '#2ea0437d', - gutterRemovedLineBackground: '#f851497d', - diagonalFill: '#00000000', - activeLineHighlightBorder: '#00000000', - activeLineHighlightBackground: '#282a2e', - }, -}); - -export interface MonacoEditorThemeDefinition { - base: 'vs' | 'vs-dark'; - inherit: boolean; - colors: MonacoEditorThemeColors; -} - -interface MonacoEditorThemeColors { - /** - * Background color for the text inserted on a line in the diff editor. Note that this will overlap with the `insertedLineBackground`. - */ - insertedTextBackground?: string; - /** - * Background color for the text removed on a line in the diff editor. Note that this will overlap with the `removedLineBackground`. - */ - removedTextBackground?: string; - /** - * Background color for an inserted line in the diff editor. - */ - insertedLineBackground?: string; - /** - * Background color for a removed line in the diff editor. - */ - removedLineBackground?: string; - /** - * Background color for the gutter in the diff editor on an inserted line. - */ - gutterInsertedLineBackground?: string; - /** - * Background color for the gutter in the diff editor on a removed line. - */ - gutterRemovedLineBackground?: string; - /** - * Color of the diagonal fill (zigzag pattern) in the diff editor. - */ - diagonalFill?: string; - /** - * Color of the border around the currently active line in the Monaco editor. - */ - activeLineHighlightBorder?: string; - /** - * Background color for the currently active line in the Monaco editor. - */ - activeLineHighlightBackground?: string; -} diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts index c9337803d317..ab24f4999b04 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import * as monaco from 'monaco-editor'; import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; -import { CUSTOM_DARK } from 'app/shared/monaco-editor/model/themes/monaco-editor-theme.model'; @Injectable({ providedIn: 'root' }) export class MonacoEditorService { @@ -9,7 +8,6 @@ export class MonacoEditorService { monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID }); monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG); monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); - CUSTOM_DARK.register(); } foo() { From 2ec48ba114270bc8f8c65e69d9dc1df97e34856a Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Tue, 1 Oct 2024 21:58:47 +0200 Subject: [PATCH 14/23] Move theme apply to service --- .../monaco-diff-editor.component.ts | 3 +- .../monaco-editor/monaco-editor.component.ts | 3 +- .../monaco-editor/monaco-editor.service.ts | 5 +-- .../monaco-editor.service.spec.ts | 35 +++++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index b0c65cdc2d73..256178a0b443 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -66,7 +66,6 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { this.renderer.appendChild(this.elementRef.nativeElement, this.monacoDiffEditorContainerElement); this.setupDiffListener(); this.setupContentHeightListeners(); - this.monacoEditorService.foo(); effect(() => { this._editor.updateOptions({ @@ -153,7 +152,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { * @param artemisTheme The active Artemis theme. */ changeTheme(artemisTheme: Theme): void { - monaco.editor.setTheme(artemisTheme === Theme.DARK ? 'vs-dark' : 'vs-light'); + this.monacoEditorService.applyTheme(artemisTheme); } /** diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index 6762251082ad..27bd911f0e9e 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -97,7 +97,6 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { this._editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF); this.textEditorAdapter = new MonacoTextEditorAdapter(this._editor); this.renderer.appendChild(this.elementRef.nativeElement, this.monacoEditorContainerElement); - this.monacoEditorService.foo(); effect(() => { // TODO: The CSS class below allows the editor to shrink in the CodeEditorContainerComponent. We should eventually remove this class and handle the editor size differently in the code editor grid. @@ -298,7 +297,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { } changeTheme(artemisTheme: Theme): void { - monaco.editor.setTheme(artemisTheme === Theme.DARK ? 'vs-dark' : 'vs-light'); + this.monacoEditorService.applyTheme(artemisTheme); } layout(): void { diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts index ab24f4999b04..a877b0a31cc5 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import * as monaco from 'monaco-editor'; import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; +import { Theme } from 'app/core/theme/theme.service'; @Injectable({ providedIn: 'root' }) export class MonacoEditorService { @@ -10,7 +11,7 @@ export class MonacoEditorService { monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); } - foo() { - // TODO: remove + applyTheme(artemisTheme: Theme): void { + monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? 'vs' : 'vs-dark'); } } diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts new file mode 100644 index 000000000000..b3141863746c --- /dev/null +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import * as monaco from 'monaco-editor'; +import { Theme } from 'app/core/theme/theme.service'; +import { MonacoEditorService } from '../../../../../../main/webapp/app/shared/monaco-editor/monaco-editor.service'; +import { ArtemisTestModule } from '../../../test.module'; +import { CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; + +describe('MonacoEditorService', () => { + let service: MonacoEditorService; + let registerLanguageSpy: jest.SpyInstance; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ArtemisTestModule], + }); + registerLanguageSpy = jest.spyOn(monaco.languages, 'register'); + service = TestBed.runInInjectionContext(() => new MonacoEditorService()); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should register the custom markdown language', () => { + const customMarkdownLanguage = monaco.languages.getLanguages().find((l) => l.id === CUSTOM_MARKDOWN_LANGUAGE_ID); + expect(customMarkdownLanguage).toBeDefined(); + expect(registerLanguageSpy).toHaveBeenCalledExactlyOnceWith({ id: customMarkdownLanguage!.id }); + }); + + it.each([Theme.LIGHT, Theme.DARK])('should apply the correct theme for $identifier mode', (theme) => { + const setThemeSpy = jest.spyOn(monaco.editor, 'setTheme'); + service.applyTheme(theme); + expect(setThemeSpy).toHaveBeenCalledExactlyOnceWith(theme === Theme.LIGHT ? 'vs' : 'vs-dark'); + }); +}); From b44d1c0bd7427a86c62510a65b3f3cabaa7287f6 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Tue, 1 Oct 2024 22:09:50 +0200 Subject: [PATCH 15/23] Replace runInInjectionContext --- ...custom-aeolus-build-plan.component.spec.ts | 6 +----- ...ercise-custom-build-plan.component.spec.ts | 19 ++++--------------- .../monaco-editor.service.spec.ts | 2 +- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts index 99331f8c23aa..005fa8fdd41a 100644 --- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-aeolus-build-plan.component.spec.ts @@ -8,7 +8,6 @@ import { ProgrammingExercise, ProgrammingLanguage, ProjectType } from 'app/entit import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { Course } from 'app/entities/course.model'; import { ProgrammingExerciseCustomAeolusBuildPlanComponent } from 'app/exercises/programming/manage/update/update-components/custom-build-plans/programming-exercise-custom-aeolus-build-plan.component'; -import { runInInjectionContext } from '@angular/core'; import { MockComponent } from 'ng-mocks'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { HelpIconComponent } from 'app/shared/components/help-icon.component'; @@ -77,10 +76,7 @@ describe('ProgrammingExercise Aeolus Custom Build Plan', () => { comp = fixture.componentInstance; comp.programmingExercise = programmingExercise; - - runInInjectionContext(fixture.debugElement.injector, () => { - monacoEditorComponent = new MonacoEditorComponent(); - }); + monacoEditorComponent = TestBed.createComponent(MonacoEditorComponent).componentInstance; }); afterEach(() => { diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts index 5d0df867b58a..7637950ee528 100644 --- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-custom-build-plan.component.spec.ts @@ -7,7 +7,7 @@ import { ArtemisTestModule } from '../../test.module'; import { ProgrammingExercise, ProgrammingLanguage, ProjectType } from 'app/entities/programming/programming-exercise.model'; import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { Course } from 'app/entities/course.model'; -import { Injector, Renderer2, runInInjectionContext } from '@angular/core'; +import { Renderer2 } from '@angular/core'; import { MockComponent } from 'ng-mocks'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { HelpIconComponent } from 'app/shared/components/help-icon.component'; @@ -32,17 +32,6 @@ describe('ProgrammingExercise Custom Build Plan', () => { let platformAction: PlatformAction = new PlatformAction(); let mockAeolusService: AeolusService; - const createMonacoEditorComponent = (injector: Injector): MonacoEditorComponent => { - let editor: MonacoEditorComponent | undefined = undefined; - runInInjectionContext(injector, () => { - editor = new MonacoEditorComponent(); - }); - if (!editor) { - throw new Error('Failed to create MonacoEditorComponent'); - } - return editor; - }; - beforeEach(() => { programmingExercise = new ProgrammingExercise(course, undefined); programmingExercise.customizeBuildPlanWithAeolus = true; @@ -96,7 +85,7 @@ describe('ProgrammingExercise Custom Build Plan', () => { it('should accept editor', () => { expect(comp.editor).toBeUndefined(); - comp.editor = createMonacoEditorComponent(fixture.debugElement.injector); + comp.editor = TestBed.createComponent(MonacoEditorComponent).componentInstance; expect(comp.editor).toBeDefined(); }); @@ -227,13 +216,13 @@ describe('ProgrammingExercise Custom Build Plan', () => { it('should accept editor for existing exercise', () => { comp.programmingExercise.id = 1; comp.programmingExercise.buildConfig!.buildScript = 'buildscript'; - const editor = createMonacoEditorComponent(fixture.debugElement.injector); + const editor = TestBed.createComponent(MonacoEditorComponent).componentInstance; expect(comp.editor).toBeUndefined(); comp.editor = editor; expect(comp.code).toBe('buildscript'); expect(comp.editor).toBeDefined(); comp.programmingExercise.buildConfig!.buildScript = undefined; - comp.editor = createMonacoEditorComponent(fixture.debugElement.injector); + comp.editor = TestBed.createComponent(MonacoEditorComponent).componentInstance; expect(comp.code).toBe(''); }); diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts index b3141863746c..aeab3da5db18 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts @@ -14,7 +14,7 @@ describe('MonacoEditorService', () => { imports: [ArtemisTestModule], }); registerLanguageSpy = jest.spyOn(monaco.languages, 'register'); - service = TestBed.runInInjectionContext(() => new MonacoEditorService()); + service = TestBed.inject(MonacoEditorService); }); afterEach(() => { From 984cbcf6ab9e1dca600561da673a8f5c57c9a562 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Tue, 1 Oct 2024 22:37:56 +0200 Subject: [PATCH 16/23] Adapt change detection strategy --- .../app/shared/monaco-editor/monaco-diff-editor.component.ts | 3 ++- .../webapp/app/shared/monaco-editor/monaco-editor.component.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index 256178a0b443..2e55961d1c06 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; import * as monaco from 'monaco-editor'; @@ -12,6 +12,7 @@ export type MonacoEditorDiffText = { original: string; modified: string }; template: '', standalone: true, styleUrls: ['monaco-diff-editor.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MonacoDiffEditorComponent implements OnInit, OnDestroy { diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index 27bd911f0e9e..a3d84deb9892 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; import * as monaco from 'monaco-editor'; import { Subscription } from 'rxjs'; import { Theme, ThemeService } from 'app/core/theme/theme.service'; @@ -21,6 +21,7 @@ export const MAX_TAB_SIZE = 8; template: '', standalone: true, styleUrls: ['monaco-editor.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) export class MonacoEditorComponent implements OnInit, OnDestroy { From ab1eb8f521464aef5e5403e70011303320c150a5 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 2 Oct 2024 09:56:16 +0200 Subject: [PATCH 17/23] Use signals for theme adjustment --- .../monaco-diff-editor.component.ts | 36 +--------- .../monaco-editor/monaco-editor.component.ts | 28 +------- .../monaco-editor/monaco-editor.service.ts | 70 +++++++++++++++++-- 3 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index 2e55961d1c06..bf770f7c7bf0 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -1,8 +1,6 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; -import { Theme, ThemeService } from 'app/core/theme/theme.service'; import * as monaco from 'monaco-editor'; -import { Subscription } from 'rxjs'; import { Disposable } from 'app/shared/monaco-editor/model/actions/monaco-editor.util'; import { MonacoEditorService } from './monaco-editor.service'; @@ -25,14 +23,12 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { /* * Subscriptions and listeners that need to be disposed of when this component is destroyed. */ - themeSubscription?: Subscription; listeners: Disposable[] = []; resizeObserver?: ResizeObserver; /* * Injected services and elements. */ - private readonly themeService = inject(ThemeService); private readonly elementRef = inject(ElementRef); private readonly renderer = inject(Renderer2); private readonly monacoEditorService = inject(MonacoEditorService); @@ -44,26 +40,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { * of this component are called. */ this.monacoDiffEditorContainerElement = this.renderer.createElement('div'); - this._editor = monaco.editor.createDiffEditor(this.monacoDiffEditorContainerElement, { - glyphMargin: true, - minimap: { enabled: false }, - readOnly: true, - renderSideBySide: true, - scrollBeyondLastLine: false, - stickyScroll: { - enabled: false, - }, - renderOverviewRuler: false, - scrollbar: { - vertical: 'hidden', - handleMouseWheel: true, - alwaysConsumeMouseWheel: false, - }, - hideUnchangedRegions: { - enabled: true, - }, - fontSize: 12, - }); + this._editor = this.monacoEditorService.createStandaloneDiffEditor(this.monacoDiffEditorContainerElement); this.renderer.appendChild(this.elementRef.nativeElement, this.monacoDiffEditorContainerElement); this.setupDiffListener(); this.setupContentHeightListeners(); @@ -80,11 +57,9 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { this.layout(); }); this.resizeObserver.observe(this.monacoDiffEditorContainerElement); - this.themeSubscription = this.themeService.getCurrentThemeObservable().subscribe((theme) => this.changeTheme(theme)); } ngOnDestroy(): void { - this.themeSubscription?.unsubscribe(); this.resizeObserver?.disconnect(); this.listeners.forEach((listener) => { listener.dispose(); @@ -147,15 +122,6 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { this._editor.layout({ width, height }); } - /** - * Sets the theme of all Monaco editors according to the Artemis theme. - * As of now, it is not possible to have two editors with different themes. - * @param artemisTheme The active Artemis theme. - */ - changeTheme(artemisTheme: Theme): void { - this.monacoEditorService.applyTheme(artemisTheme); - } - /** * Updates the files displayed in this editor. When this happens, {@link onReadyForDisplayChange} will signal that the editor is not * ready to display the diff (as it must be computed first). This will later be change by the appropriate listener. diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts index a3d84deb9892..b9138398fcee 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts @@ -1,7 +1,5 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit, Renderer2, ViewEncapsulation, effect, inject, input, output } from '@angular/core'; import * as monaco from 'monaco-editor'; -import { Subscription } from 'rxjs'; -import { Theme, ThemeService } from 'app/core/theme/theme.service'; import { MonacoEditorLineWidget } from 'app/shared/monaco-editor/model/monaco-editor-inline-widget.model'; import { MonacoEditorBuildAnnotation, MonacoEditorBuildAnnotationType } from 'app/shared/monaco-editor/model/monaco-editor-build-annotation.model'; import { MonacoEditorLineHighlight } from 'app/shared/monaco-editor/model/monaco-editor-line-highlight.model'; @@ -65,12 +63,10 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { private textChangedListener?: Disposable; private blurEditorWidgetListener?: Disposable; private textChangedEmitTimeout?: NodeJS.Timeout; - private themeSubscription?: Subscription; /* * Injected services and elements. */ - private readonly themeService = inject(ThemeService); private readonly renderer = inject(Renderer2); private readonly translateService = inject(TranslateService); private readonly elementRef = inject(ElementRef); @@ -85,17 +81,7 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { this.monacoEditorContainerElement = this.renderer.createElement('div'); this.renderer.addClass(this.monacoEditorContainerElement, 'monaco-editor-container'); this.renderer.addClass(this.monacoEditorContainerElement, MonacoEditorComponent.SHRINK_TO_FIT_CLASS); - this._editor = monaco.editor.create(this.monacoEditorContainerElement, { - value: '', - glyphMargin: true, - minimap: { enabled: false }, - lineNumbersMinChars: 4, - scrollBeyondLastLine: false, - scrollbar: { - alwaysConsumeMouseWheel: false, // Prevents the editor from consuming the mouse wheel event, allowing the parent element to scroll. - }, - }); - this._editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF); + this._editor = this.monacoEditorService.createStandaloneCodeEditor(this.monacoEditorContainerElement); this.textEditorAdapter = new MonacoTextEditorAdapter(this._editor); this.renderer.appendChild(this.elementRef.nativeElement, this.monacoEditorContainerElement); @@ -111,11 +97,6 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { effect(() => { this._editor.updateOptions({ stickyScroll: { enabled: this.stickyScroll() }, - }); - }); - - effect(() => { - this._editor.updateOptions({ readOnly: this.readOnly(), }); }); @@ -140,14 +121,11 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { this.blurEditorWidgetListener = this._editor.onDidBlurEditorWidget(() => { this.onBlurEditor.emit(); }); - - this.themeSubscription = this.themeService.getCurrentThemeObservable().subscribe((theme) => this.changeTheme(theme)); } ngOnDestroy() { this.reset(); this._editor.dispose(); - this.themeSubscription?.unsubscribe(); this.textChangedListener?.dispose(); this.contentHeightListener?.dispose(); this.blurEditorWidgetListener?.dispose(); @@ -297,10 +275,6 @@ export class MonacoEditorComponent implements OnInit, OnDestroy { this.actions = []; } - changeTheme(artemisTheme: Theme): void { - this.monacoEditorService.applyTheme(artemisTheme); - } - layout(): void { this._editor.layout(); } diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts index a877b0a31cc5..4c1c808662bf 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts @@ -1,17 +1,79 @@ -import { Injectable } from '@angular/core'; +import { Injectable, effect, inject } from '@angular/core'; import * as monaco from 'monaco-editor'; import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; -import { Theme } from 'app/core/theme/theme.service'; +import { Theme, ThemeService } from 'app/core/theme/theme.service'; +import { toSignal } from '@angular/core/rxjs-interop'; @Injectable({ providedIn: 'root' }) export class MonacoEditorService { + static readonly LIGHT_THEME_ID = 'vs'; + static readonly DARK_THEME_ID = 'vs-dark'; + + private readonly themeService: ThemeService = inject(ThemeService); + private readonly currentTheme = toSignal(this.themeService.getCurrentThemeObservable(), { requireSync: true }); + constructor() { monaco.languages.register({ id: CUSTOM_MARKDOWN_LANGUAGE_ID }); monaco.languages.setLanguageConfiguration(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_CONFIG); monaco.languages.setMonarchTokensProvider(CUSTOM_MARKDOWN_LANGUAGE_ID, CUSTOM_MARKDOWN_LANGUAGE); + + effect(() => { + this.applyTheme(this.currentTheme()); + }); + } + + /** + * Applies the given theme to the Monaco editor. + * @param artemisTheme The theme to apply. + * @private + */ + private applyTheme(artemisTheme: Theme): void { + monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? MonacoEditorService.LIGHT_THEME_ID : MonacoEditorService.DARK_THEME_ID); + } + + /** + * Creates a standalone code editor (see {@link MonacoEditorComponent}) with sensible default settings and inserts it into the given DOM element. + * @param domElement The DOM element to insert the editor into. + */ + createStandaloneCodeEditor(domElement: HTMLElement): monaco.editor.IStandaloneCodeEditor { + const editor = monaco.editor.create(domElement, { + value: '', + glyphMargin: true, + minimap: { enabled: false }, + lineNumbersMinChars: 4, + scrollBeyondLastLine: false, + scrollbar: { + alwaysConsumeMouseWheel: false, // Prevents the editor from consuming the mouse wheel event, allowing the parent element to scroll. + }, + }); + editor.getModel()?.setEOL(monaco.editor.EndOfLineSequence.LF); + return editor; } - applyTheme(artemisTheme: Theme): void { - monaco.editor.setTheme(artemisTheme === Theme.LIGHT ? 'vs' : 'vs-dark'); + /** + * Creates a standalone diff editor (see {@link MonacoDiffEditorComponent}) with sensible default settings and inserts it into the given DOM element. + * @param domElement The DOM element to insert the editor into. + */ + createStandaloneDiffEditor(domElement: HTMLElement): monaco.editor.IStandaloneDiffEditor { + return monaco.editor.createDiffEditor(domElement, { + glyphMargin: true, + minimap: { enabled: false }, + readOnly: true, + renderSideBySide: true, + scrollBeyondLastLine: false, + stickyScroll: { + enabled: false, + }, + renderOverviewRuler: false, + scrollbar: { + vertical: 'hidden', + handleMouseWheel: true, + alwaysConsumeMouseWheel: false, + }, + hideUnchangedRegions: { + enabled: true, + }, + fontSize: 12, + }); } } From f3801280791c02534b381bd7b50e489900ddd0e6 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 2 Oct 2024 09:59:19 +0200 Subject: [PATCH 18/23] Remove obsolete theme tests --- .../monaco-diff-editor.component.spec.ts | 19 +------------- .../monaco-editor.component.spec.ts | 26 ------------------- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts index c9e7e433b8c1..459c093e3896 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-diff-editor.component.spec.ts @@ -1,14 +1,11 @@ -import { Theme, ThemeService } from 'app/core/theme/theme.service'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTestModule } from '../../../test.module'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; import { MonacoDiffEditorComponent } from 'app/shared/monaco-editor/monaco-diff-editor.component'; -import { BehaviorSubject } from 'rxjs'; describe('MonacoDiffEditorComponent', () => { let fixture: ComponentFixture; let comp: MonacoDiffEditorComponent; - let mockThemeService: ThemeService; beforeEach(() => { TestBed.configureTestingModule({ @@ -19,7 +16,6 @@ describe('MonacoDiffEditorComponent', () => { global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { return new MockResizeObserver(callback); }); - mockThemeService = TestBed.inject(ThemeService); fixture = TestBed.createComponent(MonacoDiffEditorComponent); comp = fixture.componentInstance; }); @@ -29,25 +25,12 @@ describe('MonacoDiffEditorComponent', () => { jest.restoreAllMocks(); }); - it('should adjust its theme to the global theme', () => { - const themeSubject = new BehaviorSubject(Theme.LIGHT); - const subscribeStub = jest.spyOn(mockThemeService, 'getCurrentThemeObservable').mockReturnValue(themeSubject.asObservable()); - const changeThemeSpy = jest.spyOn(comp, 'changeTheme'); - fixture.detectChanges(); - themeSubject.next(Theme.DARK); - expect(subscribeStub).toHaveBeenCalledOnce(); - expect(changeThemeSpy).toHaveBeenCalledTimes(2); - expect(changeThemeSpy).toHaveBeenNthCalledWith(1, Theme.LIGHT); - expect(changeThemeSpy).toHaveBeenNthCalledWith(2, Theme.DARK); - }); - it('should dispose its listeners and subscriptions when destroyed', () => { fixture.detectChanges(); const resizeObserverDisconnectSpy = jest.spyOn(comp.resizeObserver!, 'disconnect'); - const themeSubscriptionUnsubscribeSpy = jest.spyOn(comp.themeSubscription!, 'unsubscribe'); const listenerDisposeSpies = comp.listeners.map((listener) => jest.spyOn(listener, 'dispose')); comp.ngOnDestroy(); - for (const spy of [resizeObserverDisconnectSpy, themeSubscriptionUnsubscribeSpy, ...listenerDisposeSpies]) { + for (const spy of [resizeObserverDisconnectSpy, ...listenerDisposeSpies]) { expect(spy).toHaveBeenCalledOnce(); } }); diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts index cddb2c7c32df..df228c9b340c 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.component.spec.ts @@ -2,8 +2,6 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testin import { ArtemisTestModule } from '../../../test.module'; import { MonacoEditorComponent } from 'app/shared/monaco-editor/monaco-editor.component'; import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; -import { Theme, ThemeService } from 'app/core/theme/theme.service'; -import { BehaviorSubject } from 'rxjs'; import { MonacoEditorBuildAnnotationType } from 'app/shared/monaco-editor/model/monaco-editor-build-annotation.model'; import { MonacoCodeEditorElement } from 'app/shared/monaco-editor/model/monaco-code-editor-element.model'; import { MonacoEditorLineDecorationsHoverButton } from 'app/shared/monaco-editor/model/monaco-editor-line-decorations-hover-button.model'; @@ -13,7 +11,6 @@ import { MonacoEditorOptionPreset } from 'app/shared/monaco-editor/model/monaco- describe('MonacoEditorComponent', () => { let fixture: ComponentFixture; let comp: MonacoEditorComponent; - let mockThemeService: ThemeService; const singleLineText = 'public class Main { }'; const multiLineText = ['public class Main {', 'static void main() {', 'foo();', '}', '}'].join('\n'); @@ -26,7 +23,6 @@ describe('MonacoEditorComponent', () => { }) .compileComponents() .then(() => { - mockThemeService = TestBed.inject(ThemeService); fixture = TestBed.createComponent(MonacoEditorComponent); comp = fixture.componentInstance; global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { @@ -75,28 +71,6 @@ describe('MonacoEditorComponent', () => { expect(comp.isReadOnly()).toBeFalse(); }); - it('should adjust its theme to the global theme', () => { - const themeSubject = new BehaviorSubject(Theme.LIGHT); - const subscribeStub = jest.spyOn(mockThemeService, 'getCurrentThemeObservable').mockReturnValue(themeSubject.asObservable()); - const changeThemeSpy = jest.spyOn(comp, 'changeTheme'); - fixture.detectChanges(); - themeSubject.next(Theme.DARK); - expect(subscribeStub).toHaveBeenCalledOnce(); - expect(changeThemeSpy).toHaveBeenCalledTimes(2); - expect(changeThemeSpy).toHaveBeenNthCalledWith(1, Theme.LIGHT); - expect(changeThemeSpy).toHaveBeenNthCalledWith(2, Theme.DARK); - }); - - it('should unsubscribe from the global theme when destroyed', () => { - const themeSubject = new BehaviorSubject(Theme.LIGHT); - const subscribeStub = jest.spyOn(mockThemeService, 'getCurrentThemeObservable').mockReturnValue(themeSubject.asObservable()); - fixture.detectChanges(); - const unsubscribeStub = jest.spyOn(comp['themeSubscription']!, 'unsubscribe').mockImplementation(); - comp.ngOnDestroy(); - expect(subscribeStub).toHaveBeenCalledOnce(); - expect(unsubscribeStub).toHaveBeenCalledOnce(); - }); - it('should display hidden line widgets', () => { const lineWidgetDiv = document.createElement('div'); // This is the case e.g. for feedback items. From ee18ea8f7a56e345039790d11bb92ebe8f1507a2 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 2 Oct 2024 10:25:46 +0200 Subject: [PATCH 19/23] Rework service tests --- .../monaco-editor.service.spec.ts | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts index aeab3da5db18..7d6e5f350723 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts @@ -1,20 +1,31 @@ import { TestBed } from '@angular/core/testing'; import * as monaco from 'monaco-editor'; -import { Theme } from 'app/core/theme/theme.service'; +import { Theme, ThemeService } from 'app/core/theme/theme.service'; import { MonacoEditorService } from '../../../../../../main/webapp/app/shared/monaco-editor/monaco-editor.service'; import { ArtemisTestModule } from '../../../test.module'; import { CUSTOM_MARKDOWN_LANGUAGE_ID } from 'app/shared/monaco-editor/model/languages/monaco-custom-markdown.language'; +import { BehaviorSubject } from 'rxjs'; +import { MockResizeObserver } from '../../../helpers/mocks/service/mock-resize-observer'; describe('MonacoEditorService', () => { - let service: MonacoEditorService; + let monacoEditorService: MonacoEditorService; + let setThemeSpy: jest.SpyInstance; let registerLanguageSpy: jest.SpyInstance; + const themeSubject = new BehaviorSubject(Theme.LIGHT); beforeEach(() => { TestBed.configureTestingModule({ imports: [ArtemisTestModule], }); + // Avoids an error with the diff editor, which uses a ResizeObserver. + global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { + return new MockResizeObserver(callback); + }); registerLanguageSpy = jest.spyOn(monaco.languages, 'register'); - service = TestBed.inject(MonacoEditorService); + setThemeSpy = jest.spyOn(monaco.editor, 'setTheme'); + const themeService = TestBed.inject(ThemeService); + jest.spyOn(themeService, 'getCurrentThemeObservable').mockReturnValue(themeSubject.asObservable()); + monacoEditorService = TestBed.inject(MonacoEditorService); }); afterEach(() => { @@ -27,9 +38,34 @@ describe('MonacoEditorService', () => { expect(registerLanguageSpy).toHaveBeenCalledExactlyOnceWith({ id: customMarkdownLanguage!.id }); }); - it.each([Theme.LIGHT, Theme.DARK])('should apply the correct theme for $identifier mode', (theme) => { - const setThemeSpy = jest.spyOn(monaco.editor, 'setTheme'); - service.applyTheme(theme); - expect(setThemeSpy).toHaveBeenCalledExactlyOnceWith(theme === Theme.LIGHT ? 'vs' : 'vs-dark'); + it('should correctly handle themes', () => { + // Initialization: The editor should be in light mode since that is what we initialized the themeSubject with + expect(setThemeSpy).toHaveBeenCalledExactlyOnceWith(MonacoEditorService.LIGHT_THEME_ID); + // Switch to dark theme + themeSubject.next(Theme.DARK); + TestBed.flushEffects(); + expect(setThemeSpy).toHaveBeenCalledTimes(2); + expect(setThemeSpy).toHaveBeenNthCalledWith(2, MonacoEditorService.DARK_THEME_ID); + // Switch back to light theme + themeSubject.next(Theme.LIGHT); + TestBed.flushEffects(); + expect(setThemeSpy).toHaveBeenCalledTimes(3); + expect(setThemeSpy).toHaveBeenNthCalledWith(3, MonacoEditorService.LIGHT_THEME_ID); + }); + + it('should inject an editor into the provided DOM element', () => { + const element = document.createElement('div'); + const editor = monacoEditorService.createStandaloneCodeEditor(element); + expect(editor.getContainerDomNode()).toBe(element); + expect(element.children).toHaveLength(1); + expect(element.children.item(0)!.className).toContain('monaco-editor'); + }); + + it('should inject a diff editor into the provided DOM element', () => { + const element = document.createElement('div'); + const diffEditor = monacoEditorService.createStandaloneDiffEditor(element); + expect(diffEditor.getContainerDomNode()).toBe(element); + expect(element.children).toHaveLength(1); + expect(element.children.item(0)!.className).toContain('monaco-diff-editor'); }); }); From 612406452a8afe01d67c451b6b8fef2d13dea450 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 2 Oct 2024 10:40:28 +0200 Subject: [PATCH 20/23] Refactor --- .../monaco-editor.service.spec.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts index 7d6e5f350723..e6b051ed282d 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts @@ -53,19 +53,17 @@ describe('MonacoEditorService', () => { expect(setThemeSpy).toHaveBeenNthCalledWith(3, MonacoEditorService.LIGHT_THEME_ID); }); - it('should inject an editor into the provided DOM element', () => { - const element = document.createElement('div'); - const editor = monacoEditorService.createStandaloneCodeEditor(element); - expect(editor.getContainerDomNode()).toBe(element); - expect(element.children).toHaveLength(1); - expect(element.children.item(0)!.className).toContain('monaco-editor'); - }); - - it('should inject a diff editor into the provided DOM element', () => { - const element = document.createElement('div'); - const diffEditor = monacoEditorService.createStandaloneDiffEditor(element); - expect(diffEditor.getContainerDomNode()).toBe(element); - expect(element.children).toHaveLength(1); - expect(element.children.item(0)!.className).toContain('monaco-diff-editor'); - }); + it.each([ + { className: 'monaco-editor', createFn: (element: HTMLElement) => monacoEditorService.createStandaloneCodeEditor(element) }, + { className: 'monaco-diff-editor', createFn: (element: HTMLElement) => monacoEditorService.createStandaloneDiffEditor(element) }, + ])( + 'should inject an editor ($className) into the provided DOM element', + ({ className, createFn }: { className: string; createFn: (element: HTMLElement) => monaco.editor.IStandaloneCodeEditor | monaco.editor.IStandaloneDiffEditor }) => { + const element = document.createElement('div'); + const editor = createFn(element); + expect(editor.getContainerDomNode()).toBe(element); + expect(element.children).toHaveLength(1); + expect(element.children.item(0)!.classList).toContain(className); + }, + ); }); From c17be7227038544c179231013afbe3b8a68331aa Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 2 Oct 2024 10:43:18 +0200 Subject: [PATCH 21/23] Refactor --- .../webapp/app/shared/monaco-editor/monaco-editor.service.ts | 5 +++++ .../shared/monaco-editor/monaco-editor.service.spec.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts index 4c1c808662bf..0d00d81300a3 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-editor.service.ts @@ -4,6 +4,11 @@ import { CUSTOM_MARKDOWN_CONFIG, CUSTOM_MARKDOWN_LANGUAGE, CUSTOM_MARKDOWN_LANGU import { Theme, ThemeService } from 'app/core/theme/theme.service'; import { toSignal } from '@angular/core/rxjs-interop'; +/** + * Service providing shared functionality for the Monaco editor. + * This service is intended to be used by components that need to create and manage Monaco editors. + * It also ensures that the editor's theme matches the current theme of Artemis. + */ @Injectable({ providedIn: 'root' }) export class MonacoEditorService { static readonly LIGHT_THEME_ID = 'vs'; diff --git a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts index e6b051ed282d..82ffd4e2b8b3 100644 --- a/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts +++ b/src/test/javascript/spec/component/shared/monaco-editor/monaco-editor.service.spec.ts @@ -57,7 +57,7 @@ describe('MonacoEditorService', () => { { className: 'monaco-editor', createFn: (element: HTMLElement) => monacoEditorService.createStandaloneCodeEditor(element) }, { className: 'monaco-diff-editor', createFn: (element: HTMLElement) => monacoEditorService.createStandaloneDiffEditor(element) }, ])( - 'should inject an editor ($className) into the provided DOM element', + 'should insert an editor ($className) into the provided DOM element', ({ className, createFn }: { className: string; createFn: (element: HTMLElement) => monaco.editor.IStandaloneCodeEditor | monaco.editor.IStandaloneDiffEditor }) => { const element = document.createElement('div'); const editor = createFn(element); From a80401474f7c4161ffb9715f039e99cf83a457e4 Mon Sep 17 00:00:00 2001 From: Patrik Zander Date: Wed, 2 Oct 2024 11:42:35 +0200 Subject: [PATCH 22/23] Fix tests --- .../code-editor/code-editor-container.integration.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts index 866a8ba7d7af..406dce4f6a5c 100644 --- a/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts +++ b/src/test/javascript/spec/integration/code-editor/code-editor-container.integration.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, flush, tick } from '@angular/core/testing'; import { LocalStorageService, SessionStorageService } from 'ngx-webstorage'; import dayjs from 'dayjs/esm'; -import { ChangeDetectorRef, DebugElement } from '@angular/core'; +import { DebugElement } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { NgModel } from '@angular/forms'; import { NgbDropdown, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; @@ -125,7 +125,6 @@ describe('CodeEditorContainerIntegration', () => { MockComponent(CodeEditorTutorAssessmentInlineFeedbackComponent), ], providers: [ - ChangeDetectorRef, CodeEditorConflictStateService, MockProvider(AlertService), { provide: ActivatedRoute, useClass: MockActivatedRouteWithSubjects }, From 713dffb37cc75dced984d595596e61cff4950785 Mon Sep 17 00:00:00 2001 From: Patrik Zander <38403547+pzdr7@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:03:38 +0200 Subject: [PATCH 23/23] Update src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts --- .../app/shared/monaco-editor/monaco-diff-editor.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts index bf770f7c7bf0..8cb86be9a233 100644 --- a/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts +++ b/src/main/webapp/app/shared/monaco-editor/monaco-diff-editor.component.ts @@ -17,7 +17,7 @@ export class MonacoDiffEditorComponent implements OnInit, OnDestroy { private _editor: monaco.editor.IStandaloneDiffEditor; monacoDiffEditorContainerElement: HTMLElement; - allowSplitView = input(); + allowSplitView = input(true); onReadyForDisplayChange = output(); /*