Pull request #46: Dictionaries
Merge in RED/ui from dictionaries to master * commit 'f53ad67956d4b73aee3c59c4f0785297ad834ccf': Dictionary listing data Dictionary listing UI updates Dictionaries initial View last reviewer permissions
This commit is contained in:
commit
73a226c3a8
@ -67,7 +67,7 @@ import { AppInfoComponent } from './screens/app-info/app-info.component';
|
||||
import { TableColNameComponent } from './components/table-col-name/table-col-name.component';
|
||||
import { ProjectDetailsComponent } from './screens/project-overview-screen/project-details/project-details.component';
|
||||
import { PageIndicatorComponent } from './screens/file/page-indicator/page-indicator.component';
|
||||
import { NeedsWorkBadgeComponent } from './screens/common/needs-work-badge/needs-work-badge.component';
|
||||
import { NeedsWorkBadgeComponent } from './components/needs-work-badge/needs-work-badge.component';
|
||||
import { ProjectOverviewEmptyComponent } from './screens/empty-states/project-overview-empty/project-overview-empty.component';
|
||||
import { ProjectListingEmptyComponent } from './screens/empty-states/project-listing-empty/project-listing-empty.component';
|
||||
import { AnnotationActionsComponent } from './screens/file/annotation-actions/annotation-actions.component';
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<div [class.hint]="type === 'circle'" [class.request]="type === 'rhombus'" [style.background-color]="color" class="icon">
|
||||
<span>{{ label }}</span>
|
||||
<div [class.hint]="isHint" [class.request]="isRequest" [style.background-color]="color || dictType.hexColor" class="icon">
|
||||
<span>{{ label || dictType.label.charAt(0) }}</span>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { TypeValue } from '@redaction/red-ui-http';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-annotation-icon',
|
||||
@ -9,6 +10,15 @@ export class AnnotationIconComponent {
|
||||
@Input() color: string;
|
||||
@Input() type: 'square' | 'rhombus' | 'circle';
|
||||
@Input() label: string;
|
||||
@Input() dictType: TypeValue;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public get isHint() {
|
||||
return this.type === 'circle' || this.dictType?.type === 'hint';
|
||||
}
|
||||
|
||||
public get isRequest() {
|
||||
return this.type === 'rhombus' || this.dictType?.type === 'redaction';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { AppStateService } from '../../../state/app-state.service';
|
||||
import { PermissionsService } from '../../../common/service/permissions.service';
|
||||
import { FileStatusWrapper } from '../../file/model/file-status.wrapper';
|
||||
import { ProjectWrapper } from '../../../state/model/project.wrapper';
|
||||
import { AppStateService } from '../../state/app-state.service';
|
||||
import { PermissionsService } from '../../common/service/permissions.service';
|
||||
import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper';
|
||||
import { ProjectWrapper } from '../../state/model/project.wrapper';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-needs-work-badge',
|
||||
@ -6,6 +6,7 @@
|
||||
attr.cy="{{ cy }}"
|
||||
attr.r="{{ radius }}"
|
||||
[class]="value.color"
|
||||
[attr.stroke]="value.color.includes('#') ? value.color : ''"
|
||||
attr.stroke-width="{{ strokeWidth }}"
|
||||
attr.stroke-dasharray="{{ circumference }}"
|
||||
attr.stroke-dashoffset="{{ calculateStrokeDashOffset(value.value) }}"
|
||||
@ -16,7 +17,7 @@
|
||||
</svg>
|
||||
|
||||
<div class="text-container" [style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + strokeWidth + 'px;'">
|
||||
<div class="heading-xl">{{ dataTotal }}</div>
|
||||
<div class="heading-xl">{{ displayedDataTotal }}</div>
|
||||
<div class="mt-5">{{ subtitle | translate }}</div>
|
||||
</div>
|
||||
|
||||
@ -29,7 +30,7 @@
|
||||
{
|
||||
length: val.value,
|
||||
color: val.color,
|
||||
label: val.value + ' ' + val.label
|
||||
label: getLabel(val)
|
||||
}
|
||||
]"
|
||||
>
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
@import '../../../assets/styles/red-variables';
|
||||
|
||||
:host {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@ -22,7 +22,10 @@ export class SimpleDoughnutChartComponent implements OnChanges {
|
||||
@Input() strokeWidth = 20;
|
||||
@Input() direction: 'row' | 'column' = 'column';
|
||||
@Input() filter: FilterModel[];
|
||||
@Output() public toggleFilter = new EventEmitter<string>();
|
||||
@Input() totalType: 'sum' | 'count' = 'sum';
|
||||
@Input() counterText: string;
|
||||
@Output()
|
||||
public toggleFilter = new EventEmitter<string>();
|
||||
|
||||
public chartData: any[] = [];
|
||||
public perimeter: number;
|
||||
@ -47,6 +50,10 @@ export class SimpleDoughnutChartComponent implements OnChanges {
|
||||
return this.config.map((v) => v.value).reduce((acc, val) => acc + val, 0);
|
||||
}
|
||||
|
||||
get displayedDataTotal() {
|
||||
return this.totalType === 'sum' ? this.dataTotal : this.config.length;
|
||||
}
|
||||
|
||||
calculateChartData() {
|
||||
const newData = [];
|
||||
let angleOffset = -90;
|
||||
@ -82,4 +89,8 @@ export class SimpleDoughnutChartComponent implements OnChanges {
|
||||
checked: this.filter?.find((f) => f.key === el.key)?.checked
|
||||
}));
|
||||
}
|
||||
|
||||
public getLabel(config: DoughnutChartConfig): string {
|
||||
return this.totalType === 'sum' ? `${config.value} ${config.label}` : `${config.label} (${config.value} ${this.counterText})`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
<div class="rectangle-container" [ngClass]="{ small: small }">
|
||||
<div *ngFor="let rect of config" class="section-wrapper" [style]="'flex: ' + rect.length + ';'">
|
||||
<div [className]="'rectangle ' + rect.color"></div>
|
||||
<div
|
||||
[className]="'rectangle ' + rect.color"
|
||||
[ngStyle]="{
|
||||
'background-color': rect.color.includes('#') ? rect.color : ''
|
||||
}"
|
||||
></div>
|
||||
<div *ngIf="rect.label" [ngClass]="labelClass">{{ rect.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -27,6 +27,7 @@ export class IconsModule {
|
||||
'double-chevron-right',
|
||||
'download',
|
||||
'edit',
|
||||
'entries',
|
||||
'error',
|
||||
'folder',
|
||||
'info',
|
||||
|
||||
@ -1,4 +1,84 @@
|
||||
<section>
|
||||
<h1 class="heading-xl">Under Construction</h1>
|
||||
<mat-icon svgIcon="red:under-construction"></mat-icon>
|
||||
<div class="page-header">
|
||||
<div class="section" translate="dictionaries"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="grid-container">
|
||||
<div class="header-item span-4">
|
||||
<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: 0 } }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Table column names-->
|
||||
<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>
|
||||
|
||||
<!-- Table lines -->
|
||||
<div class="table-item" *ngFor="let dict of dictionaries | sortBy: sortingOption.order:sortingOption.column">
|
||||
<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>
|
||||
<div class="table-item-title heading">
|
||||
{{ dict.label }}
|
||||
</div>
|
||||
<div class="small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:entries"></mat-icon>
|
||||
300
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="analyzed">
|
||||
<redaction-annotation-icon [dictType]="dict"></redaction-annotation-icon>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-fixed-container">
|
||||
<redaction-simple-doughnut-chart
|
||||
[config]="chartData"
|
||||
[strokeWidth]="15"
|
||||
[radius]="82"
|
||||
[subtitle]="'dictionary-listing.stats.charts.types'"
|
||||
totalType="count"
|
||||
[counterText]="'dictionary-listing.stats.charts.entries' | translate"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -1,17 +1,54 @@
|
||||
@import '../../../../assets/styles/red-variables';
|
||||
@import '../../../../assets/styles/red-mixins';
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $red-1;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
height: calc(100vh - 61px);
|
||||
.left-container {
|
||||
width: calc(100vw - 353px);
|
||||
|
||||
mat-icon {
|
||||
color: $grey-1;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
.grid-container {
|
||||
grid-template-columns: auto 1fr 1fr 2fr;
|
||||
|
||||
.header-item {
|
||||
padding: 0 24px 0 10px;
|
||||
}
|
||||
|
||||
redaction-table-col-name::ng-deep {
|
||||
> div {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-item {
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 10px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.color-square {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.analyzed {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-subtitle {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-fixed-container {
|
||||
overflow-y: scroll;
|
||||
@include no-scroll-bar();
|
||||
display: flex;
|
||||
width: calc(353px);
|
||||
height: calc(100vh - 110px - 2 * 50px);
|
||||
justify-content: center;
|
||||
padding: 50px 0;
|
||||
}
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { 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';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-dictionary-listing-screen',
|
||||
@ -6,7 +11,71 @@ import { Component, OnInit } from '@angular/core';
|
||||
styleUrls: ['./dictionary-listing-screen.component.scss']
|
||||
})
|
||||
export class DictionaryListingScreenComponent implements OnInit {
|
||||
constructor() {}
|
||||
public chartData: DoughnutChartConfig[] = [];
|
||||
public dictionaries: TypeValue[];
|
||||
public selectedDictKeys: string[] = [];
|
||||
|
||||
ngOnInit(): void {}
|
||||
constructor(
|
||||
private readonly _dialogService: DialogService,
|
||||
private readonly _sortingService: SortingService,
|
||||
private readonly _appStateService: AppStateService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this._appStateService.reset();
|
||||
const dicts = this._appStateService.dictionaryData;
|
||||
this.dictionaries = Object.keys(dicts).map((key) => dicts[key]);
|
||||
this._calculateData();
|
||||
}
|
||||
|
||||
private _calculateData() {
|
||||
this.chartData = [];
|
||||
for (const dict of this.dictionaries) {
|
||||
this.chartData.push({
|
||||
value: 100,
|
||||
color: dict.hexColor,
|
||||
label: dict.label,
|
||||
key: dict.type
|
||||
});
|
||||
}
|
||||
this.chartData.sort((a, b) => (a.label < b.label ? -1 : 1));
|
||||
}
|
||||
|
||||
public get sortingOption(): SortingOption {
|
||||
return this._sortingService.getSortingOption('dictionary-listing');
|
||||
}
|
||||
|
||||
public toggleSort($event) {
|
||||
this._sortingService.toggleSort('dictionary-listing', $event);
|
||||
}
|
||||
|
||||
toggleDictSelected($event: MouseEvent, dict: TypeValue) {
|
||||
$event.stopPropagation();
|
||||
const idx = this.selectedDictKeys.indexOf(dict.type);
|
||||
if (idx === -1) {
|
||||
this.selectedDictKeys.push(dict.type);
|
||||
} else {
|
||||
this.selectedDictKeys.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public toggleSelectAll() {
|
||||
if (this.areSomeDictsSelected) {
|
||||
this.selectedDictKeys = [];
|
||||
} else {
|
||||
this.selectedDictKeys = this.dictionaries.map((dict) => dict.type);
|
||||
}
|
||||
}
|
||||
|
||||
public get areAllDictsSelected() {
|
||||
return this.dictionaries.length !== 0 && this.selectedDictKeys.length === this.dictionaries.length;
|
||||
}
|
||||
|
||||
public get areSomeDictsSelected() {
|
||||
return this.selectedDictKeys.length > 0;
|
||||
}
|
||||
|
||||
public isDictSelected(dict: TypeValue) {
|
||||
return this.selectedDictKeys.indexOf(dict.type) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
</redaction-circle-button>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!!appStateService.activeFile.fileStatus.lastReviewer">
|
||||
<ng-container *ngIf="permissionsService.isManagerAndOwner() && !!appStateService.activeFile.fileStatus.lastReviewer">
|
||||
<div class="vertical-line"></div>
|
||||
<div class="all-caps-label mr-16 ml-8" translate="file-preview.last-reviewer"></div>
|
||||
<redaction-initials-avatar [userId]="appStateService.activeFile.fileStatus.lastReviewer" [withName]="true"></redaction-initials-avatar>
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
</div>
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="grid-container bulk-select">
|
||||
<div class="grid-container">
|
||||
<div class="header-item span-4">
|
||||
<div class="select-all-container">
|
||||
<div
|
||||
@ -106,7 +106,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Table column names-->
|
||||
<div class="select-oval-placeholder"></div>
|
||||
<div class="select-oval-placeholder placeholder-bottom-border"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
(toggleSort)="toggleSort($event)"
|
||||
|
||||
@ -6,11 +6,7 @@
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
grid-template-columns: 3fr 2fr 1fr 1fr 2fr auto;
|
||||
|
||||
&.bulk-select {
|
||||
grid-template-columns: auto 3fr 2fr 1fr 1fr 2fr auto;
|
||||
}
|
||||
grid-template-columns: auto 3fr 2fr 1fr 1fr 2fr auto;
|
||||
|
||||
.header-item {
|
||||
padding: 0 24px 0 10px;
|
||||
|
||||
@ -81,7 +81,7 @@ export class AppStateService {
|
||||
return this._appState.ruleVersion;
|
||||
}
|
||||
|
||||
get dictionaryData() {
|
||||
get dictionaryData(): { [key: string]: TypeValue } {
|
||||
return this._dictionaryData;
|
||||
}
|
||||
|
||||
|
||||
@ -5,18 +5,21 @@ export class SortingOption {
|
||||
column: string;
|
||||
}
|
||||
|
||||
type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SortingService {
|
||||
private _options: { [key: string]: SortingOption } = {
|
||||
'project-listing': { column: 'project.projectName', order: 'asc' },
|
||||
'project-overview': { column: 'filename', order: 'asc' }
|
||||
'project-overview': { column: 'filename', order: 'asc' },
|
||||
'dictionary-listing': { column: 'label', order: 'asc' }
|
||||
};
|
||||
|
||||
constructor() {}
|
||||
|
||||
public toggleSort(screen: 'project-listing' | 'project-overview', column: string) {
|
||||
public toggleSort(screen: Screen, column: string) {
|
||||
if (this._options[screen].column === column) {
|
||||
const currentOrder = this._options[screen].order;
|
||||
this._options[screen].order = currentOrder === 'asc' ? 'desc' : 'asc';
|
||||
@ -25,7 +28,7 @@ export class SortingService {
|
||||
}
|
||||
}
|
||||
|
||||
public getSortingOption(screen: 'project-listing' | 'project-overview') {
|
||||
public getSortingOption(screen: Screen) {
|
||||
return this._options[screen];
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,5 +466,21 @@
|
||||
"title": "Confirm deletion",
|
||||
"question": "Do you wish to proceed?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dictionary-listing": {
|
||||
"stats": {
|
||||
"charts": {
|
||||
"types": "Types",
|
||||
"entries": "Entries"
|
||||
}
|
||||
},
|
||||
"table-header": {
|
||||
"title": "{{length}} dictionaries"
|
||||
},
|
||||
"table-col-names": {
|
||||
"type": "Type",
|
||||
"hint-redaction": "Hint/Redaction"
|
||||
}
|
||||
},
|
||||
"dictionaries": "Dictionaries"
|
||||
}
|
||||
|
||||
17
apps/red-ui/src/assets/icons/general/entries.svg
Normal file
17
apps/red-ui/src/assets/icons/general/entries.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?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>D2E1BFB9-9086-46E0-98F5-F3E2898811B0</title>
|
||||
<g id="Settings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="01.-Dictionaries" transform="translate(-72.000000, -241.000000)">
|
||||
<rect x="0" y="0" width="1440" height="705"></rect>
|
||||
<g id="Dictionary" transform="translate(0.000000, 193.000000)">
|
||||
<rect id="Rectangle" x="0" y="0" width="1086" height="80"></rect>
|
||||
<g id="Group-16" transform="translate(72.000000, 46.000000)" fill="currentColor" fill-rule="nonzero">
|
||||
<g id="status" transform="translate(0.000000, 2.000000)">
|
||||
<path d="M5,7.5 L5,8.5 L0,8.5 L0,7.5 L5,7.5 Z M10,5.5 L10,6.5 L0,6.5 L0,5.5 L10,5.5 Z M10,3.5 L10,4.5 L0,4.5 L0,3.5 L10,3.5 Z M10,1.5 L10,2.5 L0,2.5 L0,1.5 L10,1.5 Z" id="Combined-Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -23,9 +23,6 @@
|
||||
grid-column-end: span 3;
|
||||
}
|
||||
|
||||
&.span-7 {
|
||||
grid-column-end: span 7;
|
||||
}
|
||||
&.span-4 {
|
||||
grid-column-end: span 4;
|
||||
}
|
||||
|
||||
@ -62,6 +62,11 @@ body {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ redaction-table-col-name,
|
||||
.grid-container {
|
||||
display: grid;
|
||||
|
||||
.select-oval-placeholder {
|
||||
.placeholder-bottom-border {
|
||||
border-bottom: 1px solid $separator;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user