+
diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss
index 7ea9ffd49..defb8b5e7 100644
--- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss
+++ b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss
@@ -4,7 +4,7 @@
.header-item {
padding: 0 16px 0 10px;
- .dictionary-actions-container {
+ .attributes-actions-container {
display: flex;
flex: 1;
justify-content: flex-end;
@@ -36,8 +36,7 @@ redaction-table-col-name::ng-deep {
align-items: center;
justify-content: flex-start;
- &.analyzed,
- &.rank {
+ &.center {
justify-content: center;
}
diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts
index b511b141a..fd6b9f05a 100644
--- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts
@@ -6,7 +6,7 @@ import { DialogService } from '../../../dialogs/dialog.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 { PermissionsService } from '../../../utils/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { ActivatedRoute } from '@angular/router';
diff --git a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts
index e36ab7b20..e150eb501 100644
--- a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts
@@ -2,7 +2,7 @@ import { Component, ElementRef, ViewChild } from '@angular/core';
import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http';
import { DialogService } from '../../../dialogs/dialog.service';
import { AppStateService } from '../../../state/app-state.service';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import { ActivatedRoute, Router } from '@angular/router';
import { AceEditorComponent } from 'ng2-ace-editor';
import { debounce } from '../../../utils/debounce';
diff --git a/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts b/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts
index 327553587..bbeb6b39b 100644
--- a/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts
@@ -3,7 +3,7 @@ import { DigitalSignature, DigitalSignatureControllerService } from '@redaction/
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NotificationService, NotificationType } from '../../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import { lastIndexOfEnd } from '../../../utils/functions';
@Component({
diff --git a/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html
new file mode 100644
index 000000000..b4feb342a
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ attribute.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.scss b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.scss
new file mode 100644
index 000000000..fcf232534
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.scss
@@ -0,0 +1,54 @@
+.page-header .actions {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.header-item {
+ padding: 0 24px 0 10px;
+}
+
+redaction-table-col-name::ng-deep {
+ > div {
+ padding-left: 10px !important;
+ }
+}
+
+.left-container {
+ width: 100vw;
+
+ .header-item {
+ .attributes-actions-container {
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
+
+ > *:not(:last-child) {
+ margin-right: 16px;
+ }
+ }
+ }
+
+ cdk-virtual-scroll-viewport {
+ ::ng-deep.cdk-virtual-scroll-content-wrapper {
+ grid-template-columns: auto 1fr 1fr 3fr 11px;
+
+ .table-item {
+ > div:not(.scrollbar-placeholder) {
+ padding-left: 10px;
+ }
+
+ > div {
+ &.center {
+ align-items: center;
+ }
+ }
+ }
+ }
+
+ &.has-scrollbar:hover {
+ ::ng-deep.cdk-virtual-scroll-content-wrapper {
+ grid-template-columns: auto 1fr 1fr 3fr;
+ }
+ }
+ }
+}
diff --git a/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts
new file mode 100644
index 000000000..bd4ba6d31
--- /dev/null
+++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts
@@ -0,0 +1,120 @@
+import { Component, OnInit } from '@angular/core';
+import { PermissionsService } from '../../../utils/permissions.service';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { FileAttributeConfig, FileAttributesControllerService } from '@redaction/red-ui-http';
+import { AppStateService } from '../../../state/app-state.service';
+import { ActivatedRoute } from '@angular/router';
+import { debounce } from '../../../utils/debounce';
+import { SortingOption, SortingService } from '../../../utils/sorting.service';
+import { DialogService } from '../../../dialogs/dialog.service';
+
+@Component({
+ selector: 'redaction-file-attributes-listing-screen',
+ templateUrl: './file-attributes-listing-screen.component.html',
+ styleUrls: ['./file-attributes-listing-screen.component.scss']
+})
+export class FileAttributesListingScreenComponent implements OnInit {
+ public searchForm: FormGroup;
+ public attributes: FileAttributeConfig[] = [];
+ public displayedAttributes: FileAttributeConfig[] = [];
+ public selectedFileAttributeIds: string[] = [];
+ public viewReady = false;
+
+ constructor(
+ public readonly permissionsService: PermissionsService,
+ public readonly _sortingService: SortingService,
+ private readonly _formBuilder: FormBuilder,
+ private readonly _fileAttributesService: FileAttributesControllerService,
+ private readonly _appStateService: AppStateService,
+ private readonly _activatedRoute: ActivatedRoute,
+ private readonly _dialogService: DialogService
+ ) {
+ this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId);
+
+ this.searchForm = this._formBuilder.group({
+ query: ['']
+ });
+
+ this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value));
+ }
+
+ async ngOnInit() {
+ await this._loadData();
+ }
+
+ private async _loadData() {
+ this.viewReady = false;
+ try {
+ const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise();
+ this.attributes = response?.fileAttributeConfigs || [];
+ } catch (e) {
+ } finally {
+ this._executeSearch();
+ this.viewReady = true;
+ }
+ }
+
+ public get noData(): boolean {
+ return this.displayedAttributes.length === 0;
+ }
+
+ public get sortingOption(): SortingOption {
+ return this._sortingService.getSortingOption('file-attributes-listing');
+ }
+
+ public toggleSort($event) {
+ this._sortingService.toggleSort('file-attributes-listing', $event);
+ }
+
+ @debounce(200)
+ private _executeSearch(value?: { query: string }) {
+ if (!value) {
+ value = { query: this.searchForm.get('query').value };
+ }
+ this.displayedAttributes = this.attributes.filter((attribute) => attribute.label.toLowerCase().includes(value.query.toLowerCase()));
+ }
+
+ public openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
+ $event.stopPropagation();
+ this._dialogService.openAddEditFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async () => {
+ await this._loadData();
+ });
+ }
+
+ public openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) {
+ $event.stopPropagation();
+ this._dialogService.openConfirmDeleteFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async () => {
+ await this._loadData();
+ });
+ }
+
+ public toggleAttributeSelected($event: MouseEvent, attribute: FileAttributeConfig) {
+ $event.stopPropagation();
+ const idx = this.selectedFileAttributeIds.indexOf(attribute.id);
+ if (idx === -1) {
+ this.selectedFileAttributeIds.push(attribute.id);
+ } else {
+ this.selectedFileAttributeIds.splice(idx, 1);
+ }
+ }
+
+ public toggleSelectAll() {
+ if (this.areSomeAttributesSelected) {
+ this.selectedFileAttributeIds = [];
+ } else {
+ this.selectedFileAttributeIds = this.displayedAttributes.map((a) => a.id);
+ }
+ }
+
+ public get areAllAttributesSelected() {
+ return this.displayedAttributes.length !== 0 && this.selectedFileAttributeIds.length === this.displayedAttributes.length;
+ }
+
+ public get areSomeAttributesSelected() {
+ return this.selectedFileAttributeIds.length > 0;
+ }
+
+ public isAttributeSelected(attribute: FileAttributeConfig) {
+ return this.selectedFileAttributeIds.indexOf(attribute.id) !== -1;
+ }
+}
diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts
index 263d57718..1e5081dba 100644
--- a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui-http';
import { AppConfigService } from '../../../app-config/app-config.service';
import * as moment from 'moment';
diff --git a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts
index 798199927..06cc809fa 100644
--- a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts
@@ -2,11 +2,11 @@ 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 { PermissionsService } from '../../../utils/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { debounce } from '../../../utils/debounce';
import { RuleSetModel } from '@redaction/red-ui-http';
-import { UserPreferenceService } from '../../../common/service/user-preference.service';
+import { UserPreferenceService } from '../../../utils/user-preference.service';
@Component({
selector: 'redaction-rule-sets-listing-screen',
diff --git a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts
index 33be7fbc2..21c30d8d9 100644
--- a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts
@@ -1,5 +1,5 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import { AceEditorComponent } from 'ng2-ace-editor';
import { RulesControllerService, RuleSetModel } from '@redaction/red-ui-http';
import { NotificationService, NotificationType } from '../../../notification/notification.service';
diff --git a/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts
index 99d12a890..f987df0de 100644
--- a/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import { UserService } from '../../../user/user.service';
import { User } from '@redaction/red-ui-http';
import { FormBuilder, FormGroup } from '@angular/forms';
diff --git a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html
index ea6c8c85b..d4c0ca1ff 100644
--- a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html
+++ b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html
@@ -5,14 +5,7 @@
-
+
diff --git a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts
index d0238e7f3..c6f6b89f6 100644
--- a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts
+++ b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts
@@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import WebViewer, { WebViewerInstance } from '@pdftron/webviewer';
import { AppStateService } from '../../../state/app-state.service';
import { environment } from '../../../../environments/environment';
diff --git a/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts b/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts
index 9f9e37fd8..e592b5e7f 100644
--- a/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts
+++ b/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts
@@ -2,8 +2,8 @@ import { Component } from '@angular/core';
import { UserService } from '../../user/user.service';
import { AppStateService } from '../../state/app-state.service';
import { LanguageService } from '../../i18n/language.service';
-import { PermissionsService } from '../../common/service/permissions.service';
-import { UserPreferenceService } from '../../common/service/user-preference.service';
+import { PermissionsService } from '../../utils/permissions.service';
+import { UserPreferenceService } from '../../utils/user-preference.service';
import { Router } from '@angular/router';
import { AppConfigService } from '../../app-config/app-config.service';
import { Title } from '@angular/platform-browser';
diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts
index a56185a0d..bb07606c9 100644
--- a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts
+++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts
@@ -1,9 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { AnnotationWrapper } from '../model/annotation.wrapper';
import { AppStateService } from '../../../state/app-state.service';
-import { PermissionsService } from '../../../common/service/permissions.service';
+import { PermissionsService } from '../../../utils/permissions.service';
import { AnnotationPermissions } from '../model/annotation.permissions';
-import { AnnotationActionsService } from '../../../common/service/annotation-actions.service';
+import { AnnotationActionsService } from '../../../utils/annotation-actions.service';
import { Annotations, WebViewerInstance } from '@pdftron/webviewer';
@Component({
diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html
index 94f66155c..60dd47c3d 100644
--- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html
+++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html
@@ -129,7 +129,11 @@