- {{ currentMatch + '/' + searchPositions.length }}
+ {{ currentMatch + '/' + searchMatches.length }}
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
();
- editDecorations: IModelDeltaDecoration[] = [];
- searchDecorations: IModelDeltaDecoration[] = [];
- searchPositions: ISearchPosition[] = [];
- currentMatch = 1;
+ currentMatch = 0;
+ searchMatches: FindMatch[] = [];
currentEntries: string[] = [];
- changedLines: number[] = [];
- editorOptions = { theme: 'vs-light', language: 'text/plain', automaticLayout: true };
+ editorOptions: IStandaloneEditorConstructionOptions = {
+ theme: 'vs',
+ language: 'text/plain',
+ automaticLayout: true,
+ readOnly: !this.canEdit
+ };
diffEditorText = '';
+ showDiffEditor = false;
searchText = '';
selectRuleSet = { name: 'dictionary-overview.compare.select-ruleset' };
@@ -55,13 +50,15 @@ export class DictionaryManagerComponent implements OnChanges {
private _codeEditor: ICodeEditor;
private _diffEditor: IDiffEditor;
+ private _decorations: string[] = [];
+ private _searchDecorations: string[] = [];
constructor(
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _appStateService: AppStateService,
- private readonly _formBuilder: FormBuilder,
- private readonly _changeDetector: ChangeDetectorRef
+ private readonly _formBuilder: FormBuilder
) {
+ this.currentEntries = this.initialEntries;
this.form = this._formBuilder.group({
active: [false],
ruleSet: [{ value: this.selectRuleSet, disabled: true }],
@@ -83,8 +80,17 @@ export class DictionaryManagerComponent implements OnChanges {
this._onRuleSetChanged();
});
+ this.form.controls.active.valueChanges.subscribe((value) => {
+ this.showDiffEditor =
+ value && this.form.get('dictionary').value !== this.selectDictionary;
+ });
+
this.form.controls.dictionary.valueChanges.subscribe((dictionary) => {
- this._onDictionaryChanged(dictionary);
+ this._onDictionaryChanged(dictionary).subscribe((data) => {
+ this.diffEditorText = data;
+ this.showDiffEditor = dictionary !== this.selectDictionary;
+ if (this.showDiffEditor) this._diffEditor?.getOriginalEditor().setValue(data);
+ });
});
}
@@ -94,21 +100,23 @@ export class DictionaryManagerComponent implements OnChanges {
onCodeEditorInit(editor: ICodeEditor): void {
this._codeEditor = editor;
+ (window as any).monaco.editor.defineTheme('redaction', {
+ base: 'vs',
+ inherit: true,
+ rules: [],
+ colors: {
+ 'editor.lineHighlightBackground': '#f4f5f7'
+ }
+ });
+ (window as any).monaco.editor.setTheme('redaction');
}
- get showDiffEditor(): boolean {
- return (
- this.form.get('active').value &&
- this.form.get('dictionary').value !== this.selectDictionary
- );
+ get editorValue(): string {
+ return this._codeEditor?.getModel()?.getValue();
}
- get editorValue() {
- return this._codeEditor.getModel().getValue();
- }
-
- set editorValue(value: any) {
- this._codeEditor.getModel().setValue(value);
+ set editorValue(value: string) {
+ this._codeEditor?.getModel()?.setValue(value);
}
revert() {
@@ -119,71 +127,88 @@ export class DictionaryManagerComponent implements OnChanges {
@debounce()
searchChanged(text: string) {
this.searchText = text.toLowerCase();
+ this.searchMatches = this._getMatches(this.searchText);
this._applySearchDecorations();
+
this.currentMatch = 0;
this.nextSearchMatch();
}
+ private _applySearchDecorations() {
+ this._searchDecorations = this._codeEditor.deltaDecorations(this._searchDecorations, []);
+
+ const decorations = this.searchMatches.map(
+ (match) =>
+ ({
+ range: match.range,
+ options: { inlineClassName: 'search-marker' }
+ } as IModelDeltaDecoration)
+ );
+
+ this._searchDecorations = this._codeEditor.deltaDecorations(
+ this._searchDecorations,
+ decorations
+ );
+ }
+
+ private _getMatches(text: string): FindMatch[] {
+ const model = this._codeEditor?.getModel();
+ return model.findMatches(text, false, false, false, null, false) || [];
+ }
+
get codeEditorText(): string {
return this.currentEntries.join('\n');
}
set codeEditorText(text: string) {
- this._applySearchDecorations();
this.currentEntries = text.split('\n');
- this.changedLines = [];
- this.editDecorations = [];
- this._codeEditor.deltaDecorations([], this.editDecorations);
-
- this.currentEntries.forEach((entry, index) => {
- if (this.initialEntries.indexOf(entry) < 0) {
- this.changedLines.push(index);
- }
- });
-
- this.changedLines.forEach((line) => this._makeDecorations(line));
- this._codeEditor.deltaDecorations([], this.editDecorations);
+ this.codeEditorTextChanged();
}
- private _makeDecorations(line: number) {
- const entry = this.currentEntries[line];
- if (entry.trim().length === 0) return;
- const wholeLine = new monaco.Range(line, 0, line, 1);
- const decoration: IModelDeltaDecoration = {
- range: wholeLine,
- options: { isWholeLine: true, inlineClassName: 'changed-row-marker' }
- };
- this.editDecorations.push(decoration);
- if (entry.trim().length < MIN_WORD_LENGTH) {
- this.editDecorations.push({
- range: wholeLine,
- options: { isWholeLine: true, inlineClassName: 'too-short-marker' }
- });
- }
+ @debounce()
+ codeEditorTextChanged() {
+ const newDecorations = this.currentEntries
+ .filter((entry) => this._isNew(entry))
+ .map((entry) => this._makeDecorationFor(entry));
+
+ this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations);
}
- get hasChanges() {
+ private _isNew(entry: string): boolean {
+ return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0;
+ }
+
+ private _makeDecorationFor(entry: string): IModelDeltaDecoration {
+ const line = this.currentEntries.indexOf(entry) + 1;
+ const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker';
+
+ return {
+ range: new monaco.Range(line, 1, line, 1),
+ options: { isWholeLine: true, className: cssClass }
+ } as IModelDeltaDecoration;
+ }
+
+ get hasChanges(): boolean {
return (
this.currentEntries.length &&
- (this.editDecorations.length > 0 ||
- this.currentEntries.filter((e) => e.trim().length > 0).length <
- this.initialEntries.length)
+ this.currentEntries.filter((e) => e.trim().length > 0).length <
+ this.initialEntries.length
);
}
- nextSearchMatch() {
- if (this.searchPositions.length > 0) {
+ nextSearchMatch(): void {
+ if (this.searchMatches?.length > 0) {
this.currentMatch =
- this.currentMatch < this.searchPositions.length ? this.currentMatch + 1 : 1;
- this._gotoLine();
+ this.currentMatch < this.searchMatches.length ? this.currentMatch + 1 : 1;
+ this._scrollToCurrentMatch();
}
}
- previousSearchMatch() {
- if (this.searchPositions.length > 0) {
+ previousSearchMatch(): void {
+ if (this.searchMatches.length > 0) {
this.currentMatch =
- this.currentMatch > 1 ? this.currentMatch - 1 : this.searchPositions.length;
- this._gotoLine();
+ this.currentMatch > 1 ? this.currentMatch - 1 : this.searchMatches.length;
+ this._scrollToCurrentMatch();
}
}
@@ -206,33 +231,11 @@ export class DictionaryManagerComponent implements OnChanges {
];
}
- private _applySearchDecorations() {
- this.searchPositions = this._getSearchPositions();
- this.searchDecorations = this.searchPositions.map(({ row, column, length }) => ({
- range: new monaco.Range(row, column, row, column + length),
- options: { inlineClassName: 'search-marker' }
- }));
+ private _scrollToCurrentMatch(): void {
+ const range = this.searchMatches[this.currentMatch - 1].range;
- this._codeEditor.deltaDecorations([], this.searchDecorations);
- }
-
- private _getSearchPositions(): ISearchPosition[] {
- const searchText = this.searchText.toLowerCase();
- return this.currentEntries
- .map((val, index) => {
- const columnIndex = val.toLowerCase().indexOf(searchText);
- if (columnIndex >= 0) {
- return { row: index, column: columnIndex, length: searchText.length };
- }
- })
- .filter((entry) => !!entry);
- }
-
- private _gotoLine() {
- const position = this.searchPositions[this.currentMatch - 1];
- this._codeEditor.setScrollPosition({ scrollTop: position.row });
- // this._editorComponent.getEditor().scrollToLine(position.row, true, true, () => {});
- // this._editorComponent.getEditor().gotoLine(position.row + 1, position.column, true);
+ this._codeEditor.setSelection(range);
+ this._codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL);
}
private _onRuleSetChanged() {
@@ -240,20 +243,19 @@ export class DictionaryManagerComponent implements OnChanges {
this.form.patchValue({ dictionary: this.selectDictionary });
}
- private _onDictionaryChanged(dictionary: TypeValue) {
+ private _onDictionaryChanged(dictionary: TypeValue): Observable {
if (dictionary === this.selectDictionary) {
- return;
+ return of('');
}
- this._dictionaryControllerService
+ return this._dictionaryControllerService
.getDictionaryForType(dictionary.ruleSetId, dictionary.type)
- .subscribe((data) => {
- this.diffEditorText = data.entries
- .sort((str1, str2) =>
- str1.localeCompare(str2, undefined, { sensitivity: 'accent' })
- )
- .join('\n');
- this._changeDetector.detectChanges();
- });
+ .pipe(map((data) => this._toString(data.entries)));
+ }
+
+ private _toString(entries: string[]) {
+ return entries
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' }))
+ .join('\n');
}
saveEntries() {
diff --git a/apps/red-ui/src/assets/styles/red-editor.scss b/apps/red-ui/src/assets/styles/red-editor.scss
index cb53946c0..920726438 100644
--- a/apps/red-ui/src/assets/styles/red-editor.scss
+++ b/apps/red-ui/src/assets/styles/red-editor.scss
@@ -2,19 +2,16 @@
@import 'red-mixins';
.changed-row-marker {
- position: absolute;
background: rgba($primary, 0.1);
z-index: 20;
}
.too-short-marker {
- position: absolute;
background: rgba($primary, 0.5);
z-index: 30;
}
.search-marker {
- position: absolute;
background: rgba($blue-5, 0.3);
z-index: 40;
}