compare dictionary to other dossiers

This commit is contained in:
Dan Percic 2021-06-01 16:51:55 +03:00
parent 5cf7ccb81c
commit a495c55b3d
2 changed files with 118 additions and 192 deletions

View File

@ -16,7 +16,7 @@
</div> </div>
<div *ngIf="searchText.length > 0" class="with-input"> <div *ngIf="searchText.length > 0" class="with-input">
<div class="search-match-text"> <div class="search-match-text">
{{ currentMatch + '/' + searchMatches.length }} {{ currentMatch + '/' + findMatches.length }}
</div> </div>
<mat-icon <mat-icon
(click)="previousSearchMatch()" (click)="previousSearchMatch()"
@ -36,60 +36,40 @@
</div> </div>
</div> </div>
</div> </div>
<form [formGroup]="form"> <div class="red-input-group mr-16">
<div class="red-input-group mr-16"> <mat-checkbox [(ngModel)]="compare" color="primary">
<mat-checkbox color="primary" formControlName="active"> {{ 'dictionary-overview.compare.compare' | translate }}
{{ 'dictionary-overview.compare.compare' | translate }} </mat-checkbox>
</mat-checkbox> </div>
</div> <div class="red-input-group w-200 mr-8">
<div class="red-input-group w-200 mr-8"> <mat-select [(ngModel)]="dossier" [disabled]="!compare">
<mat-select formControlName="dossierTemplate"> <mat-option [value]="selectDossier">{{
<mat-option selectDossier.name | translate
*ngFor="let dossierTemplate of dossierTemplates" }}</mat-option>
[value]="dossierTemplate" <mat-option *ngFor="let dossier of dossiers" [value]="dossier">
> {{ dossier.name }}
{{ </mat-option>
dossierTemplate === selectDossierTemplate </mat-select>
? (dossierTemplate.name | translate) </div>
: dossierTemplate.name
}}
</mat-option>
</mat-select>
</div>
<div class="red-input-group w-200">
<mat-select formControlName="dictionary">
<mat-option *ngFor="let dictionary of dictionaries" [value]="dictionary">
{{
dictionary === selectDictionary
? (dictionary.label | translate)
: dictionary.label
}}
</mat-option>
</mat-select>
</div>
</form>
</div> </div>
<div class="editor-container"> <div class="editor-container">
<ngx-monaco-editor <ngx-monaco-editor
*ngIf="!showDiffEditor"
[options]="editorOptions"
[(ngModel)]="codeEditorText"
(init)="onCodeEditorInit($event)" (init)="onCodeEditorInit($event)"
*ngIf="!compare || !showDiffEditor"
[(ngModel)]="editorValue"
[options]="editorOptions"
></ngx-monaco-editor> ></ngx-monaco-editor>
<ngx-monaco-diff-editor <ngx-monaco-diff-editor
*ngIf="showDiffEditor" (init)="onDiffEditorInit($event)"
*ngIf="compare && showDiffEditor"
[modified]="editorValue"
[options]="editorOptions" [options]="editorOptions"
[original]="diffEditorText" [original]="diffEditorText"
[modified]="codeEditorText"
(init)="onDiffEditorInit($event)"
></ngx-monaco-diff-editor> ></ngx-monaco-diff-editor>
<div <div *ngIf="compare && dossier === selectDossier" class="no-dictionary-selected">
*ngIf="form.get('active').value && form.get('dictionary').value === selectDictionary"
class="no-dictionary-selected"
>
<mat-icon svgIcon="red:dictionary"></mat-icon> <mat-icon svgIcon="red:dictionary"></mat-icon>
<span class="heading-l" translate="dictionary-overview.select-dictionary"></span> <span class="heading-l" translate="dictionary-overview.select-dictionary"></span>
</div> </div>
@ -97,11 +77,11 @@
<div <div
*ngIf="withFloatingActions && hasChanges && canEdit" *ngIf="withFloatingActions && hasChanges && canEdit"
[class.offset]="form.get('active').value" [class.offset]="compare"
class="changes-box" class="changes-box"
> >
<redaction-icon-button <redaction-icon-button
(action)="saveEntries()" (action)="saveDictionary.emit(currentEntries)"
icon="red:check" icon="red:check"
text="dictionary-overview.save-changes" text="dictionary-overview.save-changes"
type="primary" type="primary"

View File

