Pull request #60: Rules editor

Merge in RED/ui from rules to master

* commit 'f1cd540de3e4698a80c612b050b8340892b2c0e3':
  Editor theme
  Rules editor
This commit is contained in:
Timo Bejan 2020-12-09 07:44:35 +01:00
commit 7bcb8c04e5
12 changed files with 242 additions and 36 deletions

View File

@ -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": {

View File

@ -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',

View File

@ -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,

View File

@ -8,6 +8,15 @@
*ngIf="screen === 'dictionaries' || root"
></a>
<a
class="ml-32 breadcrumb"
[routerLink]="'/ui/admin/rules'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="rule-editor"
*ngIf="screen === 'rules' || root"
></a>
<a
class="ml-32 breadcrumb"
[routerLink]="'/ui/admin/users'"

View File

@ -61,12 +61,13 @@
<ace-editor
#editorComponent
[mode]="'text'"
[theme]="'redaction'"
[theme]="'eclipse'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
(textChanged)="textChanged($event)"
[autoUpdateContent]="true"
[text]="dictionaryEntriesAsText"
class="ace-redaction"
>
</ace-editor>
</div>

View File

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

View File

@ -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) {

View File

@ -0,0 +1,49 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs></redaction-admin-breadcrumbs>
<div class="actions">
<redaction-circle-button
class="ml-6"
*ngIf="permissionsService.isUser()"
[routerLink]="['/ui/projects/']"
tooltip="common.close"
tooltipPosition="before"
icon="red:close"
></redaction-circle-button>
</div>
</div>
<div class="red-content-inner">
<div class="editor-container">
<ace-editor
#editorComponent
[mode]="'java'"
[theme]="'eclipse'"
[options]="aceOptions"
[readOnly]="!permissionsService.isAdmin()"
(textChanged)="textChanged($event)"
[autoUpdateContent]="true"
[text]="rules"
class="ace-redaction"
>
</ace-editor>
</div>
<div class="changes-box" *ngIf="hasChanges">
<redaction-icon-button
*ngIf="permissionsService.isAdmin()"
icon="red:check"
(action)="save()"
text="dictionary-overview.save-changes"
[primary]="true"
></redaction-icon-button>
<redaction-icon-button
*ngIf="permissionsService.isAdmin()"
(action)="revert()"
text="dictionary-overview.revert-changes"
[linkButton]="true"
></redaction-icon-button>
</div>
</div>
</section>
<redaction-full-page-loading-indicator [displayed]="processing"></redaction-full-page-loading-indicator>

View File

@ -0,0 +1,11 @@
@import '../../../../assets/styles/red-mixins';
.editor-container {
width: 100%;
padding: 15px;
@include inset-shadow;
}
.changes-box {
right: 40px;
}

View File

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

View File

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

View File

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