Pull request #189: RED-1494
Merge in RED/ui from RED-1494 to master * commit '4c3add97f0a75b78090dc677b597a1691777b6c2': add monaco editor to rule sets working editor monaco editor wip
This commit is contained in:
commit
27ff05ce9f
12
angular.json
12
angular.json
@ -48,20 +48,10 @@
|
||||
"input": "apps/red-ui/src/assets/",
|
||||
"output": "/assets/"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/ace-builds/src-min/",
|
||||
"output": "/assets/ace-builds"
|
||||
},
|
||||
"apps/red-ui/src/manifest.webmanifest"
|
||||
],
|
||||
"styles": ["apps/red-ui/src/styles.scss"],
|
||||
"scripts": [
|
||||
"node_modules/@pdftron/webviewer/webviewer.min.js",
|
||||
"node_modules/ace-builds/src-min/ace.js",
|
||||
"node_modules/ace-builds/src-min/mode-java.js",
|
||||
"node_modules/ace-builds/src-min/theme-eclipse.js"
|
||||
],
|
||||
"scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { APP_INITIALIZER, Inject, NgModule } from '@angular/core';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
@ -20,7 +20,6 @@ import { AuthErrorComponent } from '@components/auth-error/auth-error.component'
|
||||
import { ToastComponent } from '@components/toast/toast.component';
|
||||
import { HttpCacheInterceptor } from '@redaction/red-cache';
|
||||
import { NotificationsComponent } from '@components/notifications/notifications.component';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { DownloadsListScreenComponent } from '@components/downloads-list-screen/downloads-list-screen.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
@ -28,8 +27,7 @@ import { FileUploadDownloadModule } from '@upload-download/file-upload-download.
|
||||
import { UserProfileScreenComponent } from '@components/user-profile/user-profile-screen.component';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
import { BASE_HREF } from './tokens';
|
||||
|
||||
declare let ace;
|
||||
import { MONACO_PATH, MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
|
||||
export function httpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
@ -67,6 +65,7 @@ const components = [
|
||||
AuthModule,
|
||||
ApiModule,
|
||||
AppRoutingModule,
|
||||
MonacoEditorModule,
|
||||
ToastrModule.forRoot({
|
||||
closeButton: true,
|
||||
enableHtml: true,
|
||||
@ -102,20 +101,17 @@ const components = [
|
||||
multi: true,
|
||||
useFactory: languageInitializer,
|
||||
deps: [LanguageService]
|
||||
},
|
||||
{
|
||||
provide: MONACO_PATH,
|
||||
useValue: 'https://unpkg.com/monaco-editor@0.24.0/min/vs'
|
||||
}
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(
|
||||
@Inject(BASE_HREF) private readonly _baseHref: string,
|
||||
private readonly _router: Router,
|
||||
private readonly _route: ActivatedRoute,
|
||||
private readonly _keyCloakService: KeycloakService
|
||||
) {
|
||||
constructor(private readonly _router: Router, private readonly _route: ActivatedRoute) {
|
||||
this._configureKeyCloakRouteHandling();
|
||||
|
||||
ace.config.set('basePath', _baseHref + '/assets/ace-builds/');
|
||||
}
|
||||
|
||||
private _configureKeyCloakRouteHandling() {
|
||||
|
||||
@ -15,7 +15,6 @@ import { UserListingScreenComponent } from './screens/user-listing/user-listing-
|
||||
import { WatermarkScreenComponent } from './screens/watermark/watermark-screen.component';
|
||||
import { AdminBreadcrumbsComponent } from './components/breadcrumbs/admin-breadcrumbs.component';
|
||||
import { RuleSetActionsComponent } from './components/rule-set-actions/rule-set-actions.component';
|
||||
import { AceEditorModule } from 'ng2-ace-editor';
|
||||
import { ColorPickerModule } from 'ngx-color-picker';
|
||||
import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
|
||||
import { AddEditRuleSetDialogComponent } from './dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component';
|
||||
@ -33,6 +32,7 @@ import { ConfirmDeleteUsersDialogComponent } from './dialogs/confirm-delete-user
|
||||
import { FileAttributesCsvImportDialogComponent } from './dialogs/file-attributes-csv-import-dialog/file-attributes-csv-import-dialog.component';
|
||||
import { ActiveFieldsListingComponent } from './dialogs/file-attributes-csv-import-dialog/active-fields-listing/active-fields-listing.component';
|
||||
import { AdminSideNavComponent } from './admin-side-nav/admin-side-nav.component';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
|
||||
const dialogs = [
|
||||
AddEditRuleSetDialogComponent,
|
||||
@ -81,9 +81,9 @@ const components = [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
AdminRoutingModule,
|
||||
AceEditorModule,
|
||||
NgxChartsModule,
|
||||
ColorPickerModule
|
||||
ColorPickerModule,
|
||||
MonacoEditorModule
|
||||
]
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
<redaction-admin-side-nav type="project-templates"></redaction-admin-side-nav>
|
||||
|
||||
<redaction-dictionary-manager
|
||||
[initialDictionaryEntries]="entries"
|
||||
[initialEntries]="entries"
|
||||
[canEdit]="permissionsService.isAdmin()"
|
||||
(saveDictionary)="saveEntries($event)"
|
||||
#dictionaryManager
|
||||
@ -79,7 +79,7 @@
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
{{ dictionaryManager.initialDictionaryEntries?.length }}
|
||||
{{ dictionaryManager.initialEntries?.length }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
|
||||
@ -3,11 +3,9 @@ import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { AdminDialogService } from '../../services/admin-dialog.service';
|
||||
import { DictionaryManagerComponent } from '../../../shared/components/dictionary-manager/dictionary-manager.component';
|
||||
import { DictionarySaveService } from '../../../shared/services/dictionary-save.service';
|
||||
@ -22,20 +20,18 @@ export class DictionaryOverviewScreenComponent extends ComponentHasChanges imple
|
||||
entries: string[] = [];
|
||||
|
||||
@ViewChild('dictionaryManager', { static: false })
|
||||
private _dictionaryManager: DictionaryManagerComponent;
|
||||
@ViewChild('fileInput') private _fileInput: ElementRef;
|
||||
private readonly _dictionaryManager: DictionaryManagerComponent;
|
||||
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _notificationService: NotificationService,
|
||||
protected readonly _translateService: TranslateService,
|
||||
private readonly _dictionarySaveService: DictionarySaveService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
private readonly _dialogService: AdminDialogService,
|
||||
private readonly _router: Router,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _formBuilder: FormBuilder
|
||||
private readonly _appStateService: AppStateService
|
||||
) {
|
||||
super(_translateService);
|
||||
this._appStateService.activateDictionary(
|
||||
@ -93,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);
|
||||
|
||||
@ -20,18 +20,11 @@
|
||||
<redaction-admin-side-nav type="project-templates"></redaction-admin-side-nav>
|
||||
|
||||
<div class="editor-container">
|
||||
<ace-editor
|
||||
#editorComponent
|
||||
(textChanged)="textChanged($event)"
|
||||
[autoUpdateContent]="true"
|
||||
[mode]="'java'"
|
||||
[options]="aceOptions"
|
||||
[readOnly]="!permissionsService.isAdmin()"
|
||||
[text]="rules"
|
||||
[theme]="'eclipse'"
|
||||
class="ace-redaction"
|
||||
>
|
||||
</ace-editor>
|
||||
<ngx-monaco-editor
|
||||
[options]="editorOptions"
|
||||
[(ngModel)]="codeEditorText"
|
||||
(init)="onCodeEditorInit($event)"
|
||||
></ngx-monaco-editor>
|
||||
</div>
|
||||
<div *ngIf="hasChanges && permissionsService.isAdmin()" class="changes-box">
|
||||
<redaction-icon-button
|
||||
|
||||
@ -9,3 +9,8 @@
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
ngx-monaco-editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { AceEditorComponent } from 'ng2-ace-editor';
|
||||
import { RulesControllerService } from '@redaction/red-ui-http';
|
||||
import { NotificationService, NotificationType } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@ -8,8 +7,10 @@ import { saveAs } from 'file-saver';
|
||||
import { ComponentHasChanges } from '@guards/can-deactivate.guard';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AppStateService } from '@state/app-state.service';
|
||||
|
||||
declare let ace;
|
||||
import { debounce } from '../../../../utils/debounce';
|
||||
import ICodeEditor = monaco.editor.ICodeEditor;
|
||||
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
|
||||
import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions;
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-rules-screen',
|
||||
@ -17,21 +18,23 @@ declare let ace;
|
||||
styleUrls: ['./rules-screen.component.scss']
|
||||
})
|
||||
export class RulesScreenComponent extends ComponentHasChanges {
|
||||
aceOptions = { showPrintMargin: false };
|
||||
rules: string;
|
||||
editorOptions: IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs',
|
||||
language: 'java',
|
||||
automaticLayout: true,
|
||||
readOnly: !this.permissionsService.isAdmin()
|
||||
};
|
||||
processing = true;
|
||||
|
||||
initialLines: string[] = [];
|
||||
currentLines: string[] = [];
|
||||
changedLines: number[] = [];
|
||||
activeEditMarkers: any[] = [];
|
||||
|
||||
@ViewChild('editorComponent', { static: true })
|
||||
editorComponent: AceEditorComponent;
|
||||
|
||||
@ViewChild('fileInput')
|
||||
private _fileInput: ElementRef;
|
||||
|
||||
private _codeEditor: ICodeEditor;
|
||||
private _decorations: string[] = [];
|
||||
|
||||
constructor(
|
||||
readonly permissionsService: PermissionsService,
|
||||
private readonly _rulesControllerService: RulesControllerService,
|
||||
@ -45,45 +48,59 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
get hasChanges(): boolean {
|
||||
return this.activeEditMarkers.length > 0;
|
||||
onCodeEditorInit(editor: ICodeEditor) {
|
||||
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');
|
||||
}
|
||||
|
||||
textChanged($event: any) {
|
||||
get hasChanges(): boolean {
|
||||
return this._decorations.length > 0;
|
||||
}
|
||||
|
||||
get codeEditorText() {
|
||||
return this.currentLines.join('\n');
|
||||
}
|
||||
|
||||
set codeEditorText($event: any) {
|
||||
this.currentLines = $event.split('\n');
|
||||
this.changedLines = [];
|
||||
this.activeEditMarkers.forEach((am) => {
|
||||
this.editorComponent.getEditor().getSession().removeMarker(am);
|
||||
});
|
||||
this.activeEditMarkers = [];
|
||||
this.codeEditorTextChanged();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.currentLines.length; i++) {
|
||||
const currentEntry = this.currentLines[i];
|
||||
if (this.initialLines.indexOf(currentEntry) < 0) {
|
||||
this.changedLines.push(i);
|
||||
}
|
||||
}
|
||||
@debounce()
|
||||
codeEditorTextChanged() {
|
||||
const newDecorations = this.currentLines
|
||||
.filter((entry) => this._isNew(entry))
|
||||
.map((entry) => this._makeDecorationFor(entry));
|
||||
|
||||
const range = ace.require('ace/range').Range;
|
||||
for (const i of this.changedLines) {
|
||||
const entry = this.currentLines[i];
|
||||
if (entry?.trim().length > 0) {
|
||||
// only mark non-empty lines
|
||||
this.activeEditMarkers.push(
|
||||
this.editorComponent
|
||||
.getEditor()
|
||||
.getSession()
|
||||
.addMarker(new range(i, 0, i, 1), 'changed-row-marker', 'fullLine')
|
||||
);
|
||||
}
|
||||
}
|
||||
this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations);
|
||||
}
|
||||
|
||||
private _isNew(entry: string): boolean {
|
||||
return this.initialLines.indexOf(entry) < 0 && entry?.trim().length > 0;
|
||||
}
|
||||
|
||||
private _makeDecorationFor(entry: string): IModelDeltaDecoration {
|
||||
const line = this.currentLines.indexOf(entry) + 1;
|
||||
|
||||
return {
|
||||
range: new monaco.Range(line, 1, line, 1),
|
||||
options: { isWholeLine: true, className: 'changed-row-marker' }
|
||||
} as IModelDeltaDecoration;
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
this.processing = true;
|
||||
this._rulesControllerService
|
||||
.uploadRules({
|
||||
rules: this.editorComponent.getEditor().getValue(),
|
||||
rules: this._codeEditor.getModel().getValue(),
|
||||
ruleSetId: this._appStateService.activeRuleSetId
|
||||
})
|
||||
.subscribe(
|
||||
@ -107,14 +124,13 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
}
|
||||
|
||||
revert(): void {
|
||||
this.initialLines = this.rules.split('\n');
|
||||
this.editorComponent.getEditor().setValue(this.rules);
|
||||
this.editorComponent.getEditor().clearSelection();
|
||||
this.currentLines = this.initialLines;
|
||||
this._decorations = this._codeEditor?.deltaDecorations(this._decorations, []) || [];
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
download(): void {
|
||||
const content = this.editorComponent.getEditor().getValue();
|
||||
const content = this._codeEditor.getModel().getValue();
|
||||
const blob = new Blob([content], {
|
||||
type: 'text/plain;charset=utf-8'
|
||||
});
|
||||
@ -127,7 +143,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
|
||||
if (file) {
|
||||
fileReader.onload = () => {
|
||||
this.editorComponent.getEditor().setValue(fileReader.result);
|
||||
this._codeEditor.getModel().setValue(fileReader.result as string);
|
||||
this._fileInput.nativeElement.value = null;
|
||||
};
|
||||
fileReader.readAsText(file);
|
||||
@ -137,12 +153,10 @@ export class RulesScreenComponent extends ComponentHasChanges {
|
||||
private _initialize() {
|
||||
this._rulesControllerService.downloadRules(this._appStateService.activeRuleSetId).subscribe(
|
||||
(rules) => {
|
||||
this.rules = rules.rules;
|
||||
this.currentLines = this.initialLines = rules.rules.split('\n');
|
||||
this.revert();
|
||||
},
|
||||
() => {
|
||||
this.processing = false;
|
||||
}
|
||||
() => (this.processing = false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#dictionaryManager
|
||||
[withFloatingActions]="false"
|
||||
[canEdit]="canEdit"
|
||||
[initialDictionaryEntries]="project.type?.entries"
|
||||
[initialEntries]="project.type?.entries"
|
||||
></redaction-dictionary-manager>
|
||||
</div>
|
||||
|
||||
|
||||
@ -31,8 +31,8 @@ export class DossierDictionaryDialogComponent {
|
||||
saveDossierDictionary() {
|
||||
this._dictionarySaveService
|
||||
.saveEntries(
|
||||
this._dictionaryManager.currentDictionaryEntries,
|
||||
this._dictionaryManager.initialDictionaryEntries,
|
||||
this._dictionaryManager.currentEntries,
|
||||
this._dictionaryManager.initialEntries,
|
||||
this.project.ruleSetId,
|
||||
'dossier_redaction',
|
||||
this.project.projectId
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<redaction-dictionary-manager
|
||||
[canEdit]="canEdit"
|
||||
[initialDictionaryEntries]="projectWrapper.type?.entries || []"
|
||||
[initialEntries]="projectWrapper.type?.entries || []"
|
||||
[withFloatingActions]="false"
|
||||
></redaction-dictionary-manager>
|
||||
|
||||
@ -39,8 +39,8 @@ export class EditProjectDictionaryComponent implements EditProjectSectionInterfa
|
||||
save() {
|
||||
this._dictionarySaveService
|
||||
.saveEntries(
|
||||
this._dictionaryManager.currentDictionaryEntries,
|
||||
this._dictionaryManager.initialDictionaryEntries,
|
||||
this._dictionaryManager.currentEntries,
|
||||
this._dictionaryManager.initialEntries,
|
||||
this.projectWrapper.ruleSetId,
|
||||
'dossier_redaction',
|
||||
this.projectWrapper.projectId,
|
||||
|
||||
@ -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()"
|
||||
@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form [formGroup]="compareForm">
|
||||
<form [formGroup]="form">
|
||||
<div class="red-input-group mr-16">
|
||||
<mat-checkbox color="primary" formControlName="active">
|
||||
{{ 'dictionary-overview.compare.compare' | translate }}
|
||||
@ -64,45 +64,33 @@
|
||||
</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"
|
||||
[original]="diffEditorText"
|
||||
[modified]="codeEditorText"
|
||||
(init)="onDiffEditorInit($event)"
|
||||
></ngx-monaco-diff-editor>
|
||||
|
||||
<div
|
||||
*ngIf="
|
||||
compareForm.get('active').value &&
|
||||
compareForm.get('dictionary').value === selectDictionary
|
||||
"
|
||||
*ngIf="form.get('active').value && form.get('dictionary').value === selectDictionary"
|
||||
class="no-dictionary-selected"
|
||||
>
|
||||
<mat-icon svgIcon="red:dictionary"></mat-icon>
|
||||
<span class="heading-l" translate="dictionary-overview.select-dictionary"></span>
|
||||
</div>
|
||||
<ace-editor
|
||||
#compareEditorComponent
|
||||
*ngIf="
|
||||
compareForm.get('active').value &&
|
||||
compareForm.get('dictionary').value !== selectDictionary
|
||||
"
|
||||
[mode]="'text'"
|
||||
[options]="aceOptions"
|
||||
[readOnly]="true"
|
||||
[theme]="'eclipse'"
|
||||
class="ace-redaction"
|
||||
>
|
||||
</ace-editor>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="withFloatingActions && hasChanges && canEdit"
|
||||
[class.offset]="compareForm.get('active').value"
|
||||
[class.offset]="form.get('active').value"
|
||||
class="changes-box"
|
||||
>
|
||||
<redaction-icon-button
|
||||
|
||||
@ -37,6 +37,12 @@ form {
|
||||
}
|
||||
}
|
||||
|
||||
ngx-monaco-diff-editor,
|
||||
ngx-monaco-editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
height: 100%;
|
||||
|
||||
|
||||
@ -1,205 +1,223 @@
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} 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 { AceEditorComponent } from 'ng2-ace-editor';
|
||||
import { NotificationService } from '@services/notification.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
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;
|
||||
|
||||
declare let ace;
|
||||
const MIN_WORD_LENGTH = 2;
|
||||
const SMOOTH_SCROLL = 0;
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dictionary-manager',
|
||||
templateUrl: './dictionary-manager.component.html',
|
||||
styleUrls: ['./dictionary-manager.component.scss']
|
||||
})
|
||||
export class DictionaryManagerComponent implements OnInit, OnChanges {
|
||||
export class DictionaryManagerComponent implements OnChanges {
|
||||
@Input()
|
||||
withFloatingActions = true;
|
||||
|
||||
@Input()
|
||||
initialDictionaryEntries: string[];
|
||||
|
||||
initialEntries: string[];
|
||||
@Input()
|
||||
canEdit = false;
|
||||
|
||||
@Output()
|
||||
saveDictionary = new EventEmitter<string[]>();
|
||||
|
||||
activeEditMarkers: any[] = [];
|
||||
activeSearchMarkers: any[] = [];
|
||||
searchPositions: any[] = [];
|
||||
currentMatch = 1;
|
||||
currentDictionaryEntries: string[] = [];
|
||||
compareDictionaryEntries: string[] = [];
|
||||
changedLines: number[] = [];
|
||||
aceOptions = { showPrintMargin: false };
|
||||
currentMatch = 0;
|
||||
searchMatches: FindMatch[] = [];
|
||||
currentEntries: string[] = [];
|
||||
editorOptions: IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs',
|
||||
language: 'text/plain',
|
||||
automaticLayout: true,
|
||||
readOnly: !this.canEdit
|
||||
};
|
||||
diffEditorText = '';
|
||||
showDiffEditor = false;
|
||||
searchText = '';
|
||||
|
||||
selectRuleSet = { name: 'dictionary-overview.compare.select-ruleset' };
|
||||
selectDictionary = { label: 'dictionary-overview.compare.select-dictionary' };
|
||||
ruleSets: RuleSetModel[];
|
||||
dictionaries: TypeValue[] = [this.selectDictionary];
|
||||
compareForm: FormGroup;
|
||||
form: FormGroup;
|
||||
|
||||
@ViewChild('editorComponent', { static: true }) private _editorComponent: AceEditorComponent;
|
||||
@ViewChild('compareEditorComponent') private _compareEditorComponent: AceEditorComponent;
|
||||
private _codeEditor: ICodeEditor;
|
||||
private _diffEditor: IDiffEditor;
|
||||
private _decorations: string[] = [];
|
||||
private _searchDecorations: string[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _notificationService: NotificationService,
|
||||
protected readonly _translateService: TranslateService,
|
||||
private readonly _dictionaryControllerService: DictionaryControllerService,
|
||||
private readonly _router: Router,
|
||||
private readonly _activatedRoute: ActivatedRoute,
|
||||
private readonly _appStateService: AppStateService,
|
||||
private readonly _formBuilder: FormBuilder
|
||||
) {
|
||||
this.compareForm = this._formBuilder.group({
|
||||
this.currentEntries = this.initialEntries;
|
||||
this.form = this._formBuilder.group({
|
||||
active: [false],
|
||||
ruleSet: [{ value: this.selectRuleSet, disabled: true }],
|
||||
dictionary: [{ value: this.selectDictionary, disabled: true }]
|
||||
});
|
||||
|
||||
this.compareForm.valueChanges.subscribe((value) => {
|
||||
this.form.valueChanges.subscribe((value) => {
|
||||
this._setFieldStatus('ruleSet', value.active);
|
||||
this._setFieldStatus(
|
||||
'dictionary',
|
||||
value.active && this.compareForm.get('ruleSet').value !== this.selectRuleSet
|
||||
value.active && this.form.get('ruleSet').value !== this.selectRuleSet
|
||||
);
|
||||
this._loadDictionaries();
|
||||
});
|
||||
|
||||
this.ruleSets = [this.selectRuleSet, ...this._appStateService.ruleSets];
|
||||
|
||||
this.compareForm.controls.ruleSet.valueChanges.subscribe(() => {
|
||||
this.form.controls.ruleSet.valueChanges.subscribe(() => {
|
||||
this._onRuleSetChanged();
|
||||
});
|
||||
|
||||
this.compareForm.controls.dictionary.valueChanges.subscribe((dictionary) => {
|
||||
this._onDictionaryChanged(dictionary);
|
||||
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).subscribe((data) => {
|
||||
this.diffEditorText = data;
|
||||
this.showDiffEditor = dictionary !== this.selectDictionary;
|
||||
if (this.showDiffEditor) this._diffEditor?.getOriginalEditor().setValue(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static _setEditorValue(editor: AceEditorComponent, entries: string[]) {
|
||||
const dictionaryEntriesAsText = entries.join('\n');
|
||||
editor.getEditor().setValue(dictionaryEntriesAsText);
|
||||
editor.getEditor().gotoLine(1);
|
||||
onDiffEditorInit(editor: IDiffEditor): void {
|
||||
this._diffEditor = editor;
|
||||
}
|
||||
|
||||
get editorValue() {
|
||||
return this._editorComponent.getEditor().getValue();
|
||||
}
|
||||
|
||||
set editorValue(value: any) {
|
||||
this._editorComponent.getEditor().setValue(value);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._editorComponent.getEditor().selection.on('changeCursor', () => {
|
||||
this._syncActiveLines();
|
||||
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 editorValue(): string {
|
||||
return this._codeEditor?.getModel()?.getValue();
|
||||
}
|
||||
|
||||
set editorValue(value: string) {
|
||||
this._codeEditor?.getModel()?.setValue(value);
|
||||
}
|
||||
|
||||
revert() {
|
||||
DictionaryManagerComponent._setEditorValue(
|
||||
this._editorComponent,
|
||||
this.initialDictionaryEntries
|
||||
);
|
||||
this.currentEntries = this.initialEntries;
|
||||
this.searchChanged('');
|
||||
}
|
||||
|
||||
@debounce()
|
||||
searchChanged(text: string) {
|
||||
this.searchText = text.toLowerCase();
|
||||
this._applySearchMarkers();
|
||||
this.searchMatches = this._getMatches(this.searchText);
|
||||
this._applySearchDecorations();
|
||||
|
||||
this.currentMatch = 0;
|
||||
this.nextSearchMatch();
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
textChanged($event: any) {
|
||||
this._applySearchMarkers();
|
||||
this.currentDictionaryEntries = $event.split('\n');
|
||||
this.changedLines = [];
|
||||
this.activeEditMarkers.forEach((am) => {
|
||||
this._editorComponent.getEditor().getSession().removeMarker(am);
|
||||
});
|
||||
this.activeEditMarkers = [];
|
||||
private _applySearchDecorations() {
|
||||
this._searchDecorations = this._codeEditor.deltaDecorations(this._searchDecorations, []);
|
||||
|
||||
for (let i = 0; i < this.currentDictionaryEntries.length; i++) {
|
||||
const currentEntry = this.currentDictionaryEntries[i];
|
||||
if (this.initialDictionaryEntries.indexOf(currentEntry) < 0) {
|
||||
this.changedLines.push(i);
|
||||
}
|
||||
}
|
||||
const decorations = this.searchMatches.map(
|
||||
(match) =>
|
||||
({
|
||||
range: match.range,
|
||||
options: { inlineClassName: 'search-marker' }
|
||||
} as IModelDeltaDecoration)
|
||||
);
|
||||
|
||||
const range = ace.require('ace/range').Range;
|
||||
for (const i of this.changedLines) {
|
||||
const entry = this.currentDictionaryEntries[i];
|
||||
if (entry?.trim().length > 0) {
|
||||
// only mark non-empty lines
|
||||
this.activeEditMarkers.push(
|
||||
this._editorComponent
|
||||
.getEditor()
|
||||
.getSession()
|
||||
.addMarker(new range(i, 0, i, 1), 'changed-row-marker', 'fullLine')
|
||||
);
|
||||
}
|
||||
if (entry?.trim().length > 0 && entry.trim().length < MIN_WORD_LENGTH) {
|
||||
// show lines that are too short
|
||||
this.activeEditMarkers.push(
|
||||
this._editorComponent
|
||||
.getEditor()
|
||||
.getSession()
|
||||
.addMarker(new range(i, 0, i, 1), 'too-short-marker', 'fullLine')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get hasChanges() {
|
||||
return (
|
||||
this.currentDictionaryEntries.length &&
|
||||
(this.activeEditMarkers.length > 0 ||
|
||||
this.currentDictionaryEntries.filter((e) => e && e.trim().length > 0).length <
|
||||
this.initialDictionaryEntries.length)
|
||||
this._searchDecorations = this._codeEditor.deltaDecorations(
|
||||
this._searchDecorations,
|
||||
decorations
|
||||
);
|
||||
}
|
||||
|
||||
nextSearchMatch() {
|
||||
// length = 3
|
||||
if (this.searchPositions.length > 0) {
|
||||
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.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 {
|
||||
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.currentEntries.filter((e) => e.trim().length > 0).length <
|
||||
this.initialEntries.length
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
private _setFieldStatus(field: 'ruleSet' | 'dictionary', enabled: boolean) {
|
||||
this.compareForm.get(field)[enabled ? 'enable' : 'disable']({ emitEvent: false });
|
||||
this.form.get(field)[enabled ? 'enable' : 'disable']({ emitEvent: false });
|
||||
}
|
||||
|
||||
private _loadDictionaries() {
|
||||
const ruleSetId = this.compareForm.get('ruleSet').value.ruleSetId;
|
||||
const ruleSetId = this.form.get('ruleSet').value.ruleSetId;
|
||||
if (!ruleSetId) {
|
||||
this.dictionaries = [this.selectDictionary];
|
||||
return;
|
||||
@ -207,94 +225,41 @@ export class DictionaryManagerComponent implements OnInit, OnChanges {
|
||||
const appStateDictionaryData = this._appStateService.dictionaryData[ruleSetId];
|
||||
this.dictionaries = [
|
||||
this.selectDictionary,
|
||||
...Object.keys(appStateDictionaryData)
|
||||
.map((key) => appStateDictionaryData[key])
|
||||
.filter((d) => !d.virtual || d.type === 'false_positive')
|
||||
...Object.values(appStateDictionaryData).filter(
|
||||
(d) => !d.virtual || d.type === 'false_positive'
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
private _applySearchMarkers() {
|
||||
this.searchPositions = this._getSearchPositions();
|
||||
this.activeSearchMarkers.forEach((am) => {
|
||||
this._editorComponent.getEditor().getSession().removeMarker(am);
|
||||
});
|
||||
this.activeSearchMarkers = [];
|
||||
private _scrollToCurrentMatch(): void {
|
||||
const range = this.searchMatches[this.currentMatch - 1].range;
|
||||
|
||||
const range = ace.require('ace/range').Range;
|
||||
for (const position of this.searchPositions) {
|
||||
this.activeSearchMarkers.push(
|
||||
this._editorComponent
|
||||
.getEditor()
|
||||
.getSession()
|
||||
.addMarker(
|
||||
new range(
|
||||
position.row,
|
||||
position.column,
|
||||
position.row,
|
||||
position.column + position.length
|
||||
),
|
||||
'search-marker',
|
||||
'text'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _getSearchPositions() {
|
||||
const lowerCaseSearchText = this.searchText.toLowerCase();
|
||||
return this.currentDictionaryEntries
|
||||
.map((val, index) => {
|
||||
const columnIndex = val.toLowerCase().indexOf(lowerCaseSearchText);
|
||||
if (columnIndex >= 0) {
|
||||
return { row: index, column: columnIndex, length: lowerCaseSearchText.length };
|
||||
}
|
||||
})
|
||||
.filter((entry) => !!entry);
|
||||
}
|
||||
|
||||
private _gotoLine() {
|
||||
const position = this.searchPositions[this.currentMatch - 1];
|
||||
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() {
|
||||
this._loadDictionaries();
|
||||
this.compareForm.patchValue({ dictionary: this.selectDictionary });
|
||||
this.form.patchValue({ dictionary: this.selectDictionary });
|
||||
}
|
||||
|
||||
private _onDictionaryChanged(dictionary: TypeValue) {
|
||||
if (dictionary !== this.selectDictionary) {
|
||||
this._dictionaryControllerService
|
||||
.getDictionaryForType(dictionary.ruleSetId, dictionary.type)
|
||||
.subscribe(
|
||||
(data) => {
|
||||
this.compareDictionaryEntries = data.entries.sort((str1, str2) =>
|
||||
str1.localeCompare(str2, undefined, { sensitivity: 'accent' })
|
||||
);
|
||||
DictionaryManagerComponent._setEditorValue(
|
||||
this._compareEditorComponent,
|
||||
this.compareDictionaryEntries
|
||||
);
|
||||
this._syncActiveLines();
|
||||
},
|
||||
() => {}
|
||||
);
|
||||
private _onDictionaryChanged(dictionary: TypeValue): Observable<string> {
|
||||
if (dictionary === this.selectDictionary) {
|
||||
return of('');
|
||||
}
|
||||
return this._dictionaryControllerService
|
||||
.getDictionaryForType(dictionary.ruleSetId, dictionary.type)
|
||||
.pipe(map((data) => this._toString(data.entries)));
|
||||
}
|
||||
|
||||
private _syncActiveLines() {
|
||||
if (this._compareEditorComponent) {
|
||||
this._compareEditorComponent.getEditor().gotoLine(this._activeRow);
|
||||
}
|
||||
}
|
||||
|
||||
private get _activeRow(): number {
|
||||
return this._editorComponent.getEditor().selection.getCursor().row + 1;
|
||||
private _toString(entries: string[]) {
|
||||
return entries
|
||||
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' }))
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
saveEntries() {
|
||||
this.saveDictionary.emit(this.currentDictionaryEntries);
|
||||
this.saveDictionary.emit(this.currentEntries);
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
|
||||
@ -33,8 +33,8 @@ import { MomentDateAdapter } from '@angular/material-moment-adapter';
|
||||
import { SelectComponent } from './components/select/select.component';
|
||||
import { NavigateLastProjectsScreenDirective } from './directives/navigate-last-projects-screen.directive';
|
||||
import { DictionaryManagerComponent } from './components/dictionary-manager/dictionary-manager.component';
|
||||
import { AceEditorModule } from 'ng2-ace-editor';
|
||||
import { SideNavComponent } from '@shared/components/side-nav/side-nav.component';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
|
||||
const buttons = [
|
||||
ChevronButtonComponent,
|
||||
@ -62,6 +62,7 @@ const components = [
|
||||
RoundCheckboxComponent,
|
||||
SelectComponent,
|
||||
SideNavComponent,
|
||||
DictionaryManagerComponent,
|
||||
|
||||
...buttons
|
||||
];
|
||||
@ -83,9 +84,9 @@ const modules = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [...components, ...utils, DictionaryManagerComponent],
|
||||
imports: [CommonModule, ...modules, AceEditorModule],
|
||||
exports: [...modules, ...components, ...utils, DictionaryManagerComponent],
|
||||
declarations: [...components, ...utils],
|
||||
imports: [CommonModule, ...modules, MonacoEditorModule],
|
||||
exports: [...modules, ...components, ...utils],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
|
||||
{
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
ace.define('ace/theme/redaction', [], function (exports, object) {
|
||||
object.cssClass = 'ace-redaction';
|
||||
});
|
||||
@ -2,89 +2,22 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
.ace-redaction {
|
||||
color: $accent;
|
||||
|
||||
.ace_gutter {
|
||||
background: $grey-2;
|
||||
color: $grey-7;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.ace_gutter-cell:after {
|
||||
content: '.';
|
||||
}
|
||||
|
||||
.ace_active-line {
|
||||
background: $grey-6;
|
||||
}
|
||||
|
||||
.ace_print-margin {
|
||||
width: 0;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
.ace_cursor {
|
||||
color: $grey-5;
|
||||
}
|
||||
|
||||
.ace_marker-layer .ace_selection {
|
||||
background: $grey-4;
|
||||
}
|
||||
|
||||
.ace_multiselect .ace_selection.ace_start {
|
||||
box-shadow: 0 0 3px 0 $white;
|
||||
}
|
||||
|
||||
.ace_gutter-active-line {
|
||||
background-color: $grey-6;
|
||||
}
|
||||
|
||||
.ace_marker-layer .ace_selected-word {
|
||||
border: 1px solid $grey-4;
|
||||
}
|
||||
|
||||
.ace_invisible {
|
||||
color: $grey-4;
|
||||
}
|
||||
|
||||
&[ng-reflect-read-only='true'] {
|
||||
background-color: $grey-2;
|
||||
|
||||
*:not(.ace_scrollbar) {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
width: 100%;
|
||||
|
||||
ace-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid $grey-5;
|
||||
border-radius: 8px;
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.changes-box {
|
||||
|
||||
@ -47,6 +47,7 @@
|
||||
"@angular/platform-browser-dynamic": "12.0.0",
|
||||
"@angular/router": "12.0.0",
|
||||
"@angular/service-worker": "12.0.0",
|
||||
"@materia-ui/ngx-monaco-editor": "^5.1.0",
|
||||
"@ngx-translate/core": "^13.0.0",
|
||||
"@ngx-translate/http-loader": "^6.0.0",
|
||||
"@nrwl/angular": "12.3.3",
|
||||
@ -58,7 +59,6 @@
|
||||
"keycloak-js": "13.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.1",
|
||||
"ng2-ace-editor": "^0.3.9",
|
||||
"ngx-color-picker": "^11.0.0",
|
||||
"ngx-toastr": "^13.2.1",
|
||||
"papaparse": "^5.3.0",
|
||||
@ -108,7 +108,7 @@
|
||||
"superagent-promise": "^1.1.0",
|
||||
"ts-jest": "26.5.6",
|
||||
"ts-node": "9.1.1",
|
||||
"webpack": "^4.18.1",
|
||||
"typescript": "4.2.4"
|
||||
"typescript": "4.2.4",
|
||||
"webpack": "^4.18.1"
|
||||
}
|
||||
}
|
||||
|
||||
25
yarn.lock
25
yarn.lock
@ -2379,6 +2379,13 @@
|
||||
merge-source-map "^1.1.0"
|
||||
schema-utils "^2.7.0"
|
||||
|
||||
"@materia-ui/ngx-monaco-editor@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@materia-ui/ngx-monaco-editor/-/ngx-monaco-editor-5.1.0.tgz#76c2f7ec3654f4ad30fa4a5b8439dcf0fa0f47f4"
|
||||
integrity sha512-5k4yJzh1rbygbgwomcTOA63NABr/pYMZZNmtwN/2/eo07ZNxiJY3puNKjJkNN2cuWeZA5Qu7LKDS0E8oYpN/cg==
|
||||
dependencies:
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@ngtools/webpack@12.0.0":
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-12.0.0.tgz#b2f6cc8f727cc9fdf54faac27ce1b4865c471b1c"
|
||||
@ -3347,11 +3354,6 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
|
||||
mime-types "~2.1.24"
|
||||
negotiator "0.6.2"
|
||||
|
||||
ace-builds@^1.4.2:
|
||||
version "1.4.12"
|
||||
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.4.12.tgz#888efa386e36f4345f40b5233fcc4fe4c588fae7"
|
||||
integrity sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==
|
||||
|
||||
acorn-globals@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
|
||||
@ -4071,11 +4073,6 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace@^0.11.1:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58"
|
||||
integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=
|
||||
|
||||
braces@^2.3.1, braces@^2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
|
||||
@ -9556,14 +9553,6 @@ ng-packagr@12.0.0:
|
||||
sass "^1.32.8"
|
||||
stylus "^0.54.8"
|
||||
|
||||
ng2-ace-editor@^0.3.9:
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/ng2-ace-editor/-/ng2-ace-editor-0.3.9.tgz#5ae68712e9ca49591eda31df8fe02eca1c3634e1"
|
||||
integrity sha512-e8Q4YCirlL/OEiekewmzupG+zV3prYsiYmQnRzQzd0wNgsPjOLOdb0it7cCbzFfIXKGyIIHKTW5584WxPr2LnQ==
|
||||
dependencies:
|
||||
ace-builds "^1.4.2"
|
||||
brace "^0.11.1"
|
||||
|
||||
ngx-color-picker@^11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-color-picker/-/ngx-color-picker-11.0.0.tgz#c1e5468505953bc579bf21014a135808820ea753"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user