@ -1,11 +1,11 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { DictionaryControllerService, DossierTemplateModel } from '@redaction/red-ui-http'; import { DictionaryControllerService } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { debounce } from '@utils/debounce'; import { debounce } from '@utils/debounce';
import { Observable, of } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { TypeValueWrapper } from '@models/file/type-value.wrapper'; import { DossierWrapper } from '@state/model/dossier.wrapper';
import ICodeEditor = monaco.editor.ICodeEditor; import ICodeEditor = monaco.editor.ICodeEditor;
import IDiffEditor = monaco.editor.IDiffEditor; import IDiffEditor = monaco.editor.IDiffEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration; import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
@ -31,7 +31,7 @@ export class DictionaryManagerComponent implements OnChanges {
saveDictionary = new EventEmitter<string[]>(); saveDictionary = new EventEmitter<string[]>();
currentMatch = 0; currentMatch = 0;
searchMatches: FindMatch[] = []; findMatches: FindMatch[] = [];
currentEntries: string[] = []; currentEntries: string[] = [];
editorOptions: IStandaloneEditorConstructionOptions = { editorOptions: IStandaloneEditorConstructionOptions = {
theme: 'vs', theme: 'vs',
@ -43,11 +43,8 @@ export class DictionaryManagerComponent implements OnChanges {
showDiffEditor = false; showDiffEditor = false;
searchText = ''; searchText = '';
selectDossierTemplate = { name: 'dictionary-overview.compare.select-dossier-template' }; selectDossier = { name: 'dictionary-overview.compare.select-dossier' };
selectDictionary = new TypeValueWrapper(null, 'dictionary-overview.compare.select-dictionary'); compare: false;
dossierTemplates: DossierTemplateModel[];
dictionaries: TypeValueWrapper[] = [this.selectDictionary];
form: FormGroup;
private _codeEditor: ICodeEditor; private _codeEditor: ICodeEditor;
private _diffEditor: IDiffEditor; private _diffEditor: IDiffEditor;
@ -60,43 +57,48 @@ export class DictionaryManagerComponent implements OnChanges {
private readonly _formBuilder: FormBuilder private readonly _formBuilder: FormBuilder
) { ) {
this.currentEntries = this.initialEntries; this.currentEntries = this.initialEntries;
this.form = this._formBuilder.group({ }
active: [false],
dossierTemplate: [{ value: this.selectDossierTemplate, disabled: true }],
dictionary: [{ value: this.selectDictionary, disabled: true }]
});
this.form.valueChanges.subscribe((value) => { private _dossier: DossierWrapper = this.selectDossier as DossierWrapper;
this._setFieldStatus('dossierTemplate', value.active);
this._setFieldStatus(
'dictionary',
value.active &&
this.form.get('dossierTemplate').value !== this.selectDossierTemplate
);
this._loadDictionaries();
});
this.dossierTemplates = [ get dossier() {
this.selectDossierTemplate, return this._dossier;
...this._appStateService.dossierTemplates }
];
this.form.controls.dossierTemplate.valueChanges.subscribe(() => { set dossier(dossier: DossierWrapper) {
this._onDossierTemplateChanged(); this._dossier = dossier;
});
this.form.controls.active.valueChanges.subscribe((value) => { if (dossier === this.selectDossier) {
this.showDiffEditor = this.showDiffEditor = false;
value && this.form.get('dictionary').value !== this.selectDictionary; this.diffEditorText = '';
}); return;
}
this.form.controls.dictionary.valueChanges.subscribe((dictionary) => { this._onDossierChanged(dossier)
this._onDictionaryChanged(dictionary).subscribe((data) => { .pipe(take(1))
this.diffEditorText = data; .subscribe(entries => {
this.showDiffEditor = dictionary !== this.selectDictionary; this.diffEditorText = entries;
if (this.showDiffEditor) this._diffEditor?.getOriginalEditor().setValue(data); this.showDiffEditor = true;
if (this.showDiffEditor)
this._diffEditor?.getOriginalEditor().setValue(this.diffEditorText);
}); });
}); }
get dossiers() {
return this._appStateService.allDossiers;
}
get editorValue(): string {
return this.currentEntries.join('\n');
}
set editorValue(text: string) {
this.currentEntries = text.split('\n');
this.codeEditorTextChanged();
}
get hasChanges(): boolean {
return this.currentEntries.toString() !== this.initialEntries.toString();
} }
onDiffEditorInit(editor: IDiffEditor): void { onDiffEditorInit(editor: IDiffEditor): void {
@ -116,14 +118,6 @@ export class DictionaryManagerComponent implements OnChanges {
(window as any).monaco.editor.setTheme('redaction'); (window as any).monaco.editor.setTheme('redaction');
} }
get editorValue(): string {
return this._codeEditor?.getModel()?.getValue();
}
set editorValue(value: string) {
this._codeEditor?.getModel()?.setValue(value);
}
revert() { revert() {
this.currentEntries = this.initialEntries; this.currentEntries = this.initialEntries;
this.searchChanged(''); this.searchChanged('');
@ -132,25 +126,46 @@ export class DictionaryManagerComponent implements OnChanges {
@debounce() @debounce()
searchChanged(text: string) { searchChanged(text: string) {
this.searchText = text.toLowerCase(); this.searchText = text.toLowerCase();
this.searchMatches = this._getMatches(this.searchText); this.findMatches = this._getMatches(this.searchText);
this._applySearchDecorations(); this._applySearchDecorations();
this.currentMatch = 0; this.currentMatch = 0;
this.nextSearchMatch(); 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;
this.currentMatch = this.currentMatch < this.findMatches.length ? this.currentMatch + 1 : 1;
this._scrollToCurrentMatch();
}
previousSearchMatch(): void {
if (this.findMatches.length <= 0) return;
this.currentMatch = this.currentMatch > 1 ? this.currentMatch - 1 : this.findMatches.length;
this._scrollToCurrentMatch();
}
ngOnChanges(): void {
this.revert();
}
private _applySearchDecorations() { private _applySearchDecorations() {
this._searchDecorations = this._codeEditor.deltaDecorations(this._searchDecorations, []); this._searchDecorations = this._codeEditor?.deltaDecorations(this._searchDecorations, []);
const decorations = this.searchMatches.map( const decorations = this.findMatches.map(match => this._getSearchDecoration(match));
(match) =>
({
range: match.range,
options: { inlineClassName: 'search-marker' }
} as IModelDeltaDecoration)
);
this._searchDecorations = this._codeEditor.deltaDecorations( this._searchDecorations = this._codeEditor?.deltaDecorations(
this._searchDecorations, this._searchDecorations,
decorations decorations
); );
@ -161,113 +176,44 @@ export class DictionaryManagerComponent implements OnChanges {
return model?.findMatches(text, false, false, false, null, false) || []; return model?.findMatches(text, false, false, false, null, false) || [];
} }
get codeEditorText(): string {
return this.currentEntries.join('\n');
}
set codeEditorText(text: string) {
this.currentEntries = text.split('\n');
this.codeEditorTextChanged();
}
@debounce()
codeEditorTextChanged() {
const newDecorations = this.currentEntries
.filter((entry) => this._isNew(entry))
.map((entry) => this._makeDecorationFor(entry));
this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations);
}
private _isNew(entry: string): boolean { private _isNew(entry: string): boolean {
return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0; return this.initialEntries.indexOf(entry) < 0 && entry?.trim().length > 0;
} }
private _makeDecorationFor(entry: string): IModelDeltaDecoration { private _getDecoration(entry: string): IModelDeltaDecoration {
const line = this.currentEntries.indexOf(entry) + 1; const line = this.currentEntries.indexOf(entry) + 1;
const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker'; const cssClass = entry.length < MIN_WORD_LENGTH ? 'too-short-marker' : 'changed-row-marker';
const range = new monaco.Range(line, 1, line, 1);
return { return { range: range, options: { isWholeLine: true, className: cssClass } };
range: new monaco.Range(line, 1, line, 1),
options: { isWholeLine: true, className: cssClass }
} as IModelDeltaDecoration;
} }
get hasChanges(): boolean { private _getSearchDecoration(match: FindMatch): IModelDeltaDecoration {
return ( return { range: match.range, options: { inlineClassName: 'search-marker' } };
this.currentEntries.length &&
this.currentEntries.filter((e) => e.trim().length > 0).length <
this.initialEntries.length
);
}
nextSearchMatch(): void {
if (this.searchMatches?.length > 0) {
this.currentMatch =
this.currentMatch < this.searchMatches.length ? this.currentMatch + 1 : 1;
this._scrollToCurrentMatch();
}
}
previousSearchMatch(): void {
if (this.searchMatches.length > 0) {
this.currentMatch =
this.currentMatch > 1 ? this.currentMatch - 1 : this.searchMatches.length;
this._scrollToCurrentMatch();
}
}
private _setFieldStatus(field: 'dossierTemplate' | 'dictionary', enabled: boolean) {
this.form.get(field)[enabled ? 'enable' : 'disable']({ emitEvent: false });
}
private _loadDictionaries() {
const dossierTemplateId = this.form.get('dossierTemplate').value.dossierTemplateId;
if (!dossierTemplateId) {
this.dictionaries = [this.selectDictionary];
return;
}
const appStateDictionaryData = this._appStateService.dictionaryData[dossierTemplateId];
this.dictionaries = [
this.selectDictionary,
...Object.values(appStateDictionaryData).filter(
(d) => !d.virtual || d.type === 'false_positive'
)
];
} }
private _scrollToCurrentMatch(): void { private _scrollToCurrentMatch(): void {
const range = this.searchMatches[this.currentMatch - 1].range; const range = this.findMatches[this.currentMatch - 1].range;
this._codeEditor.setSelection(range); this._codeEditor.setSelection(range);
this._codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL); this._codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL);
} }
private _onDossierTemplateChanged() { private _onDossierChanged({
this._loadDictionaries(); dossierId,
this.form.patchValue({ dictionary: this.selectDictionary }); dossierTemplateId
} }: DossierWrapper): Observable<string> {
const dictionary$ = this._dictionaryControllerService.getDictionaryForType(
dossierTemplateId,
'dossier_redaction',
dossierId
);
private _onDictionaryChanged(dictionary: TypeValueWrapper): Observable<string> { return dictionary$.pipe(map(data => this._toString(data.entries)));
if (dictionary === this.selectDictionary) {
return of('');
}
return this._dictionaryControllerService
.getDictionaryForType(dictionary.dossierTemplateId, dictionary.type)
.pipe(map((data) => this._toString(data.entries)));
} }
private _toString(entries: string[]) { private _toString(entries: string[]) {
return entries const compareFn = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' });
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' })) return entries.sort(compareFn).join('\n');
.join('\n');
}
saveEntries() {
this.saveDictionary.emit(this.currentEntries);
}
ngOnChanges(): void {
this.revert();
} }
} }