working editor

This commit is contained in:
Dan Percic 2021-05-25 10:59:13 +03:00
parent 1db0eb5737
commit 0a457a64e6
4 changed files with 117 additions and 137 deletions

View File

@ -89,7 +89,7 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
if (file) {
fileReader.onload = () => {
this._dictionaryManager.editorValue = fileReader.result;
this._dictionaryManager.editorValue = fileReader.result as string;
this._fileInput.nativeElement.value = null;
};
fileReader.readAsText(file);

View File

@ -16,7 +16,7 @@
</div>
<div *ngIf="searchText.length > 0" class="with-input">
<div class="search-match-text">
{{ currentMatch + '/' + searchPositions.length }}
{{ currentMatch + '/' + searchMatches.length }}
</div>
<mat-icon
(click)="previousSearchMatch()"
@ -64,23 +64,13 @@
</div>
<div class="editor-container">
<!-- <ace-editor-->
<!-- #editorComponent-->
<!-- (textChanged)="textChanged($event)"-->
<!-- [autoUpdateContent]="true"-->
<!-- [mode]="'text'"-->
<!-- [options]="aceOptions"-->
<!-- [readOnly]="!canEdit"-->
<!-- [theme]="'eclipse'"-->
<!-- class="ace-redaction"-->
<!-- >-->
<!-- </ace-editor>-->
<ngx-monaco-editor
*ngIf="!showDiffEditor"
[options]="editorOptions"
[(ngModel)]="codeEditorText"
(init)="onCodeEditorInit($event)"
></ngx-monaco-editor>
<ngx-monaco-diff-editor
*ngIf="showDiffEditor"
[options]="editorOptions"
@ -88,6 +78,7 @@
[modified]="codeEditorText"
(init)="onDiffEditorInit($event)"
></ngx-monaco-diff-editor>
<div
*ngIf="form.get('active').value && form.get('dictionary').value === selectDictionary"
class="no-dictionary-selected"
@ -95,16 +86,6 @@
<mat-icon svgIcon="red:dictionary"></mat-icon>
<span class="heading-l" translate="dictionary-overview.select-dictionary"></span>
</div>
<!-- <ace-editor-->
<!-- #compareEditorComponent-->
<!-- *ngIf=""-->
<!-- [mode]="'text'"-->
<!-- [options]="aceOptions"-->
<!-- [readOnly]="true"-->
<!-- [theme]="'eclipse'"-->
<!-- class="ace-redaction"-->
<!-- >-->
<!-- </ace-editor>-->
</div>
<div

View File

@ -1,26 +1,18 @@
import {
ChangeDetectorRef,
Component,
EventEmitter,
Input,
OnChanges,
Output
} from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { DictionaryControllerService, RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup } from '@angular/forms';
import { AppStateService } from '@state/app-state.service';
import { debounce } from '@utils/debounce';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import ICodeEditor = monaco.editor.ICodeEditor;
import IDiffEditor = monaco.editor.IDiffEditor;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
import FindMatch = monaco.editor.FindMatch;
const MIN_WORD_LENGTH = 2;
interface ISearchPosition {
row: number;
column: number;
length: number;
}
const SMOOTH_SCROLL = 0;
@Component({
selector: 'redaction-dictionary-manager',
@ -37,14 +29,17 @@ export class DictionaryManagerComponent implements OnChanges {
@Output()
saveDictionary = new EventEmitter<string[]>();
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<string> {
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() {

View File

@ -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;
}