diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index ac4295fd6..d73af6f1f 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -118,6 +118,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { ForceRedactionDialogComponent } from './dialogs/force-redaction-dialog/force-redaction-dialog.component'; import { AuditScreenComponent } from './screens/admin/audit-screen/audit-screen.component'; import { PaginationComponent } from './components/pagination/pagination.component'; +import { FileAttributesListingScreenComponent } from './screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component'; import { SearchInputComponent } from './components/search-input/search-input.component'; export function HttpLoaderFactory(httpClient: HttpClient) { @@ -237,6 +238,14 @@ const routes = [ routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] } }, + { + path: 'file-attributes', + component: FileAttributesListingScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, { path: 'watermark', component: WatermarkScreenComponent, @@ -392,6 +401,7 @@ const matImports = [ ComboSeriesVerticalComponent, AuditScreenComponent, PaginationComponent, + FileAttributesListingScreenComponent, SearchInputComponent ], imports: [ diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html index bdad4ece7..8dcc3efc8 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html @@ -7,7 +7,9 @@ [disabled]="disabled" [class.small]="small" [class.overlay]="showDot" + [class.dummy]="dummy" mat-icon-button + [disableRipple]="dummy" > diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss index b2af7125f..5e1167f4b 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss @@ -36,4 +36,8 @@ button { background-color: $yellow-2; } } + + &.dummy { + cursor: default; + } } diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts index 085885e2a..e8f899791 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts @@ -14,6 +14,7 @@ export class CircleButtonComponent implements OnInit { @Input() disabled = false; @Input() small = false; @Input() type: 'default' | 'primary' | 'warn' | 'dark-bg' = 'default'; + @Input() dummy = false; @Output() action = new EventEmitter(); constructor() {} diff --git a/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.scss b/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.scss index eeb99fae7..e69de29bb 100644 --- a/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.scss +++ b/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.scss @@ -1,7 +0,0 @@ -.action-buttons { - display: flex; - - redaction-circle-button:not(:last-child) { - margin-right: 2px; - } -} diff --git a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html index 0ffea530a..c93576d79 100644 --- a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html +++ b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html @@ -1,7 +1,7 @@
diff --git a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts index d73b195b5..5ddfe29e4 100644 --- a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts +++ b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts @@ -2,6 +2,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AppStateService } from '../../state/app-state.service'; import { UserPreferenceService } from '../../common/service/user-preference.service'; +import { PermissionsService } from '../../common/service/permissions.service'; @Component({ selector: 'redaction-tabs', @@ -9,17 +10,19 @@ import { UserPreferenceService } from '../../common/service/user-preference.serv styleUrls: ['./tabs.component.scss'] }) export class TabsComponent implements OnInit { - @Input() public screen: 'rules' | 'dictionaries' | 'watermark' | 'default-colors'; + @Input() public screen: 'rules' | 'dictionaries' | 'watermark' | 'default-colors' | 'file-attributes'; - public tabs: { screen: string; onlyDevMode?: boolean; label?: string }[] = [ + public tabs: { screen: string; onlyDevMode?: boolean; onlyAdmin?: boolean; label?: string }[] = [ { screen: 'dictionaries' }, { screen: 'rules', onlyDevMode: true, label: 'rule-editor' }, { screen: 'default-colors' }, - { screen: 'watermark' } + { screen: 'watermark' }, + { screen: 'file-attributes', onlyAdmin: true } ]; constructor( public readonly userPreferenceService: UserPreferenceService, + public readonly permissionsService: PermissionsService, private readonly _router: Router, private readonly _appStateService: AppStateService ) {} diff --git a/apps/red-ui/src/app/icons/icons.module.ts b/apps/red-ui/src/app/icons/icons.module.ts index 7a0945248..a4f14eae0 100644 --- a/apps/red-ui/src/app/icons/icons.module.ts +++ b/apps/red-ui/src/app/icons/icons.module.ts @@ -55,6 +55,7 @@ export class IconsModule { 'preview', 'radio-indeterminate', 'radio-selected', + 'read-only', 'ready-for-approval', 'refresh', 'report', diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html index 558ca32e0..695d1740e 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html +++ b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html @@ -42,7 +42,7 @@ {{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }} -
+
-
+
{{ dict.rank }}
-
+
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/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..b56c31b1b --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html @@ -0,0 +1,127 @@ +
+ + +
+
+
+
+
+ + +
+ + + {{ 'file-attributes-listing.table-header.title' | translate: { length: displayedAttributes.length } }} + + +
+ +
+ +
+
+
+ +
+
+ + + + + + + +
+ +
+
+ +
+ + + +
+
+
+ +
+ +
+ {{ attribute.name }} +
+
+ - + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ +
+
+
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..92f4a963f --- /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 1fr 1fr 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 1fr 1fr; + } + } + } +} 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..38e6d3856 --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts @@ -0,0 +1,112 @@ +import { Component, OnInit } from '@angular/core'; +import { PermissionsService } from '../../../common/service/permissions.service'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { FileAttribute, 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'; + +@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: FileAttribute[] = []; + public displayedAttributes: FileAttribute[] = []; + public selectedFileAttributeIds: string[] = []; + + 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 + ) { + this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); + + this.searchForm = this._formBuilder.group({ + query: [''] + }); + + this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); + } + + async ngOnInit() { + try { + const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise(); + this.attributes = response?.fileAttributes || []; + } catch (e) { + // TODO: Remove + this.attributes = [ + { + name: 'Atribut', + editable: true, + id: '1', + visible: true + }, + { + name: 'Alt atribut', + editable: false, + id: '2', + visible: true + } + ]; + } + this.displayedAttributes = [...this.attributes]; + } + + 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 }) { + this.displayedAttributes = this.attributes.filter((attribute) => attribute.name.toLowerCase().includes(value.query.toLowerCase())); + } + + public openAddEditAttributeDialog($event: MouseEvent, attribute?: FileAttribute) { + $event.stopPropagation(); + } + + public toggleAttributeSelected($event: MouseEvent, attribute: FileAttribute) { + $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: FileAttribute) { + return this.selectedFileAttributeIds.indexOf(attribute.id) !== -1; + } +} 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/project-listing-screen/project-listing-actions/project-listing-actions.component.html b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.html index 55303082c..8d93d75d7 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.html +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.html @@ -36,5 +36,5 @@ > - +
diff --git a/apps/red-ui/src/app/utils/sorting.service.ts b/apps/red-ui/src/app/utils/sorting.service.ts index 45ffa97a8..b73629bdb 100644 --- a/apps/red-ui/src/app/utils/sorting.service.ts +++ b/apps/red-ui/src/app/utils/sorting.service.ts @@ -5,7 +5,7 @@ export class SortingOption { column: string; } -type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing' | 'default-colors'; +type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing' | 'default-colors' | 'file-attributes-listing'; @Injectable({ providedIn: 'root' @@ -16,7 +16,8 @@ export class SortingService { 'project-overview': { column: 'filename', order: 'asc' }, 'dictionary-listing': { column: 'label', order: 'asc' }, 'rule-sets-listing': { column: 'name', order: 'asc' }, - 'default-colors': { column: 'key', order: 'asc' } + 'default-colors': { column: 'key', order: 'asc' }, + 'file-attributes-listing': { column: 'name', order: 'asc' } }; constructor() {} diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 2f2404fe9..16d75e479 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -694,6 +694,24 @@ "modified-on": "Modified on" } }, + "file-attributes-listing": { + "search": "Search by attribute name...", + "add-new": "New Attribute", + "table-header": { + "title": "{{length}} file attributes" + }, + "table-col-names": { + "name": "Name", + "created-by": "Created by", + "permissions": "Permissions" + }, + "no-data": "No file attributes.", + "read-only": "Read-only", + "action": { + "edit": "Edit attribute", + "delete": "Delete attribute" + } + }, "user-listing": { "table-header": { "title": "{{length}} users" @@ -748,6 +766,7 @@ }, "rule-editor": "Rule Editor", "watermark": "Watermark", + "file-attributes": "File Attributes", "pending-changes-guard": "WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.", "reset-filters": "Reset Filters", "overwrite-files-dialog": { diff --git a/apps/red-ui/src/assets/icons/general/read-only.svg b/apps/red-ui/src/assets/icons/general/read-only.svg new file mode 100644 index 000000000..ff827078c --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/read-only.svg @@ -0,0 +1,19 @@ + + + 9C81EE87-2992-4087-907E-26A83F796466 + + + + + + + + + + + + + + + + diff --git a/apps/red-ui/src/assets/styles/red-tables.scss b/apps/red-ui/src/assets/styles/red-tables.scss index c5f77f376..34da2ed36 100644 --- a/apps/red-ui/src/assets/styles/red-tables.scss +++ b/apps/red-ui/src/assets/styles/red-tables.scss @@ -80,6 +80,10 @@ cdk-virtual-scroll-viewport { width: 14px; } + redaction-circle-button:not(:last-child) { + margin-right: 2px; + } + &.active { display: flex; // compensate for scroll