From c68e92f7267696eb3dfa0579c2363d082a7a7751 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Fri, 26 May 2023 00:07:16 +0300 Subject: [PATCH] RED-6786: faster text editor --- .../components/editor/editor.component.html | 6 +- .../components/editor/editor.component.ts | 60 ++++++++++--------- 2 files changed, 35 insertions(+), 31 deletions(-) diff --git a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html index 441062e92..10abee1e3 100644 --- a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html @@ -1,10 +1,10 @@ - - diff --git a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts index 2b34ec87f..914e4931a 100644 --- a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts @@ -1,14 +1,15 @@ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { List, LoadingService, OnChange } from '@iqser/common-ui'; import { EditorThemeService } from '@services/editor-theme.service'; +import { Subject } from 'rxjs'; +import { debounceTime, filter, tap } from 'rxjs/operators'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions; import ICodeEditor = monaco.editor.ICodeEditor; import IDiffEditor = monaco.editor.IDiffEditor; import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration; import ILineChange = monaco.editor.ILineChange; import IEditorMouseEvent = monaco.editor.IEditorMouseEvent; -import { BehaviorSubject } from 'rxjs'; -import { debounceTime, filter, tap } from 'rxjs/operators'; const MIN_WORD_LENGTH = 2; const lineChangeToDecoration = ({ originalEndLineNumber, originalStartLineNumber }: ILineChange) => @@ -41,28 +42,35 @@ export class EditorComponent implements OnInit, OnChanges { editorOptions: IStandaloneEditorConstructionOptions = {}; codeEditor: ICodeEditor; value: string; + protected _initialEntriesMap = new Set(); + + protected readonly _editorTextChanged$ = new Subject(); private _diffEditor: IDiffEditor; private _decorations: string[] = []; - private readonly _codeEditorTextChanged$ = new BehaviorSubject(''); - protected readonly _codeEditorTextChangedHandler$ = this._codeEditorTextChanged$.asObservable().pipe( - filter(Boolean), - debounceTime(0), // prevent race condition with onPaste event - tap(newText => { - const newDecorations = this.#getDecorations(newText); - this._decorations = this.codeEditor.deltaDecorations(this._decorations, newDecorations); - this.diffValue = this.value; - this._loadingService.stop(); - }), - ); - constructor(private readonly _loadingService: LoadingService, private readonly _editorThemeService: EditorThemeService) {} + constructor(private readonly _loadingService: LoadingService, private readonly _editorThemeService: EditorThemeService) { + const textChanged$ = this._editorTextChanged$.pipe( + debounceTime(300), // prevent race condition with onPaste event + takeUntilDestroyed(), + filter(text => text.length > 0), + tap(newText => { + const newDecorations = this.#getDecorations(newText); + this._decorations = this.codeEditor.deltaDecorations(this._decorations, newDecorations); + this.diffValue = this.value; + this._loadingService.stop(); + }), + ); + // eslint-disable-next-line rxjs/no-ignored-subscription + textChanged$.subscribe(); + } get currentEntries(): string[] { return this.value.split('\n'); } + // TODO: this is called way too often, we need a better option get hasChanges(): boolean { - return this.currentEntries.toString() !== this.initialEntries.toString(); + return this.value.trim() !== this.initialEntries.join('\n'); } get #currentEntriesCount(): number { @@ -73,10 +81,6 @@ export class EditorComponent implements OnInit, OnChanges { this._loadingService.start(); } - codeEditorTextChanged(event) { - this._codeEditorTextChanged$.next(event); - } - ngOnChanges(changes: SimpleChanges) { if (changes.diffEditorText) { this._diffEditor?.getOriginalEditor().setValue(this.diffEditorText); @@ -114,17 +118,23 @@ export class EditorComponent implements OnInit, OnChanges { revert() { this.value = this.initialEntries.join('\n'); + this._initialEntriesMap = new Set(this.initialEntries); this.diffValue = this.value; this._diffEditor?.getModifiedEditor().setValue(this.diffValue); + this._editorTextChanged$.next(this.value); } #getDecorations(newText: string) { - const currentEntries = newText.split('/n'); + const currentEntries = newText.split('\n'); const newDecorations: IModelDeltaDecoration[] = []; - for (let index = 0; index < currentEntries.length; ++index) { - const entry = currentEntries.at(index); - if (!this.#isNew(entry)) { + for (let index = 0; index < currentEntries.length; index++) { + const entry = currentEntries.at(index)?.trim(); + if (!entry || entry.length === 0) { + continue; + } + + if (this._initialEntriesMap.has(entry)) { continue; } @@ -187,10 +197,6 @@ export class EditorComponent implements OnInit, OnChanges { }); } - #isNew(entry: string): boolean { - return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0; - } - #getDecoration(entry: string, line: number): IModelDeltaDecoration { const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker'; const range = new monaco.Range(line, 1, line, 1);