diff --git a/angular.json b/angular.json
index 063e876f9..1e85832de 100644
--- a/angular.json
+++ b/angular.json
@@ -33,10 +33,20 @@
"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"]
+ "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"
+ ]
},
"configurations": {
"production": {
diff --git a/apps/red-ui/src/app/app.component.ts b/apps/red-ui/src/app/app.component.ts
index e8bfc9c8b..23d06136d 100644
--- a/apps/red-ui/src/app/app.component.ts
+++ b/apps/red-ui/src/app/app.component.ts
@@ -1,6 +1,10 @@
import { Component } from '@angular/core';
import { AppLoadStateService } from './utils/app-load-state.service';
+declare var ace;
+
+ace.config.set('basePath', '/assets/ace-builds/');
+
@Component({
selector: 'redaction-root',
templateUrl: './app.component.html',
diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts
index 116926226..b1a4976f2 100644
--- a/apps/red-ui/src/app/app.module.ts
+++ b/apps/red-ui/src/app/app.module.ts
@@ -92,6 +92,7 @@ import { TeamMembersComponent } from './components/team-members/team-members.com
import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component';
import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component';
import { NotificationsComponent } from './components/notifications/notifications.component';
+import { RulesScreenComponent } from './screens/admin/rules-screen/rules-screen.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@@ -167,6 +168,14 @@ const routes = [
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
+ },
+ {
+ path: 'rules',
+ component: RulesScreenComponent,
+ canActivate: [CompositeRouteGuard],
+ data: {
+ routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
+ }
}
]
}
@@ -248,7 +257,8 @@ const matImports = [
TeamMembersComponent,
AdminBreadcrumbsComponent,
UserListingScreenComponent,
- NotificationsComponent
+ NotificationsComponent,
+ RulesScreenComponent
],
imports: [
BrowserModule,
diff --git a/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html b/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html
index fb513b57a..fbdb934f0 100644
--- a/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html
+++ b/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.html
@@ -8,6 +8,15 @@
*ngIf="screen === 'dictionaries' || root"
>
+
+
diff --git a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.scss b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.scss
index 7e7d258be..f7d9ce007 100644
--- a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.scss
+++ b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.scss
@@ -3,19 +3,16 @@
.editor-container {
height: calc(100% - 50px);
- width: 100%;
+}
- ace-editor {
- width: 100%;
- height: 100%;
- }
+.changes-box {
+ right: 403px;
}
.left-container {
- width: calc(100vw - 383px);
- height: calc(100vh - 141px);
+ width: calc(100vw - 353px);
padding: 15px;
- box-shadow: inset 0 4px 3px -2px #e2e4e9;
+ @include inset-shadow;
}
.right-container {
@@ -24,28 +21,6 @@
padding: 24px;
}
-.changes-box {
- position: absolute;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- bottom: 40px;
- right: 403px;
- border-radius: 8px;
- padding: 16px;
- background-color: $white;
- box-shadow: 0 2px 6px 0 rgba(40, 50, 65, 0.3);
- z-index: 5000;
-}
-
-ace-editor {
- box-sizing: border-box;
- border: 1px solid $grey-5;
- border-radius: 8px;
- background-color: $white;
-}
-
.dictionary-header {
display: flex;
align-items: center;
diff --git a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts
index 343bcd8d8..0ab7998f2 100644
--- a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts
@@ -51,7 +51,6 @@ export class DictionaryOverviewScreenComponent {
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService
) {
- ace.config.set('basePath', '/assets/ace-editor/');
this._activatedRoute.params.subscribe((params) => {
this.dictionary = this._appStateService.dictionaryData[params.type];
if (!this.dictionary) {
diff --git a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.html b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.html
new file mode 100644
index 000000000..c9f25d3b3
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.html
@@ -0,0 +1,49 @@
+
+
+
diff --git a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.scss b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.scss
new file mode 100644
index 000000000..5a2233d9b
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.scss
@@ -0,0 +1,11 @@
+@import '../../../../assets/styles/red-mixins';
+
+.editor-container {
+ width: 100%;
+ padding: 15px;
+ @include inset-shadow;
+}
+
+.changes-box {
+ right: 40px;
+}
diff --git a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts
new file mode 100644
index 000000000..a0070adf5
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts
@@ -0,0 +1,98 @@
+import { Component, ViewChild } from '@angular/core';
+import { PermissionsService } from '../../../common/service/permissions.service';
+import { AceEditorComponent } from 'ng2-ace-editor';
+import { RulesControllerService } from '@redaction/red-ui-http';
+import { NotificationService, NotificationType } from '../../../notification/notification.service';
+import { TranslateService } from '@ngx-translate/core';
+
+declare var ace;
+
+@Component({
+ selector: 'redaction-rules-screen',
+ templateUrl: './rules-screen.component.html',
+ styleUrls: ['./rules-screen.component.scss']
+})
+export class RulesScreenComponent {
+ public aceOptions = { showPrintMargin: false };
+ public rules: string;
+ public processing = true;
+
+ public initialLines: string[] = [];
+ public currentLines: string[] = [];
+ public changedLines: number[] = [];
+ public activeEditMarkers: any[] = [];
+
+ @ViewChild('editorComponent', { static: true })
+ editorComponent: AceEditorComponent;
+
+ constructor(
+ public readonly permissionsService: PermissionsService,
+ private readonly _rulesControllerService: RulesControllerService,
+ private readonly _notificationService: NotificationService,
+ private readonly _translateService: TranslateService
+ ) {
+ this._initialize();
+ }
+
+ private _initialize() {
+ this._rulesControllerService.downloadRules().subscribe(
+ (rules) => {
+ this.rules = rules.rules;
+ this.revert();
+ },
+ () => {
+ this.processing = false;
+ }
+ );
+ }
+
+ public textChanged($event: any) {
+ this.currentLines = $event.split('\n');
+ this.changedLines = [];
+ this.activeEditMarkers.forEach((am) => {
+ this.editorComponent.getEditor().getSession().removeMarker(am);
+ });
+ this.activeEditMarkers = [];
+
+ for (let i = 0; i < this.currentLines.length; i++) {
+ const currentEntry = this.currentLines[i];
+ if (this.initialLines.indexOf(currentEntry) < 0) {
+ this.changedLines.push(i);
+ }
+ }
+
+ 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'));
+ }
+ }
+ }
+
+ public get hasChanges(): boolean {
+ return this.activeEditMarkers.length > 0;
+ }
+
+ public async save(): Promise {
+ this.processing = true;
+ this._rulesControllerService.uploadRules({ rules: this.editorComponent.getEditor().getValue() }).subscribe(
+ () => {
+ this._initialize();
+ this._notificationService.showToastNotification(this._translateService.instant('rules-screen.success.generic'), null, NotificationType.SUCCESS);
+ },
+ () => {
+ this.processing = false;
+ this._notificationService.showToastNotification(this._translateService.instant('rules-screen.error.generic'), null, NotificationType.ERROR);
+ }
+ );
+ }
+
+ public revert(): void {
+ this.initialLines = this.rules.split('\n');
+ this.editorComponent.getEditor().setValue(this.rules);
+ this.editorComponent.getEditor().clearSelection();
+ this.processing = false;
+ }
+}
diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json
index 033bb2dcb..c6eb0f74f 100644
--- a/apps/red-ui/src/assets/i18n/en.json
+++ b/apps/red-ui/src/assets/i18n/en.json
@@ -546,6 +546,14 @@
},
"search": "Search..."
},
+ "rules-screen": {
+ "error": {
+ "generic": "Something went wrong... Rules update failed!"
+ },
+ "success": {
+ "generic": "Rules updated!"
+ }
+ },
"dictionaries": "Dictionaries",
"user-management": "User Management",
"notifications": {
@@ -554,5 +562,6 @@
"tomorrow": "Tomorrow",
"mark-read": "Mark as read",
"mark-unread": "Mark as unread"
- }
+ },
+ "rule-editor": "Rule Editor"
}
diff --git a/apps/red-ui/src/assets/styles/red-editor.scss b/apps/red-ui/src/assets/styles/red-editor.scss
index e5af43737..49e912190 100644
--- a/apps/red-ui/src/assets/styles/red-editor.scss
+++ b/apps/red-ui/src/assets/styles/red-editor.scss
@@ -20,12 +20,16 @@
}
.ace-redaction {
- background-color: $white;
color: $accent;
.ace_gutter {
background: $grey-2;
color: $grey-7;
+ border-right: none;
+ }
+
+ .ace_active-line {
+ background: $grey-6;
}
.ace_print-margin {
@@ -57,3 +61,30 @@
color: $grey-4;
}
}
+
+.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 {
+ position: absolute;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ bottom: 40px;
+ border-radius: 8px;
+ padding: 16px;
+ background-color: $white;
+ box-shadow: 0 2px 6px 0 rgba(40, 50, 65, 0.3);
+ z-index: 5000;
+}