Pull request #83: Rule sets

Merge in RED/ui from rule-sets to master

* commit '47700a817046b21a5f5ca72f40c8f14df69bb7da':
  fixed ruleset issues
  Add/edit rule sets WIP
  Display rule sets from API
This commit is contained in:
Timo Bejan 2021-01-06 20:24:22 +01:00
commit 10186d1d5f
33 changed files with 319 additions and 329 deletions

View File

@ -98,10 +98,10 @@ import { HtmlDebugScreenComponent } from './screens/html-debug-screen/html-debug
import { ReportDownloadBtnComponent } from './components/buttons/report-download-btn/report-download-btn.component';
import { ProjectListingActionsComponent } from './screens/project-listing-screen/project-listing-actions/project-listing-actions.component';
import { HasScrollbarDirective } from './utils/has-scrollbar.directive';
import { ProjectTemplatesListingScreenComponent } from './screens/admin/project-templates-listing-screen/project-templates-listing-screen.component';
import { AddEditProjectTemplateDialogComponent } from './screens/admin/project-templates-listing-screen/add-edit-project-template-dialog/add-edit-project-template-dialog.component';
import { ProjectTemplateActionsComponent } from './components/project-template-actions/project-template-actions.component';
import { ProjectTemplateViewSwitchComponent } from './components/project-template-view-switch/project-template-view-switch.component';
import { RuleSetsListingScreenComponent } from './screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component';
import { AddEditRuleSetDialogComponent } from './screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component';
import { RuleSetActionsComponent } from './components/rule-set-actions/rule-set-actions.component';
import { RuleSetViewSwitchComponent } from './components/rule-set-view-switch/rule-set-view-switch.component';
import { MatSliderModule } from '@angular/material/slider';
import { PendingChangesGuard } from './utils/can-deactivate.guard';
@ -178,14 +178,14 @@ const routes = [
children: [
{
path: '',
component: ProjectTemplatesListingScreenComponent,
component: RuleSetsListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: ':templateId',
path: ':ruleSetId',
children: [
{
path: 'dictionaries',
@ -313,8 +313,8 @@ const matImports = [
CircleButtonComponent,
ChevronButtonComponent,
DictionaryListingScreenComponent,
ProjectTemplatesListingScreenComponent,
AddEditProjectTemplateDialogComponent,
RuleSetsListingScreenComponent,
AddEditRuleSetDialogComponent,
SyncWidthDirective,
HasScrollbarDirective,
AddEditDictionaryDialogComponent,
@ -329,8 +329,8 @@ const matImports = [
HtmlDebugScreenComponent,
ReportDownloadBtnComponent,
ProjectListingActionsComponent,
ProjectTemplateActionsComponent,
ProjectTemplateViewSwitchComponent
RuleSetActionsComponent,
RuleSetViewSwitchComponent
],
imports: [
BrowserModule,

View File

@ -5,7 +5,7 @@
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="project-templates"
*ngIf="root || !!projectTemplate"
*ngIf="root || !!ruleSet"
></a>
<a
@ -26,10 +26,10 @@
*ngIf="root && permissionService.isAdmin()"
></a>
<ng-container *ngIf="projectTemplate">
<ng-container *ngIf="ruleSet">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a class="breadcrumb ml-0" [routerLink]="'/ui/admin/project-templates/' + projectTemplate.id" [class.active]="!dictionary">
{{ projectTemplate.name }}
<a class="breadcrumb ml-0" [routerLink]="'/ui/admin/project-templates/' + ruleSet.ruleSetId" [class.active]="!dictionary">
{{ ruleSet.name }}
</a>
</ng-container>
@ -37,7 +37,7 @@
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a
class="breadcrumb ml-0"
[routerLink]="'/ui/admin/project-templates/' + projectTemplate.id + '/dictionaries/' + dictionary.type"
[routerLink]="'/ui/admin/project-templates/' + ruleSet.ruleSetId + '/dictionaries/' + dictionary.type"
routerLinkActive="active"
>
{{ dictionary.label }}

View File

@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TypeValue } from '@redaction/red-ui-http';
import { AppStateService, ProjectTemplate } from '../../state/app-state.service';
import { RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { AppStateService } from '../../state/app-state.service';
import { UserPreferenceService } from '../../common/service/user-preference.service';
import { PermissionsService } from '../../common/service/permissions.service';
@ -12,7 +12,7 @@ import { PermissionsService } from '../../common/service/permissions.service';
})
export class AdminBreadcrumbsComponent implements OnInit {
public dictionary: TypeValue;
public projectTemplate: ProjectTemplate;
public ruleSet: RuleSetModel;
@Input()
public root = false;
@ -25,8 +25,8 @@ export class AdminBreadcrumbsComponent implements OnInit {
ngOnInit(): void {
this._activatedRoute.params.subscribe((params) => {
if (params.templateId) {
this.projectTemplate = this._appStateService.getProjectTemplateById(params.templateId);
if (params.ruleSetId) {
this.ruleSet = this._appStateService.getRuleSetById(params.ruleSetId);
}
if (params.type) {
this.dictionary = this._appStateService.getDictionaryTypeValue(params.type);

View File

@ -1,37 +0,0 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ProjectTemplate } from '../../state/app-state.service';
import { DialogService } from '../../dialogs/dialog.service';
import { PermissionsService } from '../../common/service/permissions.service';
@Component({
selector: 'redaction-project-template-actions',
templateUrl: './project-template-actions.component.html',
styleUrls: ['./project-template-actions.component.scss']
})
export class ProjectTemplateActionsComponent implements OnInit {
@Input() template: ProjectTemplate;
@Output() loadTemplatesData = new EventEmitter<any>();
constructor(private readonly _dialogService: DialogService, public readonly permissionsService: PermissionsService) {}
ngOnInit(): void {}
openAddEditTemplateDialog(template?: ProjectTemplate) {
this._dialogService.openAddEditTemplateDialog(template, async (newTemplate) => {
if (newTemplate) {
this.loadTemplatesData.emit();
}
});
}
openEditTemplateDialog($event: any, pt: ProjectTemplate) {
$event.stopPropagation();
this.openAddEditTemplateDialog(pt);
}
openDeleteTemplateDialog($event: any, template: ProjectTemplate) {
this._dialogService.openDeleteProjectTemplateDialog($event, template, async () => {
this.loadTemplatesData.emit();
});
}
}

View File

@ -1,6 +1,6 @@
<div class="action-buttons">
<redaction-circle-button
(action)="openDeleteTemplateDialog($event, template)"
(action)="openDeleteRuleSetDialog($event, ruleSet)"
*ngIf="permissionsService.isAdmin()"
tooltip="project-templates-listing.action.delete"
type="dark-bg"
@ -9,7 +9,7 @@
</redaction-circle-button>
<redaction-circle-button
(action)="openEditTemplateDialog($event, template)"
(action)="openEditRuleSetDialog($event, ruleSet)"
*ngIf="permissionsService.isAdmin()"
tooltip="project-templates-listing.action.edit"
type="dark-bg"

View File

@ -0,0 +1,33 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { DialogService } from '../../dialogs/dialog.service';
import { PermissionsService } from '../../common/service/permissions.service';
import { RuleSetModel } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-rule-set-actions',
templateUrl: './rule-set-actions.component.html',
styleUrls: ['./rule-set-actions.component.scss']
})
export class RuleSetActionsComponent implements OnInit {
@Input() ruleSet: RuleSetModel;
@Output() loadRuleSetsData = new EventEmitter<any>();
constructor(private readonly _dialogService: DialogService, public readonly permissionsService: PermissionsService) {}
ngOnInit(): void {}
openEditRuleSetDialog($event: any, ruleSet: RuleSetModel) {
$event.stopPropagation();
this._dialogService.openAddEditRuleSetDialog(ruleSet, async (newRuleSet) => {
if (newRuleSet) {
this.loadRuleSetsData.emit();
}
});
}
openDeleteRuleSetDialog($event: any, ruleSet: RuleSetModel) {
this._dialogService.openDeleteRuleSetDialog($event, ruleSet, async () => {
this.loadRuleSetsData.emit();
});
}
}

View File

@ -2,26 +2,26 @@ import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'redaction-project-template-view-switch',
templateUrl: './project-template-view-switch.component.html',
styleUrls: ['./project-template-view-switch.component.scss']
selector: 'redaction-rule-set-view-switch',
templateUrl: './rule-set-view-switch.component.html',
styleUrls: ['./rule-set-view-switch.component.scss']
})
export class ProjectTemplateViewSwitchComponent implements OnInit {
export class RuleSetViewSwitchComponent implements OnInit {
@Input() public screen: 'rules' | 'dictionaries';
private readonly _projectTemplateId: string;
private readonly _ruleSetId: string;
constructor(private readonly _actr: ActivatedRoute, private _router: Router) {
this._projectTemplateId = this._actr.snapshot.params.templateId;
this._ruleSetId = this._actr.snapshot.params.ruleSetId;
}
ngOnInit(): void {}
public switchView($event) {
if ($event.value === 'dictionaries') {
this._router.navigate(['ui/admin/project-templates/' + this._projectTemplateId + '/dictionaries']);
this._router.navigate(['ui/admin/project-templates/' + this._ruleSetId + '/dictionaries']);
} else {
this._router.navigate(['ui/admin/project-templates/' + this._projectTemplateId + '/rules']);
this._router.navigate(['ui/admin/project-templates/' + this._ruleSetId + '/rules']);
}
}
}

View File

@ -19,9 +19,9 @@
<div class="red-input-group required w-400">
<mat-form-field floatLabel="always">
<mat-label>{{ 'project-listing.add-edit-dialog.form.template' | translate }}</mat-label>
<mat-select value="EFSA 1 (Vertebrate Authors)" style="width: 100%;" [disabled]="project?.hasFiles">
<mat-option *ngFor="let type of ['EFSA 1 (Vertebrate Authors)']" [value]="type">
{{ type }}
<mat-select formControlName="ruleSet" style="width: 100%;">
<mat-option *ngFor="let ruleSet of ruleSets" [value]="ruleSet.ruleSetId">
{{ ruleSet.name }}
</mat-option>
</mat-select>
</mat-form-field>

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Project } from '@redaction/red-ui-http';
import { Project, RuleSetModel } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AppStateService } from '../../state/app-state.service';
import { ProjectWrapper } from '../../state/model/project.wrapper';
@ -23,12 +23,17 @@ export class AddEditProjectDialogComponent {
) {
this.projectForm = this._formBuilder.group({
projectName: [this.project?.projectName, Validators.required],
ruleSet: [{ value: this.project?.ruleSetId, disabled: this.project?.hasFiles }, Validators.required],
description: [this.project?.description],
dueDate: [this.project?.dueDate]
});
this.hasDueDate = !!this.project?.dueDate;
}
public get ruleSets(): RuleSetModel[] {
return this._appStateService.ruleSets;
}
public get changed() {
if (!this.project) {
return true;
@ -77,7 +82,8 @@ export class AddEditProjectDialogComponent {
return {
projectName: this.projectForm.get('projectName').value,
description: this.projectForm.get('description').value,
dueDate: this.hasDueDate ? this.projectForm.get('dueDate').value : undefined
dueDate: this.hasDueDate ? this.projectForm.get('dueDate').value : undefined,
ruleSetId: this.projectForm.get('ruleSet').value
};
}

View File

@ -1,10 +1,17 @@
import { Injectable } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { DictionaryControllerService, FileManagementControllerService, FileStatus, ManualRedactionControllerService, TypeValue } from '@redaction/red-ui-http';
import {
DictionaryControllerService,
FileManagementControllerService,
FileStatus,
ManualRedactionControllerService,
RuleSetModel,
TypeValue
} from '@redaction/red-ui-http';
import { ConfirmationDialogComponent, ConfirmationDialogInput } from './confirmation-dialog/confirmation-dialog.component';
import { NotificationService, NotificationType } from '../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { AppStateService, ProjectTemplate } from '../state/app-state.service';
import { AppStateService } from '../state/app-state.service';
import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edit-project-dialog.component';
import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-dialog.component';
import { ManualRedactionEntryWrapper } from '../screens/file/model/manual-redaction-entry.wrapper';
@ -13,7 +20,7 @@ import { ManualAnnotationDialogComponent } from './manual-redaction-dialog/manua
import { ManualAnnotationService } from '../screens/file/service/manual-annotation.service';
import { ProjectWrapper } from '../state/model/project.wrapper';
import { AddEditDictionaryDialogComponent } from '../screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component';
import { AddEditProjectTemplateDialogComponent } from '../screens/admin/project-templates-listing-screen/add-edit-project-template-dialog/add-edit-project-template-dialog.component';
import { AddEditRuleSetDialogComponent } from '../screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component';
import { DEFAULT_RUL_SET_UUID } from '../utils/rule-set-default';
const dialogConfig = {
@ -175,7 +182,7 @@ export class DialogService {
return ref;
}
public openDeleteProjectTemplateDialog($event: MouseEvent, projectTemplate: ProjectTemplate, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
public openDeleteRuleSetDialog($event: MouseEvent, ruleSet: RuleSetModel, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async (result) => {
@ -285,10 +292,10 @@ export class DialogService {
return ref;
}
public openAddEditTemplateDialog(template: ProjectTemplate, cb?: Function): MatDialogRef<AddEditProjectTemplateDialogComponent> {
const ref = this._dialog.open(AddEditProjectTemplateDialogComponent, {
public openAddEditRuleSetDialog(ruleSet: RuleSetModel, cb?: Function): MatDialogRef<AddEditRuleSetDialogComponent> {
const ref = this._dialog.open(AddEditRuleSetDialogComponent, {
...dialogConfig,
data: template,
data: ruleSet,
autoFocus: true
});

View File

@ -63,7 +63,8 @@ export class AddEditDictionaryDialogComponent {
observable = this._dictionaryControllerService.updateType(typeValue, typeValue.type, DEFAULT_RUL_SET_UUID);
} else {
// create mode
observable = this._dictionaryControllerService.addType(typeValue, DEFAULT_RUL_SET_UUID);
typeValue.ruleSetId = DEFAULT_RUL_SET_UUID;
observable = this._dictionaryControllerService.addType(typeValue);
}
//

View File

@ -2,11 +2,10 @@
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<redaction-project-template-view-switch [screen]="'dictionaries'"></redaction-project-template-view-switch>
<redaction-rule-set-view-switch [screen]="'dictionaries'"></redaction-rule-set-view-switch>
<div class="flex-1 actions">
<redaction-project-template-actions (loadTemplatesData)="loadProjectTemplatesData()" [template]="projectTemplate">
</redaction-project-template-actions>
<redaction-rule-set-actions (loadRuleSetsData)="loadRuleSetsData()" [ruleSet]="ruleSet"> </redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
</div>

View File

@ -1,15 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/simple-doughnut-chart.component';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { DictionaryControllerService, RuleSetModel, TypeValue } from '@redaction/red-ui-http';
import { SortingOption, SortingService } from '../../../utils/sorting.service';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService, ProjectTemplate } from '../../../state/app-state.service';
import { AppStateService } from '../../../state/app-state.service';
import { tap } from 'rxjs/operators';
import { forkJoin } from 'rxjs';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { UserPreferenceService } from '../../../common/service/user-preference.service';
import { DEFAULT_RUL_SET_UUID } from '../../../utils/rule-set-default';
import { ActivatedRoute } from '@angular/router';
@ -24,7 +23,7 @@ export class DictionaryListingScreenComponent implements OnInit {
public displayedDictionaries: TypeValue[];
public selectedDictKeys: string[] = [];
public searchForm: FormGroup;
public projectTemplate: ProjectTemplate;
public ruleSet: RuleSetModel;
constructor(
private readonly _dialogService: DialogService,
@ -41,7 +40,7 @@ export class DictionaryListingScreenComponent implements OnInit {
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
this.projectTemplate = this._appStateService.getProjectTemplateById(this._actr.snapshot.params.templateId);
this.ruleSet = this._appStateService.getRuleSetById(this._actr.snapshot.params.ruleSetId);
}
ngOnInit(): void {
@ -147,7 +146,7 @@ export class DictionaryListingScreenComponent implements OnInit {
});
}
public loadProjectTemplatesData(): void {
console.log('load project templates data');
public loadRuleSetsData(): void {
console.log('load rule sets data');
}
}

View File

@ -1,55 +0,0 @@
import { Component, Inject } from '@angular/core';
import { AppStateService, ProjectTemplate } from '../../../../state/app-state.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as moment from 'moment';
@Component({
selector: 'redaction-add-edit-project-template-dialog',
templateUrl: './add-edit-project-template-dialog.component.html',
styleUrls: ['./add-edit-project-template-dialog.component.scss']
})
export class AddEditProjectTemplateDialogComponent {
public templateForm: FormGroup;
public hasValidFrom: boolean;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
public dialogRef: MatDialogRef<AddEditProjectTemplateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public template: ProjectTemplate
) {
this.templateForm = this._formBuilder.group({
name: [this.template?.name, Validators.required],
description: [this.template?.description],
validFrom: [this.template?.validFrom],
validTo: [this.template?.validTo]
});
this.hasValidFrom = !!this.template?.validFrom && !!this.template?.validTo;
}
public get changed(): boolean {
if (!this.template) return true;
for (const key of Object.keys(this.templateForm.getRawValue())) {
if (key === 'validFrom' || key === 'validTo') {
if (this.hasValidFrom !== (!!this.template.validFrom && !!this.template.validTo)) {
return true;
}
if (this.hasValidFrom && !moment(this.template[key]).isSame(moment(this.templateForm.get(key).value))) {
return true;
}
} else if (this.template[key] !== this.templateForm.get(key).value) {
return true;
}
}
return false;
}
async saveTemplate() {
const template = this.templateForm.getRawValue();
console.log({ template });
this.dialogRef.close({ template });
}
}

View File

@ -1,94 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { SortingOption, SortingService } from '../../../utils/sorting.service';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService, ProjectTemplate } from '../../../state/app-state.service';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
@Component({
selector: 'redaction-project-templates-listing-screen',
templateUrl: './project-templates-listing-screen.component.html',
styleUrls: ['./project-templates-listing-screen.component.scss']
})
export class ProjectTemplatesListingScreenComponent implements OnInit {
public templates: ProjectTemplate[];
public displayedTemplates: ProjectTemplate[];
public selectedTemplateIds: string[] = [];
public searchForm: FormGroup;
constructor(
private readonly _dialogService: DialogService,
private readonly _sortingService: SortingService,
private readonly _formBuilder: FormBuilder,
private readonly _appStateService: AppStateService,
public readonly permissionsService: PermissionsService
) {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
}
ngOnInit(): void {
this.loadTemplatesData();
}
@debounce(200)
private _executeSearch(value: { query: string }) {
this.displayedTemplates = this.templates.filter((pt) => pt.name.toLowerCase().includes(value.query.toLowerCase()));
}
public loadTemplatesData() {
this._appStateService.reset();
this.templates = this._appStateService.projectTemplates;
this.displayedTemplates = [...this.templates];
}
public get sortingOption(): SortingOption {
return this._sortingService.getSortingOption('project-templates-listing');
}
public toggleSort($event) {
this._sortingService.toggleSort('project-templates-listing', $event);
}
toggleTemplateSelected($event: MouseEvent, template: ProjectTemplate) {
$event.stopPropagation();
const idx = this.selectedTemplateIds.indexOf(template.id);
if (idx === -1) {
this.selectedTemplateIds.push(template.id);
} else {
this.selectedTemplateIds.splice(idx, 1);
}
}
public toggleSelectAll() {
if (this.areSomeTemplatesSelected) {
this.selectedTemplateIds = [];
} else {
this.selectedTemplateIds = this.displayedTemplates.map((pt) => pt.id);
}
}
public get areAllTemplatesSelected() {
return this.displayedTemplates.length !== 0 && this.selectedTemplateIds.length === this.displayedTemplates.length;
}
public get areSomeTemplatesSelected() {
return this.selectedTemplateIds.length > 0;
}
public isTemplateSelected(pt: ProjectTemplate) {
return this.selectedTemplateIds.indexOf(pt.id) !== -1;
}
openAddTemplateDialog() {
this._dialogService.openAddEditTemplateDialog(null, async (newTemplate) => {
if (newTemplate) {
this.loadTemplatesData();
}
});
}
}

View File

@ -1,9 +1,9 @@
<section class="dialog">
<div class="dialog-header heading-l">
{{ (template ? 'add-edit-project-template.title.edit' : 'add-edit-project-template.title.new') | translate: { name: template?.name } }}
{{ (ruleSet ? 'add-edit-project-template.title.edit' : 'add-edit-project-template.title.new') | translate: { name: ruleSet?.name } }}
</div>
<form (submit)="saveTemplate()" [formGroup]="templateForm">
<form (submit)="saveRuleSet()" [formGroup]="ruleSetForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="add-edit-project-template.form.name"></label>
@ -49,7 +49,7 @@
</div>
<div class="dialog-actions">
<button [disabled]="templateForm.invalid || !changed" color="primary" mat-flat-button type="submit">
<button [disabled]="ruleSetForm.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ 'add-edit-project-template.save' | translate }}
</button>
</div>

View File

@ -0,0 +1,60 @@
import { Component, Inject } from '@angular/core';
import { AppStateService } from '../../../../state/app-state.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import * as moment from 'moment';
import { RuleSetControllerService, RuleSetModel } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-add-edit-rule-set-dialog',
templateUrl: './add-edit-rule-set-dialog.component.html',
styleUrls: ['./add-edit-rule-set-dialog.component.scss']
})
export class AddEditRuleSetDialogComponent {
public ruleSetForm: FormGroup;
public hasValidFrom: boolean;
constructor(
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
private readonly _ruleSetController: RuleSetControllerService,
public dialogRef: MatDialogRef<AddEditRuleSetDialogComponent>,
@Inject(MAT_DIALOG_DATA) public ruleSet: RuleSetModel
) {
this.ruleSetForm = this._formBuilder.group({
name: [this.ruleSet?.name, Validators.required],
description: [this.ruleSet?.description],
validFrom: [this.ruleSet?.validFrom],
validTo: [this.ruleSet?.validTo]
});
this.hasValidFrom = !!this.ruleSet?.validFrom && !!this.ruleSet?.validTo;
}
public get changed(): boolean {
if (!this.ruleSet) return true;
for (const key of Object.keys(this.ruleSetForm.getRawValue())) {
if (key === 'validFrom' || key === 'validTo') {
if (this.hasValidFrom !== (!!this.ruleSet.validFrom && !!this.ruleSet.validTo)) {
return true;
}
if (this.hasValidFrom && !moment(this.ruleSet[key]).isSame(moment(this.ruleSetForm.get(key).value))) {
return true;
}
} else if (this.ruleSet[key] !== this.ruleSetForm.get(key).value) {
return true;
}
}
return false;
}
async saveRuleSet() {
const ruleSet = {
ruleSetId: this.ruleSet?.ruleSetId,
...this.ruleSetForm.getRawValue()
};
const res = await this._ruleSetController.createOrUpdateRuleSet(ruleSet).toPromise();
this.dialogRef.close({ ruleSet });
}
}

View File

@ -19,7 +19,7 @@
<redaction-icon-button
*ngIf="permissionsService.isAdmin()"
icon="red:plus"
(action)="openAddTemplateDialog()"
(action)="openAddRuleSetDialog()"
text="project-templates-listing.add-new"
type="primary"
></redaction-icon-button>
@ -41,13 +41,13 @@
<div class="select-all-container">
<div
(click)="toggleSelectAll()"
[class.active]="areAllTemplatesSelected"
[class.active]="areAllRuleSetsSelected"
class="select-oval always-visible"
*ngIf="!areAllTemplatesSelected && !areSomeTemplatesSelected"
*ngIf="!areAllRuleSetsSelected && !areSomeRuleSetsSelected"
></div>
<mat-icon *ngIf="areAllTemplatesSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon *ngIf="areAllRuleSetsSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon
*ngIf="areSomeTemplatesSelected && !areAllTemplatesSelected"
*ngIf="areSomeRuleSetsSelected && !areAllRuleSetsSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
@ -55,7 +55,7 @@
</div>
<span class="all-caps-label">
{{ 'project-templates-listing.table-header.title' | translate: { length: displayedTemplates.length } }}
{{ 'project-templates-listing.table-header.title' | translate: { length: displayedRuleSets.length } }}
</span>
</div>
@ -93,17 +93,17 @@
<!-- Table lines -->
<div
class="table-item pointer"
*ngFor="let template of displayedTemplates | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[template.id, 'dictionaries']"
*ngFor="let ruleSet of displayedRuleSets | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[ruleSet.ruleSetId, 'dictionaries']"
>
<div class="pr-0" (click)="toggleTemplateSelected($event, template)">
<div *ngIf="!isTemplateSelected(template)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isTemplateSelected(template)" svgIcon="red:radio-selected"></mat-icon>
<div class="pr-0" (click)="toggleTemplateSelected($event, ruleSet)">
<div *ngIf="!isRuleSetSelected(ruleSet)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isRuleSetSelected(ruleSet)" svgIcon="red:radio-selected"></mat-icon>
</div>
<div class="template-name">
<div class="table-item-title heading">
{{ template.name }}
{{ ruleSet.name }}
</div>
<div class="small-label stats-subtitle">
<div>
@ -121,17 +121,18 @@
<redaction-initials-avatar></redaction-initials-avatar>
</div>
<div class="created-on small-label">
{{ template.dateAdded | date: 'd MMM. yyyy' }}
{{ ruleSet.dateAdded | date: 'd MMM. yyyy' }}
</div>
<div class="modified-on">
<div class="small-label">
{{ template.dateModified | date: 'd MMM. yyyy' }}
{{ ruleSet.dateModified | date: 'd MMM. yyyy' }}
</div>
<redaction-project-template-actions
<redaction-rule-set-actions
class="actions-container"
(loadTemplatesData)="loadTemplatesData()"
></redaction-project-template-actions>
[ruleSet]="ruleSet"
(loadRuleSetsData)="loadRuleSetsData()"
></redaction-rule-set-actions>
</div>
<div class="scrollbar-placeholder"></div>

View File

@ -0,0 +1,95 @@
import { Component, OnInit } from '@angular/core';
import { SortingOption, SortingService } from '../../../utils/sorting.service';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService } from '../../../state/app-state.service';
import { PermissionsService } from '../../../common/service/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { RuleSetModel } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-rule-sets-listing-screen',
templateUrl: './rule-sets-listing-screen.component.html',
styleUrls: ['./rule-sets-listing-screen.component.scss']
})
export class RuleSetsListingScreenComponent implements OnInit {
public ruleSets: RuleSetModel[];
public displayedRuleSets: RuleSetModel[];
public selectedRuleSetIds: string[] = [];
public searchForm: FormGroup;
constructor(
private readonly _dialogService: DialogService,
private readonly _sortingService: SortingService,
private readonly _formBuilder: FormBuilder,
private readonly _appStateService: AppStateService,
public readonly permissionsService: PermissionsService
) {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
}
ngOnInit(): void {
this.loadRuleSetsData();
}
@debounce(200)
private _executeSearch(value: { query: string }) {
this.displayedRuleSets = this.ruleSets.filter((pt) => pt.name.toLowerCase().includes(value.query.toLowerCase()));
}
public loadRuleSetsData() {
this._appStateService.reset();
this.ruleSets = this._appStateService.ruleSets;
this.displayedRuleSets = [...this.ruleSets];
}
public get sortingOption(): SortingOption {
return this._sortingService.getSortingOption('rule-sets-listing');
}
public toggleSort($event) {
this._sortingService.toggleSort('rule-sets-listing', $event);
}
toggleTemplateSelected($event: MouseEvent, ruleSet: RuleSetModel) {
$event.stopPropagation();
const idx = this.selectedRuleSetIds.indexOf(ruleSet.ruleSetId);
if (idx === -1) {
this.selectedRuleSetIds.push(ruleSet.ruleSetId);
} else {
this.selectedRuleSetIds.splice(idx, 1);
}
}
public toggleSelectAll() {
if (this.areSomeRuleSetsSelected) {
this.selectedRuleSetIds = [];
} else {
this.selectedRuleSetIds = this.displayedRuleSets.map((rs) => rs.ruleSetId);
}
}
public get areAllRuleSetsSelected() {
return this.displayedRuleSets.length !== 0 && this.selectedRuleSetIds.length === this.displayedRuleSets.length;
}
public get areSomeRuleSetsSelected() {
return this.selectedRuleSetIds.length > 0;
}
public isRuleSetSelected(ruleSet: RuleSetModel) {
return this.selectedRuleSetIds.indexOf(ruleSet.ruleSetId) !== -1;
}
openAddRuleSetDialog() {
this._dialogService.openAddEditRuleSetDialog(null, async (newRuleSet) => {
if (newRuleSet) {
this.loadRuleSetsData();
}
});
}
}

View File

@ -2,11 +2,10 @@
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<redaction-project-template-view-switch [screen]="'rules'"></redaction-project-template-view-switch>
<redaction-rule-set-view-switch [screen]="'rules'"></redaction-rule-set-view-switch>
<div class="flex-1 actions">
<redaction-project-template-actions (loadTemplatesData)="loadProjectTemplatesData()" [template]="projectTemplate">
</redaction-project-template-actions>
<redaction-rule-set-actions (loadRuleSetsData)="loadRuleSetsData()" [ruleSet]="ruleSet"> </redaction-rule-set-actions>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
</div>

View File

@ -1,14 +1,14 @@
import { Component, ElementRef, 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 { RulesControllerService, RuleSetModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import { DEFAULT_RUL_SET_UUID } from '../../../utils/rule-set-default';
import { ComponentHasChanges } from '../../../utils/can-deactivate.guard';
import { ActivatedRoute } from '@angular/router';
import { AppStateService, ProjectTemplate } from '../../../state/app-state.service';
import { AppStateService } from '../../../state/app-state.service';
declare var ace;
@ -26,7 +26,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
public currentLines: string[] = [];
public changedLines: number[] = [];
public activeEditMarkers: any[] = [];
public projectTemplate: ProjectTemplate;
public ruleSet: RuleSetModel;
@ViewChild('editorComponent', { static: true })
editorComponent: AceEditorComponent;
@ -44,7 +44,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
) {
super(_translateService);
this._initialize();
this.projectTemplate = this._appStateService.getProjectTemplateById(this._actr.snapshot.params.templateId);
this.ruleSet = this._appStateService.getRuleSetById(this._actr.snapshot.params.ruleSetId);
}
private _initialize() {
@ -90,7 +90,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
public async save(): Promise<void> {
this.processing = true;
this._rulesControllerService.uploadRules({ rules: this.editorComponent.getEditor().getValue() }, DEFAULT_RUL_SET_UUID).subscribe(
this._rulesControllerService.uploadRules({ rules: this.editorComponent.getEditor().getValue(), ruleSetId: DEFAULT_RUL_SET_UUID }).subscribe(
() => {
this._initialize();
this._notificationService.showToastNotification(this._translateService.instant('rules-screen.success.generic'), null, NotificationType.SUCCESS);
@ -130,7 +130,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
}
}
public loadProjectTemplatesData(): void {
console.log('load project templates data');
public loadRuleSetsData(): void {
console.log('load rule sets data');
}
}

View File

@ -82,7 +82,7 @@
</div>
<div>
<mat-icon svgIcon="red:template"></mat-icon>
<span>{{ 'EFSA 1 (Vertebrate Authors)' }} </span>
<span>{{ appStateService.getRuleSetById(appStateService.activeProject.ruleSetId)?.name }} </span>
</div>
</div>

View File

@ -11,6 +11,7 @@ export class AppStateGuard implements CanActivate {
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
await this._userService.loadAllUsersIfNecessary();
await this._appStateService.loadRuleSetsIfNecessary();
await this._appStateService.loadDictionaryDataIfNecessary();
await this._appStateService.updateDictionaryVersion();

View File

@ -6,6 +6,9 @@ import {
Project,
ProjectControllerService,
ReanalysisControllerService,
RulesControllerService,
RuleSetControllerService,
RuleSetModel,
StatusControllerService,
TypeValue,
VersionsControllerService
@ -24,6 +27,7 @@ import { DEFAULT_RUL_SET_UUID } from '../utils/rule-set-default';
export interface AppState {
projects: ProjectWrapper[];
ruleSets: RuleSetModel[];
activeProjectId: string;
activeFileId: string;
totalAnalysedPages?: number;
@ -33,18 +37,6 @@ export interface AppState {
ruleVersion?: number;
}
export interface ProjectTemplate {
id: string;
name: string;
description: string;
dateAdded: string; // ( iso-string )
dateModified: string; // ( iso-string)
createdBy: string; // userId
modifiedBy: string; // userid
validFrom: string; // (iso-date )
validTo: string; // ( iso-date)
}
@Injectable({
providedIn: 'root'
})
@ -64,11 +56,13 @@ export class AppStateService {
private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _translateService: TranslateService,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _ruleSetControllerService: RuleSetControllerService,
private readonly _statusControllerService: StatusControllerService,
private readonly _versionsControllerService: VersionsControllerService
) {
this._appState = {
projects: [],
ruleSets: [],
activeProjectId: null,
activeFileId: null
};
@ -161,35 +155,12 @@ export class AppStateService {
return this.getProjectById(projectId).files.find((file) => file.fileId === fileId);
}
public get projectTemplates(): ProjectTemplate[] {
return [
{
id: '1',
name: 'Most important sets',
description: 'description',
dateAdded: '2011-10-05T14:48:00.000Z',
dateModified: '2011-10-05T14:48:00.000Z',
createdBy: '2',
modifiedBy: '2',
validFrom: '2011-10-05T14:48:00.000Z',
validTo: '2011-10-05T14:48:00.000Z'
},
{
id: '2',
name: 'Another rule set',
description: 'description',
dateAdded: '2012-10-05T14:48:00.000Z',
dateModified: '2013-10-05T14:48:00.000Z',
createdBy: '2',
modifiedBy: '2',
validFrom: '2011-10-05T14:48:00.000Z',
validTo: '2011-10-05T14:48:00.000Z'
}
];
public get ruleSets(): RuleSetModel[] {
return this._appState.ruleSets;
}
public getProjectTemplateById(id: string): ProjectTemplate {
return this.projectTemplates.find((pt) => pt.id === id);
public getRuleSetById(id: string): RuleSetModel {
return this.ruleSets.find((pt) => pt.ruleSetId === id);
}
async loadAllProjects() {
@ -403,6 +374,16 @@ export class AppStateService {
}
}
async loadAllRuleSets() {
this._appState.ruleSets = await this._ruleSetControllerService.getAllRuleSets1().toPromise();
}
async loadRuleSetsIfNecessary() {
if (!this._appState.ruleSets.length) {
await this.loadAllRuleSets();
}
}
async loadAllProjectsIfNecessary() {
if (!this._appState.projects.length) {
await this.loadAllProjects();

View File

@ -5,7 +5,7 @@ export class SortingOption {
column: string;
}
type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'project-templates-listing';
type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing';
@Injectable({
providedIn: 'root'
@ -15,7 +15,7 @@ export class SortingService {
'project-listing': { column: 'project.projectName', order: 'asc' },
'project-overview': { column: 'filename', order: 'asc' },
'dictionary-listing': { column: 'label', order: 'asc' },
'project-templates-listing': { column: 'name', order: 'asc' }
'rule-sets-listing': { column: 'name', order: 'asc' }
};
constructor() {}

View File

@ -17,6 +17,7 @@ import { VersionsControllerService } from './api/versionsController.service';
import { ViewedPagesControllerService } from './api/viewedPagesController.service';
import { LegalBasisMappingControllerService } from './api/legalBasisMappingController.service';
import { WatermarkControllerService } from './api/watermarkController.service';
import { RuleSetControllerService } from './api/ruleSetController.service';
@NgModule({
imports: [],
@ -31,6 +32,7 @@ import { WatermarkControllerService } from './api/watermarkController.service';
ProjectControllerService,
ReanalysisControllerService,
RedactionLogControllerService,
RuleSetControllerService,
RulesControllerService,
UserControllerService,
StatusControllerService,

View File

@ -140,21 +140,17 @@ export class DictionaryControllerService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public addType(body: TypeValue, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public addType(body: TypeValue, observe?: 'body', reportProgress?: boolean): Observable<any>;
public addType(body: TypeValue, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public addType(body: TypeValue, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public addType(body: TypeValue, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public addType(body: TypeValue, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public addType(body: TypeValue, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
public addType(body: TypeValue, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling addType.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling addType.');
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
@ -177,7 +173,7 @@ export class DictionaryControllerService {
headers = headers.set('Content-Type', httpContentTypeSelected);
}
return this.httpClient.request<any>('post', `${this.basePath}/dictionary/type/${encodeURIComponent(String(ruleSetId))}`, {
return this.httpClient.request<any>('post', `${this.basePath}/dictionary/type`, {
body: body,
withCredentials: this.configuration.withCredentials,
headers: headers,

View File

@ -133,21 +133,17 @@ export class RulesControllerService {
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
* @param reportProgress flag to report request and response progress.
*/
public uploadRules(body: Rules, ruleSetId: string, observe?: 'body', reportProgress?: boolean): Observable<any>;
public uploadRules(body: Rules, observe?: 'body', reportProgress?: boolean): Observable<any>;
public uploadRules(body: Rules, ruleSetId: string, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public uploadRules(body: Rules, observe?: 'response', reportProgress?: boolean): Observable<HttpResponse<any>>;
public uploadRules(body: Rules, ruleSetId: string, observe?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public uploadRules(body: Rules, bserve?: 'events', reportProgress?: boolean): Observable<HttpEvent<any>>;
public uploadRules(body: Rules, ruleSetId: string, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
public uploadRules(body: Rules, observe: any = 'body', reportProgress: boolean = false): Observable<any> {
if (body === null || body === undefined) {
throw new Error('Required parameter body was null or undefined when calling uploadRules.');
}
if (ruleSetId === null || ruleSetId === undefined) {
throw new Error('Required parameter ruleSetId was null or undefined when calling uploadRules.');
}
let headers = this.defaultHeaders;
// authentication (RED-OAUTH) required
@ -170,7 +166,7 @@ export class RulesControllerService {
headers = headers.set('Content-Type', httpContentTypeSelected);
}
return this.httpClient.request<any>('post', `${this.basePath}/rules/${encodeURIComponent(String(ruleSetId))}`, {
return this.httpClient.request<any>('post', `${this.basePath}/rules`, {
body: body,
withCredentials: this.configuration.withCredentials,
headers: headers,