Pull request #77: Project templates

Merge in RED/ui from project-templates to master

* commit 'd6d54b3740ce59b4cc5a97543ec6298dd826a39b':
  Added project templates
This commit is contained in:
Timo Bejan 2021-01-06 14:29:10 +01:00
commit 4166bdb9cb
51 changed files with 1009 additions and 249 deletions

View File

@ -98,6 +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 { MatSliderModule } from '@angular/material/slider';
import { PendingChangesGuard } from './utils/can-deactivate.guard';
@ -168,23 +172,56 @@ const routes = [
{
path: 'admin',
children: [
{ path: '', redirectTo: 'dictionaries', pathMatch: 'full' },
{ path: '', redirectTo: 'project-templates', pathMatch: 'full' },
{
path: 'dictionaries',
component: DictionaryListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'dictionaries/:type',
component: DictionaryOverviewScreenComponent,
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
path: 'project-templates',
children: [
{
path: '',
component: ProjectTemplatesListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: ':templateId',
children: [
{
path: 'dictionaries',
children: [
{
path: '',
component: DictionaryListingScreenComponent,
canActivate: [CompositeRouteGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: ':type',
component: DictionaryOverviewScreenComponent,
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
}
]
},
{
path: 'rules',
component: RulesScreenComponent,
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{ path: '', redirectTo: 'dictionaries', pathMatch: 'full' }
]
}
]
},
{
path: 'users',
@ -194,15 +231,6 @@ const routes = [
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'rules',
component: RulesScreenComponent,
canActivate: [CompositeRouteGuard],
canDeactivate: [PendingChangesGuard],
data: {
routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard]
}
},
{
path: 'watermark',
component: WatermarkScreenComponent,
@ -285,6 +313,8 @@ const matImports = [
CircleButtonComponent,
ChevronButtonComponent,
DictionaryListingScreenComponent,
ProjectTemplatesListingScreenComponent,
AddEditProjectTemplateDialogComponent,
SyncWidthDirective,
HasScrollbarDirective,
AddEditDictionaryDialogComponent,
@ -298,7 +328,9 @@ const matImports = [
PdfViewerScreenComponent,
HtmlDebugScreenComponent,
ReportDownloadBtnComponent,
ProjectListingActionsComponent
ProjectListingActionsComponent,
ProjectTemplateActionsComponent,
ProjectTemplateViewSwitchComponent
],
imports: [
BrowserModule,
@ -351,7 +383,10 @@ const matImports = [
provide: MAT_DATE_FORMATS,
useValue: {
display: {
dateInput: 'DD/MM/YY'
dateInput: 'DD/MM/YY',
monthYearLabel: 'YYYY',
dateA11yLabel: 'LL',
monthYearA11yLabel: 'YYYY'
}
}
}

View File

@ -1,7 +1,7 @@
.file-actions {
display: flex;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 2px;
}
}

View File

@ -9,7 +9,7 @@
.actions {
display: flex;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 8px;
}
}

View File

@ -1,44 +1,46 @@
<div class="menu flex-2 visible-lg breadcrumbs-container">
<a
class="breadcrumb"
routerLink="/ui/admin/dictionaries"
translate="dictionaries"
routerLinkActive="active"
[routerLink]="'/ui/admin/project-templates'"
[routerLinkActiveOptions]="{ exact: true }"
*ngIf="screen === 'dictionaries' || root"
routerLinkActive="active"
translate="project-templates"
*ngIf="root || !!projectTemplate"
></a>
<a
class="ml-32 breadcrumb"
[routerLink]="'/ui/admin/rules'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="rule-editor"
*ngIf="(screen === 'rules' || root) && userPreferenceService.areDevFeaturesEnabled"
></a>
<a
class="ml-32 breadcrumb"
class="breadcrumb"
[routerLink]="'/ui/admin/users'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="user-management"
*ngIf="(screen === 'users' || root) && userPreferenceService.areDevFeaturesEnabled"
*ngIf="root && userPreferenceService.areDevFeaturesEnabled"
></a>
<a
class="ml-32 breadcrumb"
class="breadcrumb"
[routerLink]="'/ui/admin/watermark'"
[routerLinkActiveOptions]="{ exact: true }"
routerLinkActive="active"
translate="watermark"
*ngIf="(screen === 'watermark' || root) && permissionService.isAdmin()"
*ngIf="root && permissionService.isAdmin()"
></a>
<ng-container *ngIf="projectTemplate">
<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>
</ng-container>
<ng-container *ngIf="dictionary">
<mat-icon svgIcon="red:arrow-right"></mat-icon>
<a class="breadcrumb" [routerLink]="'/ui/admin/dictionaries/' + dictionary.type" routerLinkActive="active">
{{ dictionary.type | humanize }}
<a
class="breadcrumb ml-0"
[routerLink]="'/ui/admin/project-templates/' + projectTemplate.id + '/dictionaries/' + dictionary.type"
routerLinkActive="active"
>
{{ dictionary.label }}
</a>
</ng-container>
</div>

View File

@ -1,3 +1,3 @@
.ml-32 {
.breadcrumb:not(:first-child):not(.ml-0) {
margin-left: 32px;
}

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TypeValue } from '@redaction/red-ui-http';
import { AppStateService } from '../../state/app-state.service';
import { AppStateService, ProjectTemplate } from '../../state/app-state.service';
import { UserPreferenceService } from '../../common/service/user-preference.service';
import { PermissionsService } from '../../common/service/permissions.service';
@ -12,24 +12,25 @@ import { PermissionsService } from '../../common/service/permissions.service';
})
export class AdminBreadcrumbsComponent implements OnInit {
public dictionary: TypeValue;
public root: boolean;
public screen: string;
public projectTemplate: ProjectTemplate;
@Input()
public root = false;
constructor(
public readonly userPreferenceService: UserPreferenceService,
public readonly permissionService: PermissionsService,
private readonly _activatedRoute: ActivatedRoute,
private readonly _appStateService: AppStateService
) {
) {}
ngOnInit(): void {
this._activatedRoute.params.subscribe((params) => {
const url = this._activatedRoute.snapshot.url;
this.root = url.length === 1;
this.screen = url[0].path;
if (this.screen === 'dictionaries' && url.length === 2) {
this.dictionary = this._appStateService.dictionaryData[params.type];
if (params.templateId) {
this.projectTemplate = this._appStateService.getProjectTemplateById(params.templateId);
}
if (params.type) {
this.dictionary = this._appStateService.getDictionaryTypeValue(params.type);
}
});
}
ngOnInit(): void {}
}

View File

@ -5,14 +5,14 @@
flex-direction: column;
margin-top: 10px;
> *:not(last-child) {
> *:not(:last-child) {
margin-bottom: 10px;
}
.comment {
display: flex;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 12px;
}
@ -53,7 +53,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 15px;
}
}
@ -61,7 +61,7 @@
.actions-container {
margin-left: 26px;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 5px;
}

View File

@ -4,7 +4,7 @@
align-items: center;
justify-content: flex-start;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 4px;
}
}

View File

@ -0,0 +1,19 @@
<div class="action-buttons">
<redaction-circle-button
(action)="openDeleteTemplateDialog($event, template)"
*ngIf="permissionsService.isAdmin()"
tooltip="project-templates-listing.action.delete"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openEditTemplateDialog($event, template)"
*ngIf="permissionsService.isAdmin()"
tooltip="project-templates-listing.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
</div>

View File

@ -0,0 +1,7 @@
.action-buttons {
display: flex;
redaction-circle-button:not(:last-child) {
margin-right: 2px;
}
}

View File

@ -0,0 +1,37 @@
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

@ -0,0 +1,8 @@
<div class="">
<div class="red-input-group slider-row">
<mat-button-toggle-group [value]="screen" (change)="switchView($event)" appearance="legacy">
<mat-button-toggle [value]="'dictionaries'"> {{ 'dictionaries' | translate }}</mat-button-toggle>
<mat-button-toggle [value]="'rules'"> {{ 'rule-editor' | translate }}</mat-button-toggle>
</mat-button-toggle-group>
</div>
</div>

View File

@ -0,0 +1,27 @@
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']
})
export class ProjectTemplateViewSwitchComponent implements OnInit {
@Input() public screen: 'rules' | 'dictionaries';
private readonly _projectTemplateId: string;
constructor(private readonly _actr: ActivatedRoute, private _router: Router) {
this._projectTemplateId = this._actr.snapshot.params.templateId;
}
ngOnInit(): void {}
public switchView($event) {
if ($event.value === 'dictionaries') {
this._router.navigate(['ui/admin/project-templates/' + this._projectTemplateId + '/dictionaries']);
} else {
this._router.navigate(['ui/admin/project-templates/' + this._projectTemplateId + '/rules']);
}
}
}

