File attributes listing

This commit is contained in:
Adina Țeudan 2021-03-19 22:00:36 +02:00
parent 7d2e9a7b7a
commit ac8fa0ff52
19 changed files with 370 additions and 28 deletions

View File

@ -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: [

View File

@ -7,7 +7,9 @@
[disabled]="disabled"
[class.small]="small"
[class.overlay]="showDot"
[class.dummy]="dummy"
mat-icon-button
[disableRipple]="dummy"
>
<mat-icon [svgIcon]="icon"></mat-icon>
</button>

View File

@ -36,4 +36,8 @@ button {
background-color: $yellow-2;
}
}
&.dummy {
cursor: default;
}
}

View File

@ -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<any>();
constructor() {}

View File

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

View File

@ -1,7 +1,7 @@
<ng-container *ngFor="let tab of tabs">
<div
class="red-tab"
*ngIf="!tab.onlyDevMode || userPreferenceService.areDevFeaturesEnabled"
*ngIf="(!tab.onlyAdmin || permissionsService.isAdmin()) && (!tab.onlyDevMode || userPreferenceService.areDevFeaturesEnabled)"
(click)="switchView(tab.screen)"
[class.active]="tab.screen === screen"
>

View File

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

View File

@ -55,6 +55,7 @@ export class IconsModule {
'preview',
'radio-indeterminate',
'radio-selected',
'read-only',
'ready-for-approval',
'refresh',
'report',

View File

@ -42,7 +42,7 @@
{{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }}
</span>
<div class="dictionary-actions-container">
<div class="attributes-actions-container">
<redaction-search-input [form]="searchForm" [placeholder]="'dictionary-listing.search'"></redaction-search-input>
<div class="actions">
<redaction-icon-button
@ -111,11 +111,11 @@
</div>
</div>
<div class="rank small-label">
<div class="center small-label">
{{ dict.rank }}
</div>
<div class="analyzed">
<div class="center">
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
</div>

View File

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

View File

@ -0,0 +1,127 @@
<section>
<div class="page-header">
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
<redaction-tabs [screen]="'file-attributes'"></redaction-tabs>
<div class="actions flex-1">
<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
(click)="toggleSelectAll()"
[class.active]="areAllAttributesSelected"
class="select-oval always-visible"
*ngIf="!areAllAttributesSelected && !areSomeAttributesSelected"
></div>
<mat-icon
*ngIf="areAllAttributesSelected"
(click)="toggleSelectAll()"
class="selection-icon active"
svgIcon="red:radio-selected"
></mat-icon>
<mat-icon
*ngIf="areSomeAttributesSelected && !areAllAttributesSelected"
(click)="toggleSelectAll()"
class="selection-icon"
svgIcon="red:radio-indeterminate"
></mat-icon>
</div>
<span class="all-caps-label">
{{ 'file-attributes-listing.table-header.title' | translate: { length: displayedAttributes.length } }}
</span>
<div class="attributes-actions-container">
<redaction-search-input [form]="searchForm" [placeholder]="'file-attributes-listing.search'"></redaction-search-input>
<div class="actions">
<redaction-icon-button
icon="red:plus"
(action)="openAddEditAttributeDialog($event)"
text="file-attributes-listing.add-new"
type="primary"
></redaction-icon-button>
</div>
</div>
</div>
<div class="table-header" redactionSyncWidth="table-item">
<div class="select-oval-placeholder"></div>
<redaction-table-col-name
label="file-attributes-listing.table-col-names.name"
(toggleSort)="toggleSort($event)"
[activeSortingOption]="sortingOption"
[withSort]="true"
column="name"
></redaction-table-col-name>
<redaction-table-col-name label="file-attributes-listing.table-col-names.created-by" class="flex-center"></redaction-table-col-name>
<redaction-table-col-name label="file-attributes-listing.table-col-names.permissions" class="flex-center"></redaction-table-col-name>
<div></div>
<div class="scrollbar-placeholder"></div>
</div>
<div *ngIf="noData" class="no-data heading-l" translate="file-attributes-listing.no-data"></div>
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
<!-- Table lines -->
<div class="table-item" *cdkVirtualFor="let attribute of displayedAttributes | sortBy: sortingOption.order:sortingOption.column">
<div class="pr-0" (click)="toggleAttributeSelected($event, attribute)">
<div *ngIf="!isAttributeSelected(attribute)" class="select-oval"></div>
<mat-icon class="selection-icon active" *ngIf="isAttributeSelected(attribute)" svgIcon="red:radio-selected"></mat-icon>
</div>
<div>
{{ attribute.name }}
</div>
<div class="center">
-
<!-- TODO-->
<!-- <redaction-initials-avatar [userId]="attribute.userId" [withName]="true" size="large"></redaction-initials-avatar>-->
</div>
<div class="center">
<redaction-circle-button
*ngIf="!attribute.editable"
type="dark-bg"
icon="red:read-only"
tooltip="file-attributes-listing.read-only"
[dummy]="true"
></redaction-circle-button>
</div>
<div class="actions-container">
<div class="action-buttons">
<redaction-circle-button
(action)="openAddEditAttributeDialog($event, attribute)"
tooltip="file-attributes-listing.action.edit"
tooltipPosition="before"
type="dark-bg"
icon="red:edit"
>
</redaction-circle-button>
<redaction-circle-button
(action)="openAddEditAttributeDialog($event, attribute)"
tooltip="file-attributes-listing.action.delete"
tooltipPosition="before"
type="dark-bg"
icon="red:trash"
>
</redaction-circle-button>
</div>
</div>
<div class="scrollbar-placeholder"></div>
</div>
</cdk-virtual-scroll-viewport>
</div>
<div class="right-container"></div>
</div>
</section>

View File

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

View File

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

View File

@ -5,14 +5,7 @@
<redaction-tabs [screen]="'watermark'"></redaction-tabs>
<div class="actions flex-1">
<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>

View File

@ -36,5 +36,5 @@
>
</redaction-circle-button>
<redaction-file-download-btn [file]="project.files" [project]="project"> </redaction-file-download-btn>
<redaction-file-download-btn [file]="project.files" [project]="project" type="dark-bg"> </redaction-file-download-btn>
</div>

View File

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

View File

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

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>9C81EE87-2992-4087-907E-26A83F796466</title>
<g id="File-attributes" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Admin---File-attributes-list" transform="translate(-1154.000000, -212.000000)">
<rect x="0" y="0" width="1440" height="900"></rect>
<polygon id="Rectangle" points="0 194 1430 194 1430 244 0 244"></polygon>
<g id="Permission" transform="translate(1124.000000, 171.000000)" fill="currentColor" fill-rule="nonzero">
<g id="icon-read-only" transform="translate(20.000000, 31.000000)">
<g id="read-only" transform="translate(10.000000, 10.000000)">
<g id="noun_Lock_791668-(1)" transform="translate(1.400000, 0.000000)">
<path d="M9.8,5.6 L9.1,5.6 L9.1,3.5 C9.1,1.54 7.56,0 5.6,0 C3.64,0 2.1,1.54 2.1,3.5 L2.1,5.6 L1.4,5.6 C0.63,5.6 0,6.23 0,7 L0,12.6 C0,13.37 0.63,14 1.4,14 L9.8,14 C10.57,14 11.2,13.37 11.2,12.6 L11.2,7 C11.2,6.23 10.57,5.6 9.8,5.6 Z M3.5,3.5 C3.5,2.31 4.41,1.4 5.6,1.4 C6.79,1.4 7.7,2.31 7.7,3.5 L7.7,5.6 L3.5,5.6 L3.5,3.5 Z M1.4,12.6 L1.4,7 L9.8,7 L9.8,12.6 L1.4,12.6 Z" id="Shape"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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