From 43a46d04e88ef0c845c180d13f3fbb2ab2716d5c Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Tue, 5 Oct 2021 22:54:16 +0300 Subject: [PATCH] new component for editor and fix compare for template dictionaries --- .../dictionary-overview-screen.component.ts | 6 +- .../edit-dossier-dictionary.component.ts | 4 +- .../dictionary-manager.component.html | 61 ++++---- .../dictionary-manager.component.scss | 6 - .../dictionary-manager.component.ts | 147 +++++++----------- .../components/editor/editor.component.html | 14 ++ .../components/editor/editor.component.scss | 6 + .../components/editor/editor.component.ts | 104 +++++++++++++ .../src/app/modules/shared/shared.module.ts | 3 +- apps/red-ui/src/assets/i18n/de.json | 3 +- apps/red-ui/src/assets/i18n/en.json | 3 +- 11 files changed, 223 insertions(+), 134 deletions(-) create mode 100644 apps/red-ui/src/app/modules/shared/components/editor/editor.component.html create mode 100644 apps/red-ui/src/app/modules/shared/components/editor/editor.component.scss create mode 100644 apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts diff --git a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts index f8efa69b6..3933d6ab1 100644 --- a/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/dictionary-overview/dictionary-overview-screen.component.ts @@ -42,7 +42,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple } get hasChanges() { - return this._dictionaryManager.hasChanges; + return this._dictionaryManager.editor.hasChanges; } async ngOnInit() { @@ -85,7 +85,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple } download(): void { - const content = this._dictionaryManager.editorValue; + const content = this._dictionaryManager.editor.value; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); @@ -98,7 +98,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple if (file) { fileReader.onload = () => { - this._dictionaryManager.editorValue = fileReader.result as string; + this._dictionaryManager.editor.value = fileReader.result as string; this._fileInput.nativeElement.value = null; }; fileReader.readAsText(file); diff --git a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts index 6f43510c1..1a63a0900 100644 --- a/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts +++ b/apps/red-ui/src/app/modules/dossier/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts @@ -33,7 +33,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa } get changed() { - return this._dictionaryManager.hasChanges; + return this._dictionaryManager.editor.hasChanges; } get disabled() { @@ -58,7 +58,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa async save() { await this._dictionaryService .saveEntries( - this._dictionaryManager.currentEntries, + this._dictionaryManager.editor.currentEntries, this._dictionaryManager.initialEntries, this.dossier.dossierTemplateId, 'dossier_redaction', diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html index 64a7db0ee..7d6a3e05e 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.html @@ -30,18 +30,31 @@ {{ 'dictionary-overview.compare.compare' | translate }} -
- - {{ selectDossierTemplate.name | translate }} - - {{ dossierTemplate.name }} - - -
-
- + + +
+ + {{ selectDossierTemplate.name | translate }} + + {{ dossierTemplate.name }} + + +
+ +
+ + {{ selectDictionary.label | translate }} + + {{ dictionary.label }} + + +
+
+ +
+ {{ selectDossier.dossierName | translate }} - + {{ dossier.dossierName }} @@ -50,30 +63,22 @@
- + - - -
+
-
+
(); + @ViewChild(EditorComponent) readonly editor: EditorComponent; currentMatch = 0; findMatches: FindMatch[] = []; - currentEntries: string[] = []; - editorOptions: IStandaloneEditorConstructionOptions = {}; diffEditorText = ''; showDiffEditor = false; searchText = ''; - - selectDossier = { dossierName: _('dictionary-overview.compare.select-dossier') }; - selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') }; + selectDossier = { dossierName: _('dictionary-overview.compare.select-dossier') } as Dossier; + selectDictionary = { + label: _('dictionary-overview.compare.select-dictionary') + } as TypeValue; + selectDossierTemplate = { name: _('dictionary-overview.compare.select-dossier-template') } as DossierTemplate; compare: false; - dossiers: List = this._dossiers; - - private _codeEditor: ICodeEditor; - private _diffEditor: IDiffEditor; - private _decorations: string[] = []; + dictionaries: List = this._dictionaries; private _searchDecorations: string[] = []; constructor( private readonly _dictionaryService: DictionaryService, readonly dossiersService: DossiersService, + readonly appStateService: AppStateService, readonly dossierTemplatesService: DossierTemplatesService ) {} - private _dossierTemplate: DossierTemplate = this.selectDossierTemplate as DossierTemplate; + private _dossierTemplate = this.selectDossierTemplate; get dossierTemplate() { return this._dossierTemplate; @@ -63,11 +60,12 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { set dossierTemplate(value) { this._dossierTemplate = value; - this.dossier = this.selectDossier as Dossier; - this.dossiers = this._dossiers; + this.dictionaries = this._dictionaries; + this._dictionary = this.selectDictionary; + this.showDiffEditor = false; } - private _dossier: Dossier = this.selectDossier as Dossier; + private _dossier = this.selectDossier; get dossier() { return this._dossier; @@ -76,77 +74,61 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { set dossier(dossier: Dossier) { this._dossier = dossier; - if (dossier === this.selectDossier) { + if (dossier.dossierName === this.selectDossier.dossierName) { this.showDiffEditor = false; this.diffEditorText = ''; return; } - this._onDossierChanged(dossier) + this._onDossierChanged(dossier.dossierTemplateId, dossier.dossierId) .pipe(take(1)) .subscribe(entries => { this.diffEditorText = entries; this.showDiffEditor = true; - if (this.showDiffEditor) { - this._diffEditor?.getOriginalEditor().setValue(this.diffEditorText); - } }); } - get editorValue(): string { - return this.currentEntries.join('\n'); + private _dictionary = this.selectDictionary; + + get dictionary() { + return this._dictionary; } - set editorValue(text: string) { - this.currentEntries = text.split('\n'); - this.codeEditorTextChanged(); - } + set dictionary(dictionary: TypeValue) { + this._dictionary = dictionary; - get hasChanges(): boolean { - return this.currentEntries.toString() !== this.initialEntries.toString(); - } - - get _dossiers() { - if (this.filterByDossierTemplate) { - return this.dossiersService.all.filter(dossier => dossier.dossierTemplateId === this.dossierTemplate.dossierTemplateId); + if (dictionary.label === this.selectDictionary.label) { + this.showDiffEditor = false; + this.diffEditorText = ''; + return; } - return this.dossiersService.all; + + const entries = this.appStateService.dictionaryData[this._dictionary.dossierTemplateId][this._dictionary.type].entries; + + this.diffEditorText = this._toString(entries); + this.showDiffEditor = true; + } + + get _dictionaries() { + if (!this._dossierTemplate || this._dossierTemplate.name === this.selectDossierTemplate.name) { + return; + } + return Object.values(this.appStateService.dictionaryData[this.dossierTemplate?.dossierTemplateId]); } get dossierTemplateIsNotSelected() { return this.filterByDossierTemplate && this._dossierTemplate.name === this.selectDossierTemplate.name; } - ngOnInit(): void { - this.currentEntries = [...this.initialEntries]; - - this.editorOptions = { - theme: 'vs', - language: 'text/plain', - automaticLayout: true, - readOnly: !this.canEdit - }; - } - - onDiffEditorInit(editor: IDiffEditor): void { - this._diffEditor = editor; - } - - 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 optionNotSelected() { + if (this.filterByDossierTemplate) { + return this.selectDictionary.label === this._dictionary.label; + } + return this.dossier.dossierName === this.selectDossier.dossierName; } revert() { - this.currentEntries = [...this.initialEntries]; + this.editor?.revert(); this.searchChanged(''); } @@ -160,13 +142,6 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { this.nextSearchMatch(); } - @Debounce() - codeEditorTextChanged() { - const newDecorations = this.currentEntries.filter(entry => this._isNew(entry)).map(entry => this._getDecoration(entry)); - - this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations); - } - nextSearchMatch(): void { if (this.findMatches.length <= 0) { return; @@ -190,30 +165,18 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { } private _applySearchDecorations() { - this._searchDecorations = this._codeEditor?.deltaDecorations(this._searchDecorations, []) || []; + this._searchDecorations = this.editor.codeEditor?.deltaDecorations(this._searchDecorations, []) || []; const decorations = this.findMatches.map(match => this._getSearchDecoration(match)); - this._searchDecorations = this._codeEditor?.deltaDecorations(this._searchDecorations, decorations) || []; + this._searchDecorations = this.editor.codeEditor?.deltaDecorations(this._searchDecorations, decorations) || []; } private _getMatches(text: string): FindMatch[] { - const model = this._codeEditor?.getModel(); + const model = this.editor.codeEditor?.getModel(); return model?.findMatches(text, false, false, false, null, false) || []; } - private _isNew(entry: string): boolean { - return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0; - } - - private _getDecoration(entry: string): IModelDeltaDecoration { - const line = this.currentEntries.indexOf(entry) + 1; - const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker'; - const range = new monaco.Range(line, 1, line, 1); - - return { range: range, options: { isWholeLine: true, className: cssClass } }; - } - private _getSearchDecoration(match: FindMatch): IModelDeltaDecoration { return { range: match.range, options: { inlineClassName: 'search-marker' } }; } @@ -221,12 +184,12 @@ export class DictionaryManagerComponent implements OnChanges, OnInit { private _scrollToCurrentMatch(): void { const range = this.findMatches[this.currentMatch - 1].range; - this._codeEditor.setSelection(range); - this._codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL); + this.editor.codeEditor.setSelection(range); + this.editor.codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL); } - private _onDossierChanged({ id, dossierTemplateId }: Dossier): Observable { - const dictionary$ = this._dictionaryService.getFor(dossierTemplateId, 'dossier_redaction', id); + private _onDossierChanged(dossierTemplateId: string, dossierId?: string, type = 'dossier_redaction'): Observable { + const dictionary$ = this._dictionaryService.getFor(dossierTemplateId, type, dossierId); return dictionary$.pipe(map(data => this._toString([...data.entries]))); } 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 new file mode 100644 index 000000000..4834d2b2a --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html @@ -0,0 +1,14 @@ + + + diff --git a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.scss b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.scss new file mode 100644 index 000000000..7707ea433 --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.scss @@ -0,0 +1,6 @@ +:host, +ngx-monaco-diff-editor, +ngx-monaco-editor { + height: 100%; + width: 100%; +} 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 new file mode 100644 index 000000000..fee65d68c --- /dev/null +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts @@ -0,0 +1,104 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { Debounce, List } from '@iqser/common-ui'; +import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions; +import ICodeEditor = monaco.editor.ICodeEditor; +import IDiffEditor = monaco.editor.IDiffEditor; +import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration; + +const MIN_WORD_LENGTH = 2; + +@Component({ + selector: 'redaction-editor', + templateUrl: './editor.component.html', + styleUrls: ['./editor.component.scss'] +}) +export class EditorComponent implements OnInit, OnChanges { + editorOptions: IStandaloneEditorConstructionOptions = {}; + @Input() showDiffEditor = false; + @Input() initialEntries: List; + @Input() canEdit = false; + currentEntries: string[] = []; + codeEditor: ICodeEditor; + diffEditor: IDiffEditor; + private _decorations: string[] = []; + _diffEditorText = ''; + + get diffEditorText() { + return this._diffEditorText; + } + + @Input() + set diffEditorText(value: string) { + this._diffEditorText = value; + this.diffEditor?.getOriginalEditor().setValue(value); + } + + get value(): string { + return this.currentEntries.join('\n'); + } + + set value(text: string) { + this.currentEntries = text.split('\n'); + this.codeEditorTextChanged(); + } + + get hasChanges(): boolean { + return this.currentEntries.toString() !== this.initialEntries.toString(); + } + + ngOnInit(): void { + this.currentEntries = [...this.initialEntries]; + + this.editorOptions = { + theme: 'vs', + language: 'text/plain', + automaticLayout: true, + readOnly: !this.canEdit + }; + } + + ngOnChanges(changes: SimpleChanges) { + console.log(changes); + this.revert(); + } + + onDiffEditorInit(editor: IDiffEditor): void { + this.diffEditor = editor; + } + + 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'); + } + + @Debounce() + codeEditorTextChanged() { + const newDecorations = this.currentEntries.filter(entry => this._isNew(entry)).map(entry => this._getDecoration(entry)); + + this._decorations = this.codeEditor.deltaDecorations(this._decorations, newDecorations); + } + + revert() { + this.currentEntries = [...this.initialEntries]; + } + + private _isNew(entry: string): boolean { + return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0; + } + + private _getDecoration(entry: string): IModelDeltaDecoration { + const line = this.currentEntries.indexOf(entry) + 1; + const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker'; + const range = new monaco.Range(line, 1, line, 1); + + return { range: range, options: { isWholeLine: true, className: cssClass } }; + } +} diff --git a/apps/red-ui/src/app/modules/shared/shared.module.ts b/apps/red-ui/src/app/modules/shared/shared.module.ts index 97ff6f7a2..2698a60f3 100644 --- a/apps/red-ui/src/app/modules/shared/shared.module.ts +++ b/apps/red-ui/src/app/modules/shared/shared.module.ts @@ -24,6 +24,7 @@ import { LongPressDirective } from './directives/long-press.directive'; import { NamePipe } from './pipes/name.pipe'; import { TypeFilterComponent } from './components/type-filter/type-filter.component'; import { TeamMembersComponent } from './components/team-members/team-members.component'; +import { EditorComponent } from './components/editor/editor.component'; const buttons = [FileDownloadBtnComponent, UserButtonComponent]; @@ -47,7 +48,7 @@ const utils = [DatePipe, NamePipe, NavigateLastDossiersScreenDirective, LongPres const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule, CommonUiModule]; @NgModule({ - declarations: [...components, ...utils], + declarations: [...components, ...utils, EditorComponent], imports: [CommonModule, ...modules, MonacoEditorModule], exports: [...modules, ...components, ...utils], providers: [ diff --git a/apps/red-ui/src/assets/i18n/de.json b/apps/red-ui/src/assets/i18n/de.json index 6769d6015..cad0fbd1f 100644 --- a/apps/red-ui/src/assets/i18n/de.json +++ b/apps/red-ui/src/assets/i18n/de.json @@ -435,7 +435,8 @@ "compare": { "compare": "Vergleichen Sie", "select-dossier": "Wählen Sie Dossiervorlage", - "select-dossier-template": "" + "select-dossier-template": "", + "select-dictionary": "" }, "dictionary-details": { "description": "Beschreibung" diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 7bfc55b26..a219ab90e 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -475,7 +475,8 @@ "compare": { "compare": "Compare", "select-dossier": "Select Dossier", - "select-dossier-template": "Select Dossier Template" + "select-dossier-template": "Select Dossier Template", + "select-dictionary": "Select Dictionary" }, "dictionary-details": { "description": "Description"