View File

@ -10,7 +10,7 @@
align-items: center;
&:not(.column) {
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 20px;
}
}
@ -18,7 +18,7 @@
&.column {
flex-direction: column;
> *:not(last-child) {
> *:not(:last-child) {
margin-bottom: 20px;
}
}
@ -51,7 +51,7 @@
border-radius: 4px;
padding: 3px 8px;
&:not(last-child) {
&:not(:last-child) {
margin-bottom: 8px;
}

View File

@ -15,7 +15,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 10px;
}
}

View File

@ -13,7 +13,7 @@ import * as moment from 'moment';
})
export class AddEditProjectDialogComponent {
public projectForm: FormGroup;
public hasDueDate = true;
public hasDueDate: boolean;
constructor(
private readonly _appStateService: AppStateService,

View File

@ -4,7 +4,7 @@ import { DictionaryControllerService, FileManagementControllerService, FileStatu
import { ConfirmationDialogComponent, ConfirmationDialogInput } from './confirmation-dialog/confirmation-dialog.component';
import { NotificationService, NotificationType } from '../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { AppStateService } from '../state/app-state.service';
import { AppStateService, ProjectTemplate } 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,6 +13,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 { DEFAULT_RUL_SET_UUID } from '../utils/rule-set-default';
const dialogConfig = {
@ -174,6 +175,17 @@ export class DialogService {
return ref;
}
public openDeleteProjectTemplateDialog($event: MouseEvent, projectTemplate: ProjectTemplate, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async (result) => {
if (result) {
if (cb) cb();
}
});
return ref;
}
public openDeleteProjectDialog($event: MouseEvent, project: ProjectWrapper, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const ref = this._dialog.open(ConfirmationDialogComponent, {
@ -273,6 +285,22 @@ export class DialogService {
return ref;
}
public openAddEditTemplateDialog(template: ProjectTemplate, cb?: Function): MatDialogRef<AddEditProjectTemplateDialogComponent> {
const ref = this._dialog.open(AddEditProjectTemplateDialogComponent, {
...dialogConfig,
data: template,
autoFocus: true
});
ref.afterClosed().subscribe((result) => {
if (result && cb) {
cb(result);
}
});
return ref;
}
openRemoveAnnotationModal($event: MouseEvent, annotation: AnnotationWrapper, callback: () => void) {
$event?.stopPropagation();

View File

@ -27,6 +27,7 @@ export class IconsModule {
'comment',
'case-sensitive',
'comment-fill',
'dictionary',
'document',
'double-chevron-right',
'download',

View File

@ -3,7 +3,7 @@
.first-row {
display: flex;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 16px;
}
@ -12,18 +12,6 @@
}
}
.slider-row {
display: flex;
flex-direction: row;
align-items: center;
}
.mat-button-toggle-checked {
background: $primary;
transition: background-color 0.25s ease;
color: $white;
}
.mb-14 {
margin-bottom: 14px;
}

View File

@ -2,130 +2,162 @@
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<form [formGroup]="searchForm">
<div class="red-input-group">
<input [placeholder]="'dictionary-listing.search' | translate" formControlName="query" name="query" type="text" class="with-icon mt-0" />
<mat-icon class="icon-right" svgIcon="red:search"></mat-icon>
</div>
</form>
<redaction-project-template-view-switch [screen]="'dictionaries'"></redaction-project-template-view-switch>
<div class="actions">
<redaction-icon-button
*ngIf="permissionsService.isAdmin()"
icon="red:plus"
(action)="openAddEditDictionaryDialog()"
text="dictionary-listing.add-new"
type="primary"
></redaction-icon-button>
<div class="flex-1 actions">
<redaction-project-template-actions (loadTemplatesData)="loadProjectTemplatesData()" [template]="projectTemplate">
</redaction-project-template-actions>
<redaction-circle-button
class="ml-6"
*ngIf="permissionsService.isUser()"
[routerLink]="['/ui/projects/']"
tooltip="common.close"
tooltipPosition="before"
icon="red:close"
></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
</div>
</div>
<div class="red-content-inner">
<div class="left-container">
<div class="header-item">
<div class="select-all-container">
<div *ngIf="dictionaries.length === 0" class="empty-state">
<mat-icon svgIcon="red:dictionary"></mat-icon>
<div class="heading-l" translate="dictionary-listing.no-data.title"></div>
<redaction-icon-button (action)="openAddEditDictionaryDialog()" icon="red:plus" text="dictionary-listing.no-data.action" type="primary">
</redaction-icon-button>
</div>
<ng-container *ngIf="dictionaries.length > 0">
<div class="header-item">
<div class="select-all-container">
<div
(click)="toggleSelectAll()"
[class.active]="areAllDictsSelected"
class="select-oval always-visible"
*ngIf="!areAllDictsSelected && !areSomeDictsSelected"
></div>
<mat-icon *ngIf="areAllDictsSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon
*ngIf="areSomeDictsSelected && !areAllDictsSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
></mat-icon>
</div>
<span class="all-caps-label">
{{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }}
</span>
<div class="dictionary-actions-container">
<form [formGroup]="searchForm">
<div class="red-input-group w-250">
<input
[placeholder]="'dictionary-listing.search' | translate"
formControlName="query"
name="query"
type="text"
class="with-icon mt-0"
/>
<mat-icon class="icon-right" svgIcon="red:search"></mat-icon>
</div>
</form>
<div class="actions">
<redaction-icon-button
*ngIf="permissionsService.isAdmin()"
icon="red:plus"
(action)="openAddEditDictionaryDialog()"
text="dictionary-listing.add-new"
type="primary"
></redaction-icon-button>
</div>
</div>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder placeholder-bottom-border"></div>
<redaction-table-col-name
label="dictionary-listing.table-col-names.type"
column="label"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
></redaction-table-col-name>
<redaction-table-col-name
label="dictionary-listing.table-col-names.order-of-importance"
column="rank"
class="flex-center"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
></redaction-table-col-name>
<redaction-table-col-name label="dictionary-listing.table-col-names.hint-redaction" class="flex-center"></redaction-table-col-name>
<div class="placeholder-bottom-border"></div>
<div class="placeholder-bottom-border scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<!-- Table lines -->
<div
(click)="toggleSelectAll()"
[class.active]="areAllDictsSelected"
class="select-oval always-visible"
*ngIf="!areAllDictsSelected && !areSomeDictsSelected"
></div>
<mat-icon *ngIf="areAllDictsSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon
*ngIf="areSomeDictsSelected && !areAllDictsSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
></mat-icon>
</div>
class="table-item pointer"
*ngFor="let dict of displayedDictionaries | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[dict.type]"
>
<div class="pr-0" (click)="toggleDictSelected($event, dict)">
<div *ngIf="!isDictSelected(dict)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isDictSelected(dict)" svgIcon="red:radio-selected"></mat-icon>
</div>
<span class="all-caps-label">
{{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }}
</span>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder placeholder-bottom-border"></div>
<redaction-table-col-name
label="dictionary-listing.table-col-names.type"
column="label"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
></redaction-table-col-name>
<redaction-table-col-name label="dictionary-listing.table-col-names.hint-redaction" class="flex-center"></redaction-table-col-name>
<div class="placeholder-bottom-border"></div>
<div class="placeholder-bottom-border scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<!-- Table lines -->
<div
class="table-item pointer"
*ngFor="let dict of displayedDictionaries | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="['/ui/admin/dictionaries/' + dict.type]"
>
<div class="pr-0" (click)="toggleDictSelected($event, dict)">
<div *ngIf="!isDictSelected(dict)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isDictSelected(dict)" svgIcon="red:radio-selected"></mat-icon>
</div>
<div>
<div class="color-square" [ngStyle]="{ 'background-color': dict.hexColor }"></div>
<div class="dict-name">
<div class="table-item-title heading">
{{ dict.label }}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ dict.entries?.length }}
<div>
<div class="color-square" [ngStyle]="{ 'background-color': dict.hexColor }"></div>
<div class="dict-name">
<div class="table-item-title heading">
{{ dict.label }}
</div>
<div *ngIf="!dict.caseInsensitive">
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
{{ 'dictionary-listing.case-sensitive' | translate }}
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ dict.entries?.length }}
</div>
<div *ngIf="!dict.caseInsensitive">
<mat-icon svgIcon="red:case-sensitive"></mat-icon>
{{ 'dictionary-listing.case-sensitive' | translate }}
</div>
</div>
</div>
</div>
</div>
<div class="analyzed">
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
</div>
<div class="actions-container">
<div class="action-buttons">
<redaction-circle-button
(action)="openDeleteDictionaryDialog($event, dict)"
*ngIf="permissionsService.isAdmin()"
tooltip="dictionary-listing.action.delete"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openEditDictionaryDialog($event, dict)"
*ngIf="permissionsService.isAdmin()"
tooltip="dictionary-listing.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
<div class="rank small-label">
{{ dict.rank }}
</div>
<div class="analyzed">
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
</div>
<div class="actions-container">
<div class="action-buttons">
<redaction-circle-button
(action)="openDeleteDictionaryDialog($event, dict)"
*ngIf="permissionsService.isAdmin()"
tooltip="dictionary-listing.action.delete"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openEditDictionaryDialog($event, dict)"
*ngIf="permissionsService.isAdmin()"
tooltip="dictionary-listing.action.edit"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
</div>
</div>
<div class="scrollbar-placeholder"></div>
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</ng-container>
</div>
<div class="right-container">

View File

@ -2,7 +2,17 @@
@import '../../../../assets/styles/red-mixins';
.header-item {
padding: 0 24px 0 10px;
padding: 0 16px 0 10px;
.dictionary-actions-container {
display: flex;
flex: 1;
justify-content: flex-end;
> *:not(:last-child) {
margin-right: 16px;
}
}
}
redaction-table-col-name::ng-deep {
@ -15,10 +25,10 @@ redaction-table-col-name::ng-deep {
width: calc(100vw - 353px);
.grid-container {
grid-template-columns: auto 1fr 1fr 2fr 11px;
grid-template-columns: auto 2fr 1fr 1fr 1fr 11px;
&.has-scrollbar:hover {
grid-template-columns: auto 1fr 1fr 2fr;
grid-template-columns: auto 2fr 1fr 1fr 1fr;
}
.table-item {
@ -29,7 +39,8 @@ redaction-table-col-name::ng-deep {
align-items: center;
justify-content: flex-start;
&.analyzed {
&.analyzed,
&.rank {
justify-content: center;
}
@ -71,10 +82,5 @@ redaction-table-col-name::ng-deep {
.page-header .actions {
display: flex;
width: calc(353px - 24px);
justify-content: flex-end;
.ml-6 {
margin-left: 6px;
}
}

View File

@ -3,7 +3,7 @@ import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/s
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { SortingOption, SortingService } from '../../../utils/sorting.service';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService } from '../../../state/app-state.service';
import { AppStateService, ProjectTemplate } from '../../../state/app-state.service';
import { tap } from 'rxjs/operators';
import { forkJoin } from 'rxjs';
import { PermissionsService } from '../../../common/service/permissions.service';
@ -11,6 +11,7 @@ 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';
@Component({
selector: 'redaction-dictionary-listing-screen',
@ -23,6 +24,7 @@ export class DictionaryListingScreenComponent implements OnInit {
public displayedDictionaries: TypeValue[];
public selectedDictKeys: string[] = [];
public searchForm: FormGroup;
public projectTemplate: ProjectTemplate;
constructor(
private readonly _dialogService: DialogService,
@ -30,6 +32,7 @@ export class DictionaryListingScreenComponent implements OnInit {
private readonly _formBuilder: FormBuilder,
private readonly _dictionaryControllerService: DictionaryControllerService,
private readonly _appStateService: AppStateService,
private readonly _actr: ActivatedRoute,
public readonly permissionsService: PermissionsService
) {
this.searchForm = this._formBuilder.group({
@ -37,6 +40,8 @@ export class DictionaryListingScreenComponent implements OnInit {
});
this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
this.projectTemplate = this._appStateService.getProjectTemplateById(this._actr.snapshot.params.templateId);
}
ngOnInit(): void {
@ -141,4 +146,8 @@ export class DictionaryListingScreenComponent implements OnInit {
this._loadDictionaryData();
});
}
public loadProjectTemplatesData(): void {
console.log('load project templates data');
}
}

View File

@ -42,7 +42,7 @@
<redaction-circle-button
class="ml-6"
[routerLink]="['/ui/admin/dictionaries/']"
[routerLink]="['..']"
tooltip="common.close"
tooltipPosition="before"
icon="red:close"

View File

@ -0,0 +1,59 @@
<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 } }}
</div>
<form (submit)="saveTemplate()" [formGroup]="templateForm">
<div class="dialog-content">
<div class="red-input-group required w-300">
<label translate="add-edit-project-template.form.name"></label>
<input formControlName="name" name="name" type="text" placeholder="{{ 'add-edit-project-template.form.name-placeholder' | translate }}" />
</div>
<div class="red-input-group w-400">
<label translate="add-edit-project-template.form.description"></label>
<textarea
formControlName="description"
name="description"
type="text"
rows="4"
placeholder="{{ 'add-edit-project-template.form.description-placeholder' | translate }}"
></textarea>
</div>
<div class="valid-from">
<mat-checkbox [checked]="hasValidFrom" (change)="hasValidFrom = !hasValidFrom" class="filter-menu-checkbox" color="primary">
{{ 'project-listing.add-edit-dialog.form.due-date' | translate }}
</mat-checkbox>
<ng-container *ngIf="hasValidFrom">
<div class="red-input-group datepicker-wrapper ml-16 mr-16">
<input placeholder="dd/mm/yy" [matDatepicker]="fromPicker" formControlName="validFrom" />
<mat-datepicker-toggle matSuffix [for]="fromPicker">
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #fromPicker></mat-datepicker>
</div>
to
<div class="red-input-group datepicker-wrapper ml-16">
<input placeholder="dd/mm/yy" [matDatepicker]="toPicker" formControlName="validTo" />
<mat-datepicker-toggle matSuffix [for]="toPicker">
<mat-icon matDatepickerToggleIcon svgIcon="red:calendar"></mat-icon>
</mat-datepicker-toggle>
<mat-datepicker #toPicker></mat-datepicker>
</div>
</ng-container>
</div>
</div>
<div class="dialog-actions">
<button [disabled]="templateForm.invalid || !changed" color="primary" mat-flat-button type="submit">
{{ 'add-edit-project-template.save' | translate }}
</button>
</div>
</form>
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
</section>

View File

@ -0,0 +1,19 @@
.valid-from {
margin-top: 16px;
min-height: 34px;
display: flex;
flex-direction: row;
align-items: center;
mat-checkbox {
width: fit-content;
}
.mr-16 {
margin-right: 16px;
}
.ml-16 {
margin-left: 16px;
}
}

View File

@ -0,0 +1,55 @@
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

@ -0,0 +1,142 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1" [root]="true"></redaction-admin-breadcrumbs>
<div class="actions">
<form [formGroup]="searchForm">
<div class="red-input-group w-250">
<input
[placeholder]="'project-templates-listing.search' | translate"
formControlName="query"
name="query"
type="text"
class="with-icon mt-0"
/>
<mat-icon class="icon-right" svgIcon="red:search"></mat-icon>
</div>
</form>
<redaction-icon-button
*ngIf="permissionsService.isAdmin()"
icon="red:plus"
(action)="openAddTemplateDialog()"
text="project-templates-listing.add-new"
type="primary"
></redaction-icon-button>
<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="left-container">
<div class="header-item">
<div class="select-all-container">
<div
(click)="toggleSelectAll()"
[class.active]="areAllTemplatesSelected"
class="select-oval always-visible"
*ngIf="!areAllTemplatesSelected && !areSomeTemplatesSelected"
></div>
<mat-icon *ngIf="areAllTemplatesSelected" (click)="toggleSelectAll()" class="selection-icon active" svgIcon="red:radio-selected"></mat-icon>
<mat-icon
*ngIf="areSomeTemplatesSelected && !areAllTemplatesSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
></mat-icon>
</div>
<span class="all-caps-label">
{{ 'project-templates-listing.table-header.title' | translate: { length: displayedTemplates.length } }}
</span>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder placeholder-bottom-border"></div>
<redaction-table-col-name
label="project-templates-listing.table-col-names.name"
column="name"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
></redaction-table-col-name>
<redaction-table-col-name label="project-templates-listing.table-col-names.created-by" class="flex-center"></redaction-table-col-name>
<redaction-table-col-name
label="project-templates-listing.table-col-names.created-on"
class="flex-center"
column="dateAdded"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
></redaction-table-col-name>
<redaction-table-col-name
label="project-templates-listing.table-col-names.modified-on"
class="flex-center"
column="dateModified"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
></redaction-table-col-name>
<div class="placeholder-bottom-border scrollbar-placeholder"></div>
</div>
<div class="grid-container" redactionHasScrollbar>
<!-- Table lines -->
<div
class="table-item pointer"
*ngFor="let template of displayedTemplates | sortBy: sortingOption.order:sortingOption.column"
[routerLink]="[template.id, '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>
<div class="template-name">
<div class="table-item-title heading">
{{ template.name }}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:dictionary"></mat-icon>
{{ 'project-templates-listing.dictionaries' | translate: { length: 3 } }}
</div>
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ 'project-templates-listing.entries' | translate: { length: 300 } }}
</div>
</div>
</div>
<div class="created-by">
<redaction-initials-avatar></redaction-initials-avatar>
</div>
<div class="created-on small-label">
{{ template.dateAdded | date: 'd MMM. yyyy' }}
</div>
<div class="modified-on">
<div class="small-label">
{{ template.dateModified | date: 'd MMM. yyyy' }}
</div>
<redaction-project-template-actions
class="actions-container"
(loadTemplatesData)="loadTemplatesData()"
></redaction-project-template-actions>
</div>
<div class="scrollbar-placeholder"></div>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,56 @@
@import '../../../../assets/styles/red-variables';
@import '../../../../assets/styles/red-mixins';
.header-item {
padding: 0 24px 0 10px;
}
redaction-table-col-name::ng-deep {
> div {
padding-left: 10px !important;
}
}
.left-container {
width: 100vw;
.grid-container {
grid-template-columns: auto 1fr 1fr 1fr 1fr 11px;
&.has-scrollbar:hover {
grid-template-columns: auto 1fr 1fr 1fr 1fr;
}
.table-item {
> div:not(.scrollbar-placeholder) {
display: flex;
flex-direction: row;
padding-left: 10px;
align-items: center;
&.template-name {
flex-direction: column;
align-items: flex-start;
}
.stats-subtitle {
margin-top: 4px;
}
.table-item-title {
max-width: 100%;
}
&.created-by,
&.created-on,
&.modified-on {
display: flex;
}
}
}
}
}
.page-header .actions > *:not(:last-child) {
margin-right: 16px;
}

View File

@ -0,0 +1,94 @@
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,34 +1,43 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs></redaction-admin-breadcrumbs>
<div class="actions">
<redaction-circle-button
(action)="download()"
tooltip="rules-screen.action.download"
tooltipPosition="below"
icon="red:download"
></redaction-circle-button>
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<redaction-circle-button
(action)="fileInput.click()"
*ngIf="permissionsService.isAdmin()"
tooltip="rules-screen.action.upload"
tooltipPosition="below"
icon="red:upload"
>
</redaction-circle-button>
<redaction-project-template-view-switch [screen]="'rules'"></redaction-project-template-view-switch>
<input #fileInput (change)="upload($event)" hidden class="file-upload-input" type="file" accept="text/plain" />
<div class="flex-1 actions">
<redaction-project-template-actions (loadTemplatesData)="loadProjectTemplatesData()" [template]="projectTemplate">
</redaction-project-template-actions>
<redaction-circle-button
class="ml-6"
*ngIf="permissionsService.isUser()"
[routerLink]="['/ui/projects/']"
tooltip="common.close"
tooltipPosition="before"
icon="red:close"
></redaction-circle-button>
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
</div>
<!-- <div class="actions">-->
<!-- <redaction-circle-button-->
<!-- (action)="download()"-->
<!-- tooltip="rules-screen.action.download"-->
<!-- tooltipPosition="below"-->
<!-- icon="red:download"-->
<!-- ></redaction-circle-button>-->
<!-- <redaction-circle-button-->
<!-- (action)="fileInput.click()"-->
<!-- *ngIf="permissionsService.isAdmin()"-->
<!-- tooltip="rules-screen.action.upload"-->
<!-- tooltipPosition="below"-->
<!-- icon="red:upload"-->
<!-- >-->
<!-- </redaction-circle-button>-->
<!-- <input #fileInput (change)="upload($event)" hidden class="file-upload-input" type="file" accept="text/plain" />-->
<!-- <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">

View File

@ -9,3 +9,8 @@
.changes-box {
right: 40px;
}
.page-header .actions {
display: flex;
justify-content: flex-end;
}

View File

@ -7,6 +7,8 @@ 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';
declare var ace;
@ -24,6 +26,7 @@ export class RulesScreenComponent extends ComponentHasChanges {
public currentLines: string[] = [];
public changedLines: number[] = [];
public activeEditMarkers: any[] = [];
public projectTemplate: ProjectTemplate;
@ViewChild('editorComponent', { static: true })
editorComponent: AceEditorComponent;
@ -34,11 +37,14 @@ export class RulesScreenComponent extends ComponentHasChanges {
constructor(
public readonly permissionsService: PermissionsService,
private readonly _rulesControllerService: RulesControllerService,
private readonly _appStateService: AppStateService,
private readonly _notificationService: NotificationService,
protected readonly _translateService: TranslateService
protected readonly _translateService: TranslateService,
private readonly _actr: ActivatedRoute
) {
super(_translateService);
this._initialize();
this.projectTemplate = this._appStateService.getProjectTemplateById(this._actr.snapshot.params.templateId);
}
private _initialize() {
@ -123,4 +129,8 @@ export class RulesScreenComponent extends ComponentHasChanges {
fileReader.readAsText(file);
}
}
public loadProjectTemplatesData(): void {
console.log('load project templates data');
}
}

View File

@ -1,6 +1,6 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<redaction-admin-breadcrumbs class="flex-1" [root]="true"></redaction-admin-breadcrumbs>
<form [formGroup]="searchForm">
<div class="red-input-group">

View File

@ -1,6 +1,6 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<redaction-admin-breadcrumbs class="flex-1" [root]="true"></redaction-admin-breadcrumbs>
<div class="actions">
<redaction-circle-button

View File

@ -145,7 +145,7 @@
display: flex;
margin-left: 8px;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 2px;
}
}

View File

@ -2,7 +2,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 2px;
}
}

View File

@ -33,6 +33,18 @@ 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'
})
@ -149,6 +161,37 @@ 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 getProjectTemplateById(id: string): ProjectTemplate {
return this.projectTemplates.find((pt) => pt.id === id);
}
async loadAllProjects() {
const projects = await this._projectControllerService.getProjects().toPromise();
if (projects) {

View File

@ -5,7 +5,7 @@ export class SortingOption {
column: string;
}
type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing';
type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'project-templates-listing';
@Injectable({
providedIn: 'root'
@ -14,7 +14,8 @@ export class SortingService {
private _options: { [key: string]: SortingOption } = {
'project-listing': { column: 'project.projectName', order: 'asc' },
'project-overview': { column: 'filename', order: 'asc' },
'dictionary-listing': { column: 'label', order: 'asc' }
'dictionary-listing': { column: 'label', order: 'asc' },
'project-templates-listing': { column: 'name', order: 'asc' }
};
constructor() {}

View File

@ -560,6 +560,20 @@
},
"save": "Save Dictionary"
},
"add-edit-project-template": {
"title": {
"edit": "Edit {{name}} Project Template",
"new": "Create Project Template"
},
"form": {
"name": "Project Template Name",
"name-placeholder": "Enter Name",
"description": "Description",
"description-placeholder": "Enter Description",
"valid-from": "Valid from"
},
"save": "Save Project Template"
},
"dictionary-overview": {
"action": {
"delete": "Delete Dictionary",
@ -597,9 +611,34 @@
},
"table-col-names": {
"type": "Type",
"order-of-importance": "Order Of Importance",
"hint-redaction": "Hint/Redaction"
},
"search": "Search..."
"search": "Search...",
"no-data": {
"title": "There are no dictionaries yet.",
"action": "New Dictionary"
}
},
"project-templates": "Project Templates",
"project-templates-listing": {
"table-header": {
"title": "{{length}} project templates"
},
"entries": "{{length}} entries",
"dictionaries": "{{length}} dictionaries",
"action": {
"delete": "Delete Template",
"edit": "Edit Template"
},
"add-new": "New Project Template",
"search": "Search...",
"table-col-names": {
"name": "Name",
"created-by": "Created by",
"created-on": "Created on",
"modified-on": "Modified on"
}
},
"user-listing": {
"table-header": {

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="10px" height="10px" viewBox="0 0 10 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>6C0B3F0C-53AF-4E50-8C71-BCE07198E584</title>
<g id="Settings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="01.0-Rule-Sets" transform="translate(-40.000000, -241.000000)">
<rect x="0" y="0" width="1440" height="705"></rect>
<rect id="Rectangle" x="0" y="193" width="1440" height="80"></rect>
<g id="Group-16" transform="translate(40.000000, 239.000000)" fill="currentColor" fill-rule="nonzero" >
<g id="status" transform="translate(0.000000, 2.000000)">
<path d="M9.5,0 L9.5,10 L2,10 C1.15,10 0.5,9.35 0.5,8.5 L0.5,8.5 L0.5,1.5 C0.5,0.65 1.15,0 2,0 L2,0 L9.5,0 Z M2.5,1 L2,1 C1.7,1 1.5,1.2 1.5,1.5 L1.5,1.5 L1.5,8.5 C1.5,8.8 1.7,9 2,9 L2,9 L2.5,9 L2.5,1 Z M8.5,1 L3.5,1 L3.5,9 L8.5,9 L8.5,1 Z M7.5,2 L7.5,5 L4.5,5 L4.5,2 L7.5,2 Z M6.5,3 L5.5,3 L5.5,4 L6.5,4 L6.5,3 Z" id="Combined-Shape"></path>
</g>
</g>
<g id="Group-20" transform="translate(0.000000, 163.000000)"></g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -5,7 +5,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 6px;
}

View File

@ -15,7 +15,7 @@
line-height: 34px;
transition: opacity 0.2s;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 6px;
}
}

View File

@ -25,7 +25,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 8px;
}
}

View File

@ -77,7 +77,7 @@
margin-right: 4px;
}
&:not(last-child) {
&:not(:last-child) {
margin-right: 12px;
}
}

