working editor
This commit is contained in:
parent
1db0eb5737
commit
0a457a64e6
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user