View File

@ -88,7 +88,7 @@
box-shadow: 0 2px 6px 0 rgba(40, 50, 65, 0.3);
z-index: 5000;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 24px;
}
}

View File

@ -12,7 +12,7 @@
box-sizing: border-box;
@include inset-shadow;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 10px;
}
@ -20,7 +20,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 25px;
}
}

View File

@ -73,6 +73,18 @@ form {
right: 10px;
}
.slider-row {
display: flex;
flex-direction: row;
align-items: center;
}
.mat-button-toggle-checked {
background: $primary;
transition: background-color 0.25s ease;
color: $white;
}
input,
textarea,
mat-select {

View File

@ -24,7 +24,7 @@
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 8px;
}

View File

@ -26,7 +26,7 @@ body {
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 2px;
}
@ -47,7 +47,7 @@ body {
display: flex;
align-items: center;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 2px;
}
}
@ -284,7 +284,7 @@ body {
height: calc(100vh - 61px);
position: relative;
> *:not(last-child) {
> *:not(:last-child) {
margin-bottom: 2px;
}
}

View File

@ -13,7 +13,7 @@
align-items: center;
box-shadow: none;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 10px;
}
@ -25,7 +25,7 @@
margin-top: 24px;
display: flex;
> *:not(last-child) {
> *:not(:last-child) {
margin-right: 24px;
}