Pull request #11: Ui updates
Merge in RED/ui from ui-updates to master * commit 'f97f9bac62d797f15471d917294eb71f9fa44163': Filter annotations logic Annotations filter menu Quick navigation list items File preview close tabs Added translations Download file button View toggle
This commit is contained in:
commit
661b9cf4f2
@ -52,6 +52,7 @@ import { AppStateGuard } from './state/app-state.guard';
|
||||
import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
import { ManualRedactionDialogComponent } from './screens/file/manual-redaction-dialog/manual-redaction-dialog.component';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
|
||||
export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||
@ -134,6 +135,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||
NgpSortModule,
|
||||
MatToolbarModule,
|
||||
MatButtonModule,
|
||||
MatSlideToggleModule,
|
||||
MatMenuModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
|
||||
@ -10,7 +10,7 @@ export class InitialsAvatarComponent implements OnInit {
|
||||
public username: string;
|
||||
|
||||
@Input()
|
||||
public color: 'red-white' | 'gray-red' | 'gray-dark' = 'gray-dark';
|
||||
public color = 'lightgray-dark';
|
||||
|
||||
@Input()
|
||||
public size: 'small' | 'large' = 'small';
|
||||
|
||||
@ -1,19 +1,29 @@
|
||||
<section class="" [class.hidden]="!viewReady">
|
||||
<div class="page-header">
|
||||
<div class="flex-row">
|
||||
<div class="btn-group">
|
||||
<button class="btn-group-btn" [class.active]="activeViewer === 'ANNOTATED'"
|
||||
(click)="activateViewer('ANNOTATED')"
|
||||
translate="file-preview.annotated.label">
|
||||
</button>
|
||||
<button class="btn-group-btn" [class.active]="activeViewer === 'REDACTED'"
|
||||
(click)="activateViewer('REDACTED')"
|
||||
translate="file-preview.redacted.label">
|
||||
</button>
|
||||
</div>
|
||||
<mat-slide-toggle color="primary"
|
||||
[(ngModel)]="redactedView"
|
||||
translate="file-preview.view-toggle.label"></mat-slide-toggle>
|
||||
<div>
|
||||
<button color="primary" mat-flat-button
|
||||
[matMenuTriggerFor]="downloadMenu">
|
||||
<span translate="file-preview.download.label"></span>
|
||||
<mat-icon class="dropdown-icon">arrow_drop_down</mat-icon>
|
||||
</button>
|
||||
<mat-menu #downloadMenu="matMenu" xPosition="before">
|
||||
<div mat-menu-item
|
||||
translate="file-preview.download.dropdown.original.label"
|
||||
(click)="downloadFile('original')"
|
||||
></div>
|
||||
<div mat-menu-item
|
||||
translate="file-preview.download.dropdown.annotated.label"
|
||||
(click)="downloadFile('annotated')"
|
||||
></div>
|
||||
<div mat-menu-item
|
||||
translate="file-preview.download.dropdown.redacted.label"
|
||||
(click)="downloadFile('redacted')"
|
||||
></div>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<button color="warn" mat-flat-button
|
||||
translate="file-preview.download-redacted.label"></button>
|
||||
</div>
|
||||
|
||||
<div class="flex red-content-inner">
|
||||
@ -36,31 +46,49 @@
|
||||
<!-- Quick navigation tab-->
|
||||
<div class="vertical" (click)="selectTab('NAVIGATION')" [ngClass]="{ active: navigationTab}"
|
||||
#navigationTabElement>
|
||||
<div class="tab-title" [ngClass]="navigationTab ? 'heading' : 'subheading'">
|
||||
Quick Navigation
|
||||
<div class="tab-title" [ngClass]="navigationTab ? 'heading' : 'subheading'"
|
||||
translate="file-preview.tabs.quick-navigation.label">
|
||||
<div>
|
||||
<button color="accent" mat-button
|
||||
[matMenuTriggerFor]="filterMenu" [ngClass]="{ overlay: hasActiveFilters }">
|
||||
<span translate="file-preview.filter-menu.label"></span>
|
||||
<mat-icon class="dropdown-icon">arrow_drop_down</mat-icon>
|
||||
</button>
|
||||
<div class="dot" *ngIf="hasActiveFilters"></div>
|
||||
<mat-menu class="filter-menu" #filterMenu="matMenu" xPosition="before">
|
||||
<div class="filter-menu-header">
|
||||
<div class="subheading" translate="file-preview.filter-menu.filter-types.label"></div>
|
||||
<div class="actions">
|
||||
<div class="subheading primary pointer" translate="file-preview.filter-menu.all.label"
|
||||
(click)="setAllFilters(true); $event.stopPropagation();"></div>
|
||||
<div class="subheading primary pointer" translate="file-preview.filter-menu.none.label"
|
||||
(click)="setAllFilters(false); $event.stopPropagation();"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mat-menu-item" *ngFor="let key of filterKeys" (click)="$event.stopPropagation()">
|
||||
<mat-checkbox [(ngModel)]="filters[key].value" color="primary">
|
||||
<div [class]="filters[key].class + ' x-small'">
|
||||
{{ filters[key].symbol }}
|
||||
</div>
|
||||
{{filters[key].label | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" [class.not-visible]="!navigationTab">
|
||||
<div *ngFor="let item of quickNavigation | sortBy:'asc':'number'"
|
||||
class="page-navigation"
|
||||
[id]="'quick-nav-page-'+item.pageNumber"
|
||||
[ngClass]="{ active: item.pageNumber === activeViewerPage }"
|
||||
[ngClass]="{ active: item.pageNumber === activeViewerPage, hidden: !showQuickNavigationItem(item) }"
|
||||
(click)="selectPage(item.pageNumber)"
|
||||
>
|
||||
<div class="page-number">Page {{ item.pageNumber }}</div>
|
||||
<div class="stats-subtitle subtitle mt-5">
|
||||
<div *ngIf="item.redactions" class="center">
|
||||
<mat-icon svgIcon="red:secret"></mat-icon>
|
||||
{{item.redactions}} Redactions
|
||||
</div>
|
||||
<div *ngIf="item.hints" class="center">
|
||||
<mat-icon svgIcon="red:idea"></mat-icon>
|
||||
{{item.hints}} Hints
|
||||
</div>
|
||||
<div *ngIf="item.hints" class="center">
|
||||
<mat-icon svgIcon="red:info"></mat-icon>
|
||||
{{item.ignore}} Ignore
|
||||
</div>
|
||||
<div class="page-number subtitle">{{ item.pageNumber }}</div>
|
||||
<div *ngFor="let key of filterKeys"
|
||||
[ngClass]="{ hidden: !showAnnotations(item, key)}" class="page-stats subtitle">
|
||||
<div [class]="filters[key].class + ' x-small'">{{ filters[key].symbol }}</div>
|
||||
{{item[key]}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,8 +97,12 @@
|
||||
<!-- Annotations tab-->
|
||||
<div (click)="selectTab('ANNOTATIONS')" class="vertical" [ngClass]="{ active: annotationsTab }"
|
||||
#annotationsContainer>
|
||||
<div class="tab-title" [ngClass]="annotationsTab ? 'heading' : 'subheading'">
|
||||
Annotations
|
||||
<div class="tab-title" [ngClass]="annotationsTab ? 'heading' : 'subheading'"
|
||||
translate="file-preview.tabs.annotations.label">
|
||||
<mat-icon class="close-icon"
|
||||
*ngIf="annotationsTab"
|
||||
(click)="selectTab('NAVIGATION', $event)"
|
||||
svgIcon="red:close"></mat-icon>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" [class.not-visible]="!annotationsTab">
|
||||
@ -86,7 +118,7 @@
|
||||
|
||||
<div class="annotation-actions">
|
||||
<button mat-icon-button (click)="suggestRemoveAnnotation($event, annotation)">
|
||||
<mat-icon svgIcon="red:delete" ></mat-icon>
|
||||
<mat-icon svgIcon="red:delete"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -95,8 +127,12 @@
|
||||
|
||||
<!-- Info tab-->
|
||||
<div class="vertical" (click)="selectTab('INFO')" [ngClass]="{ active: infoTab}">
|
||||
<div class="tab-title" [ngClass]="infoTab ? 'heading' : 'subheading'">
|
||||
Info
|
||||
<div class="tab-title" [ngClass]="infoTab ? 'heading' : 'subheading'"
|
||||
translate="file-preview.tabs.info.label">
|
||||
<mat-icon class="close-icon"
|
||||
*ngIf="infoTab"
|
||||
(click)="selectTab('NAVIGATION', $event)"
|
||||
svgIcon="red:close"></mat-icon>
|
||||
</div>
|
||||
|
||||
<div *ngIf="infoTab" class="tab-content info-container">
|
||||
@ -115,25 +151,22 @@
|
||||
|
||||
<div class="flex-row mt-20">
|
||||
<redaction-initials-avatar size="large" color="red-white"></redaction-initials-avatar>
|
||||
<a class="assign-reviewer">Assign Reviewer</a>
|
||||
<a class="assign-reviewer" translate="file-preview.tabs.info.assign-reviewer.label"></a>
|
||||
</div>
|
||||
|
||||
<div class="subheading mt-20">
|
||||
Added on
|
||||
<div class="subheading mt-20" translate="file-preview.tabs.info.added-on.label">
|
||||
</div>
|
||||
<div class="subtitle mt-5">
|
||||
{{appStateService.activeFile.added | date:'medium'}}
|
||||
</div>
|
||||
|
||||
<div class="subheading mt-20">
|
||||
Added by
|
||||
<div class="subheading mt-20" translate="file-preview.tabs.info.added-by.label">
|
||||
</div>
|
||||
<div class="subtitle mt-5">
|
||||
{{user.name}}
|
||||
</div>
|
||||
|
||||
<div class="subheading mt-20">
|
||||
Last modified on
|
||||
<div class="subheading mt-20" translate="file-preview.tabs.info.last-modified-on.label">
|
||||
</div>
|
||||
<div class="subtitle mt-5">
|
||||
{{appStateService.activeFile.lastUpdated | date:'medium'}}
|
||||
|
||||
@ -29,7 +29,28 @@ redaction-pdf-viewer {
|
||||
display: flex;
|
||||
border-bottom: 1px solid $separator;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 25px;
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
|
||||
.dot {
|
||||
background: $primary;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
@ -84,7 +105,7 @@ redaction-pdf-viewer {
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-left: 2px solid $red-1;
|
||||
border-left: 2px solid $primary;
|
||||
}
|
||||
|
||||
.annotation-actions {
|
||||
@ -98,21 +119,48 @@ redaction-pdf-viewer {
|
||||
}
|
||||
|
||||
.page-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid $separator;
|
||||
padding: 14px;
|
||||
border-left: 4px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: #F4F5F7;
|
||||
background-color: $grey-2;
|
||||
}
|
||||
|
||||
.page-number {
|
||||
font-weight: 600;
|
||||
border: 1px solid $separator;
|
||||
padding: 7px;
|
||||
margin-right: 3px;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-left: 4px solid $red-1;
|
||||
border-left: 4px solid $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-menu-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 250px;
|
||||
padding: 7px 15px 15px;
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {
|
||||
FileUploadControllerService,
|
||||
ManualRedactionControllerService,
|
||||
@ -7,20 +7,31 @@ import {
|
||||
ProjectControllerService,
|
||||
StatusControllerService
|
||||
} from '@redaction/red-ui-http';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {NotificationService, NotificationType} from '../../../notification/notification.service';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {AppStateService} from '../../../state/app-state.service';
|
||||
import {FileDetailsDialogComponent} from './file-details-dialog/file-details-dialog.component';
|
||||
import {ViewerSyncService} from '../service/viewer-sync.service';
|
||||
import {Annotations} from '@pdftron/webviewer';
|
||||
import {PdfViewerComponent} from '../pdf-viewer/pdf-viewer.component';
|
||||
import {AnnotationUtils} from '../../../utils/annotation-utils';
|
||||
import {ManualRedactionDialogComponent} from "../manual-redaction-dialog/manual-redaction-dialog.component";
|
||||
import {UserService} from "../../../user/user.service";
|
||||
import {debounce} from "../../../utils/debounce";
|
||||
import scrollIntoView from 'scroll-into-view-if-needed'
|
||||
import {ConfirmationDialogComponent} from "../../../common/confirmation-dialog/confirmation-dialog.component";
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationService, NotificationType } from '../../../notification/notification.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AppStateService } from '../../../state/app-state.service';
|
||||
import { FileDetailsDialogComponent } from './file-details-dialog/file-details-dialog.component';
|
||||
import { ViewerSyncService } from '../service/viewer-sync.service';
|
||||
import { Annotations } from '@pdftron/webviewer';
|
||||
import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
|
||||
import { AnnotationUtils } from '../../../utils/annotation-utils';
|
||||
import { ManualRedactionDialogComponent } from '../manual-redaction-dialog/manual-redaction-dialog.component';
|
||||
import { UserService } from '../../../user/user.service';
|
||||
import { debounce } from '../../../utils/debounce';
|
||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||
import { ConfirmationDialogComponent } from '../../../common/confirmation-dialog/confirmation-dialog.component';
|
||||
import { AnnotationFilters } from '../../../utils/types';
|
||||
import { FiltersService } from '../service/filters.service';
|
||||
|
||||
class QuickNavigationItem {
|
||||
pageNumber: number;
|
||||
hints: number;
|
||||
redactions: number;
|
||||
comments: number;
|
||||
suggestions: number;
|
||||
ignored: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-file-preview-screen',
|
||||
@ -31,6 +42,7 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
private _readyViewers: string[] = [];
|
||||
private projectId: string;
|
||||
private _selectedTab: 'NAVIGATION' | 'ANNOTATIONS' | 'INFO' = 'NAVIGATION';
|
||||
private _activeViewer: 'ANNOTATED' | 'REDACTED' = 'ANNOTATED';
|
||||
|
||||
@ViewChild(PdfViewerComponent)
|
||||
private _viewerComponent: PdfViewerComponent;
|
||||
@ -44,7 +56,13 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
public fileId: string;
|
||||
public annotations: Annotations.Annotation[] = [];
|
||||
public selectedAnnotation: Annotations.Annotation;
|
||||
public quickNavigation: { pageNumber: number, redactions: number, hints: number, ignore: number; }[] = [];
|
||||
public quickNavigation: QuickNavigationItem[] = [];
|
||||
|
||||
public filters: AnnotationFilters;
|
||||
|
||||
public get filterKeys() {
|
||||
return Object.keys(this.filters);
|
||||
}
|
||||
|
||||
private _manualRedactionEntry: ManualRedactionEntry;
|
||||
|
||||
@ -63,18 +81,29 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
private readonly _manualRedactionControllerService: ManualRedactionControllerService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _fileUploadControllerService: FileUploadControllerService,
|
||||
private readonly _projectControllerService: ProjectControllerService) {
|
||||
private readonly _projectControllerService: ProjectControllerService,
|
||||
private readonly _filtersService: FiltersService) {
|
||||
this._activatedRoute.params.subscribe(params => {
|
||||
this.projectId = params.projectId;
|
||||
this.fileId = params.fileId;
|
||||
this.appStateService.activateFile(this.projectId, this.fileId)
|
||||
this.appStateService.activateFile(this.projectId, this.fileId);
|
||||
});
|
||||
this.filters = _filtersService.filters;
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this._userService.user;
|
||||
}
|
||||
|
||||
public get redactedView() {
|
||||
return this._activeViewer === 'REDACTED';
|
||||
}
|
||||
|
||||
public set redactedView(value: boolean) {
|
||||
this._activeViewer = value ? 'REDACTED' : 'ANNOTATED';
|
||||
this._activateViewer(this._activeViewer);
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
// PDFTRON cache fix
|
||||
localStorage.clear();
|
||||
@ -103,11 +132,14 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
return this._viewerSyncService.activeViewer;
|
||||
}
|
||||
|
||||
public activateViewer(value: string) {
|
||||
private _activateViewer(value: string) {
|
||||
this._viewerSyncService.activateViewer(value);
|
||||
}
|
||||
|
||||
public selectTab(value: 'ANNOTATIONS' | 'INFO' | 'NAVIGATION') {
|
||||
public selectTab(value: 'ANNOTATIONS' | 'INFO' | 'NAVIGATION', $event?: MouseEvent) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
if (value !== this._selectedTab) {
|
||||
this._selectedTab = value;
|
||||
setTimeout(() => {
|
||||
@ -124,14 +156,14 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
const pageNumber = annotation.getPageNumber();
|
||||
let el = this.quickNavigation.find((page) => page.pageNumber === pageNumber);
|
||||
if (!el) {
|
||||
el = {pageNumber, redactions: 0, hints: 0, ignore: 0}
|
||||
el = { pageNumber, redactions: 0, hints: 0, ignored: 0, comments: 0, suggestions: 0 };
|
||||
this.quickNavigation.push(el);
|
||||
}
|
||||
if (annotation.Id.startsWith('hint:')) {
|
||||
el.hints++;
|
||||
}
|
||||
if (annotation.Id.startsWith('ignore:')) {
|
||||
el.ignore++;
|
||||
el.ignored++;
|
||||
}
|
||||
if (annotation.Id.startsWith('redaction:')) {
|
||||
el.redactions++;
|
||||
@ -141,6 +173,16 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
this.annotations = AnnotationUtils.sortAnnotations(this.annotations);
|
||||
}
|
||||
|
||||
public showQuickNavigationItem(item: QuickNavigationItem): boolean {
|
||||
let showItem = false;
|
||||
Object.keys(this.filters).map((key) => {
|
||||
if (this.showAnnotations(item, key)) {
|
||||
showItem = true;
|
||||
}
|
||||
})
|
||||
return showItem;
|
||||
}
|
||||
|
||||
public handleAnnotationSelected(annotation: Annotations.Annotation) {
|
||||
this.selectedAnnotation = annotation;
|
||||
this.selectTab('ANNOTATIONS');
|
||||
@ -191,7 +233,7 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
|
||||
ref.afterClosed().subscribe(() => {
|
||||
this._manualRedactionEntry = null;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
viewerPageChanged(pageNumber: number) {
|
||||
@ -228,8 +270,8 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
behavior: 'smooth',
|
||||
scrollMode: 'if-needed',
|
||||
block: 'center',
|
||||
inline: 'center',
|
||||
})
|
||||
inline: 'center'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,12 +320,36 @@ export class FilePreviewScreenComponent implements OnInit {
|
||||
id: annotation.Id,
|
||||
removeFromDictionary: false
|
||||
}]
|
||||
}, this.appStateService.activeProjectId, this.appStateService.activeFile.fileId).subscribe(ok=>{
|
||||
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.success.label'), null, NotificationType.SUCCESS);
|
||||
},(err)=>{
|
||||
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.failed.label',err), null, NotificationType.ERROR);
|
||||
});
|
||||
}, this.appStateService.activeProjectId, this.appStateService.activeFile.fileId).subscribe(ok => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.success.label'), null, NotificationType.SUCCESS);
|
||||
}, (err) => {
|
||||
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.failed.label', err), null, NotificationType.ERROR);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public downloadFile(type: 'original' | 'annotated' | 'redacted') {
|
||||
console.log(`Downloading ${type}...`);
|
||||
}
|
||||
|
||||
public setAllFilters(value: boolean) {
|
||||
Object.keys(this.filters).map((key) => {
|
||||
this.filters[key].value = value;
|
||||
});
|
||||
}
|
||||
|
||||
public get hasActiveFilters(): boolean {
|
||||
let activeFilters = false;
|
||||
Object.keys(this.filters).map((key) => {
|
||||
if (this.filters[key].value) {
|
||||
activeFilters = true;
|
||||
}
|
||||
});
|
||||
return activeFilters;
|
||||
}
|
||||
|
||||
public showAnnotations(item: QuickNavigationItem, type: string): boolean {
|
||||
return item[type] && (!this.hasActiveFilters || (this.hasActiveFilters && this.filters[type]?.value));
|
||||
}
|
||||
}
|
||||
|
||||
22
apps/red-ui/src/app/screens/file/service/filters.service.ts
Normal file
22
apps/red-ui/src/app/screens/file/service/filters.service.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AnnotationFilters } from '../../../utils/types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FiltersService {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
private _filters: AnnotationFilters = {
|
||||
hints: { label: 'file-preview.filter-menu.hint.label', value: false, class: 'oval darkgray-white', symbol: 'H' },
|
||||
redactions: { label: 'file-preview.filter-menu.redaction.label', value: false, class: 'square darkgray-white', symbol: 'R' },
|
||||
comments: { label: 'file-preview.filter-menu.comment.label', value: false, class: 'oval darkgray-white', symbol: 'C' },
|
||||
suggestions: { label: 'file-preview.filter-menu.suggestion.label', value: false, class: 'oval red-white', symbol: 'S' },
|
||||
ignored: { label: 'file-preview.filter-menu.ignored.label', value: false, class: 'oval lightgray-white', symbol: 'I' },
|
||||
}
|
||||
|
||||
public get filters(): AnnotationFilters {
|
||||
return JSON.parse(JSON.stringify(this._filters));
|
||||
}
|
||||
}
|
||||
@ -7,23 +7,23 @@
|
||||
<div translate="filters.project.label"></div>
|
||||
<div translate="filters.document.label"></div>
|
||||
</div>
|
||||
<button (click)="openAddProjectDialog()" color="warn" mat-flat-button class="add-project-btn">
|
||||
<button (click)="openAddProjectDialog()" color="primary" mat-flat-button class="add-project-btn">
|
||||
<mat-icon svgIcon="red:plus">
|
||||
</mat-icon>
|
||||
<span translate="projects.add-new.label"></span>
|
||||
<span translate="project-listing.add-new.label"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="appStateService.allProjects?.length === 0 " translate="projects.no-projects.label"></div>
|
||||
<div *ngIf="appStateService.allProjects?.length === 0 " translate="project-listing.no-projects.label"></div>
|
||||
|
||||
<div class="flex red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="table-header">
|
||||
<span class="subheading">
|
||||
{{'projects.table-header.title.label'| translate:{ length: appStateService.allProjects?.length || 0 } }}
|
||||
{{'project-listing.table-header.title.label'| translate:{ length: appStateService.allProjects?.length || 0 } }}
|
||||
</span>
|
||||
<div class="actions">
|
||||
<div translate="projects.table-header.bulk-select.label"></div>
|
||||
<div translate="project-listing.table-header.bulk-select.label"></div>
|
||||
<mat-form-field appearance="none" class="red-select">
|
||||
<mat-select [(ngModel)]="sortingOption" panelClass="red-select-panel">
|
||||
<mat-option *ngFor="let option of sortingOptions" [value]="option">
|
||||
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<redaction-initials-avatar [username]="user.name"
|
||||
color="gray-red"
|
||||
color="lightgray-red"
|
||||
withName="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
@ -88,7 +88,7 @@
|
||||
<div>
|
||||
<redaction-simple-doughnut-chart [config]="projectsChartData"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'Projects'"
|
||||
[subtitle]="'project-listing.stats.charts.projects.label' | translate"
|
||||
></redaction-simple-doughnut-chart>
|
||||
|
||||
<div class="project-stats-container">
|
||||
@ -96,7 +96,7 @@
|
||||
<mat-icon svgIcon="red:files"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">{{totalPages}}</div>
|
||||
<div>Analyzed pages</div>
|
||||
<div translate="project-listing.stats.analyzed-pages.label"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -104,7 +104,7 @@
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">240</div>
|
||||
<div>Total people</div>
|
||||
<div translate="project-listing.stats.total-people.label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -112,7 +112,7 @@
|
||||
<div>
|
||||
<redaction-simple-doughnut-chart [config]="documentsChartData"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'Total Documents'"
|
||||
[subtitle]="'project-listing.stats.charts.total-documents.label' | translate"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,8 +20,8 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
public projectsChartData: DoughnutChartConfig [] = [];
|
||||
public documentsChartData: DoughnutChartConfig [] = [];
|
||||
public sortingOptions: SortingOption[] = [
|
||||
{ label: 'projects.sorting.recent.label', order: 'desc', column: 'projectDate' },
|
||||
{ label: 'projects.sorting.alphabetically.label', order: 'asc', column: 'project.projectName' }
|
||||
{ label: 'project-listing.sorting.recent.label', order: 'desc', column: 'projectDate' },
|
||||
{ label: 'project-listing.sorting.alphabetically.label', order: 'asc', column: 'project.projectName' }
|
||||
];
|
||||
public sortingOption: SortingOption = this.sortingOptions[0];
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<div translate="filters.due-date.label"></div>
|
||||
<div translate="filters.document.label"></div>
|
||||
</div>
|
||||
<button (click)="fileInput.click()" color="warn" mat-flat-button
|
||||
<button (click)="fileInput.click()" color="primary" mat-flat-button
|
||||
translate="project-overview.upload-document.label"></button>
|
||||
<input #fileInput (change)="uploadFiles($event.target.files)" class="file-upload-input" multiple="true"
|
||||
type="file">
|
||||
@ -120,7 +120,7 @@
|
||||
<div class="owner flex-row mt-20">
|
||||
<redaction-initials-avatar [username]="user.name"
|
||||
size="large"
|
||||
color="gray-red"
|
||||
color="lightgray-red"
|
||||
withName="true"
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
@ -136,10 +136,10 @@
|
||||
<redaction-initials-avatar [username]="username" size="large"></redaction-initials-avatar>
|
||||
</div>
|
||||
<div class="member">
|
||||
<div class="oval large">+2</div>
|
||||
<div class="oval large white-dark">+2</div>
|
||||
</div>
|
||||
<div class="member">
|
||||
<div class="oval red large">+</div>
|
||||
<div class="oval red-white large">+</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
8
apps/red-ui/src/app/utils/types.d.ts
vendored
8
apps/red-ui/src/app/utils/types.d.ts
vendored
@ -15,3 +15,11 @@ export class SortingOption {
|
||||
column: string;
|
||||
}
|
||||
|
||||
export class AnnotationFilters {
|
||||
[key: string]: {
|
||||
label: string,
|
||||
value: boolean,
|
||||
class: string,
|
||||
symbol: string
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@
|
||||
"label": "Document"
|
||||
}
|
||||
},
|
||||
"projects": {
|
||||
"project-listing": {
|
||||
"table-header": {
|
||||
"title": {
|
||||
"label": "{{length}} active projects"
|
||||
@ -145,6 +145,22 @@
|
||||
"label": "Recent"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"analyzed-pages": {
|
||||
"label": "Analyzed pages"
|
||||
},
|
||||
"total-people": {
|
||||
"label": "Total people"
|
||||
},
|
||||
"charts": {
|
||||
"projects": {
|
||||
"label": "Projects"
|
||||
},
|
||||
"total-documents": {
|
||||
"label": "Total Documents"
|
||||
}
|
||||
}
|
||||
},
|
||||
"add-edit-dialog": {
|
||||
"header-new": {
|
||||
"label": "New Project"
|
||||
@ -323,14 +339,72 @@
|
||||
}
|
||||
},
|
||||
"file-preview": {
|
||||
"annotated": {
|
||||
"label": "Review Annotations"
|
||||
"view-toggle": {
|
||||
"label": "Redacted View"
|
||||
},
|
||||
"redacted": {
|
||||
"label": "Preview Redaction"
|
||||
"filter-menu": {
|
||||
"label": "Filter",
|
||||
"all": {
|
||||
"label": "All"
|
||||
},
|
||||
"none": {
|
||||
"label": "None"
|
||||
},
|
||||
"filter-types": {
|
||||
"label": "Filter types"
|
||||
},
|
||||
"hint": {
|
||||
"label": "Hint annotation"
|
||||
},
|
||||
"redaction": {
|
||||
"label": "Redaction"
|
||||
},
|
||||
"comment": {
|
||||
"label": "Comment"
|
||||
},
|
||||
"suggestion": {
|
||||
"label": "Suggested redaction"
|
||||
},
|
||||
"ignored": {
|
||||
"label": "Ignored redaction"
|
||||
}
|
||||
},
|
||||
"download-redacted": {
|
||||
"label": "Download Redacted"
|
||||
"tabs": {
|
||||
"quick-navigation": {
|
||||
"label": "Quick Navigation"
|
||||
},
|
||||
"annotations": {
|
||||
"label": "Annotations"
|
||||
},
|
||||
"info": {
|
||||
"label": "Info",
|
||||
"assign-reviewer": {
|
||||
"label": "Assign reviewer"
|
||||
},
|
||||
"added-on": {
|
||||
"label": "Added on"
|
||||
},
|
||||
"added-by": {
|
||||
"label": "Added by"
|
||||
},
|
||||
"last-modified-on": {
|
||||
"label": "Last modified on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"download": {
|
||||
"label": "Download",
|
||||
"dropdown": {
|
||||
"original": {
|
||||
"label": "Original"
|
||||
},
|
||||
"annotated": {
|
||||
"label": "Annotated"
|
||||
},
|
||||
"redacted": {
|
||||
"label": "Redacted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"initials-avatar": {
|
||||
|
||||
20
apps/red-ui/src/assets/styles/red-button.scss
Normal file
20
apps/red-ui/src/assets/styles/red-button.scss
Normal file
@ -0,0 +1,20 @@
|
||||
@import "red-variables";
|
||||
|
||||
.mat-button, .mat-flat-button {
|
||||
font-family: Inter, sans-serif !important;
|
||||
font-weight: 400 !important;
|
||||
border-radius: 25px !important;
|
||||
|
||||
.mat-button-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-button[aria-expanded="true"], .mat-button.overlay {
|
||||
background: rgba($primary, 0.1);
|
||||
}
|
||||
12
apps/red-ui/src/assets/styles/red-checkbox.scss
Normal file
12
apps/red-ui/src/assets/styles/red-checkbox.scss
Normal file
@ -0,0 +1,12 @@
|
||||
@import "red-variables";
|
||||
|
||||
.mat-checkbox .mat-checkbox-frame {
|
||||
border: 1px solid $grey-5;
|
||||
}
|
||||
|
||||
.mat-checkbox-indeterminate.mat-accent .mat-checkbox-background, .mat-checkbox-checked.mat-accent .mat-checkbox-background {
|
||||
margin-top: 1px;
|
||||
margin-left: 1px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
@ -1,44 +1,62 @@
|
||||
@import "red-variables";
|
||||
|
||||
.oval {
|
||||
.oval, .square {
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
border: 1px solid #E2E4E9;
|
||||
letter-spacing: 0;
|
||||
line-height: 12px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-family: Inter, sans-serif;
|
||||
border: none;
|
||||
|
||||
&.large {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&.gray-dark {
|
||||
background-color: $grey-4;
|
||||
border: none;
|
||||
&.x-small {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
&.gray-red {
|
||||
&.lightgray-dark {
|
||||
background-color: $grey-4;
|
||||
}
|
||||
|
||||
&.lightgray-red {
|
||||
background-color: $grey-4;
|
||||
color: $red-1;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.darkgray-white {
|
||||
background-color: $grey-1;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.lightgray-white {
|
||||
background-color: $grey-5;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&.red-white {
|
||||
background-color: $red-1;
|
||||
color: $white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.white-dark {
|
||||
border: 1px solid #E2E4E9;
|
||||
}
|
||||
}
|
||||
|
||||
.oval {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.stats-subtitle {
|
||||
|
||||
@ -12,13 +12,13 @@
|
||||
height: 6px;
|
||||
width: 16px;
|
||||
border-radius: 3px;
|
||||
background-color: $red-1;
|
||||
background-color: $primary;
|
||||
}
|
||||
|
||||
.line-2 {
|
||||
height: 6px;
|
||||
width: 22px;
|
||||
border-radius: 6px;
|
||||
background-color: $red-1;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
30
apps/red-ui/src/assets/styles/red-menu.scss
Normal file
30
apps/red-ui/src/assets/styles/red-menu.scss
Normal file
@ -0,0 +1,30 @@
|
||||
@import "red-variables";
|
||||
|
||||
.mat-menu-panel {
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 2px 6px 0 rgba(40, 50, 65, 0.3);
|
||||
|
||||
.mat-menu-item {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 13px;
|
||||
color: $accent;
|
||||
|
||||
.mat-checkbox-layout {
|
||||
width: 100%;
|
||||
|
||||
.mat-checkbox-inner-container {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mat-checkbox-label {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 13px;
|
||||
color: $accent;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,7 +167,7 @@ html, body {
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
color: $red-1;
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -42,11 +42,11 @@
|
||||
line-height: 14px;
|
||||
|
||||
&.mat-selected.mat-active {
|
||||
color: $red-1;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #F4F5F7 !important;
|
||||
background: $grey-2 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,11 @@
|
||||
@import "red-variables";
|
||||
@import "red-mixins";
|
||||
|
||||
button {
|
||||
font-family: Inter, sans-serif !important;
|
||||
font-weight: 400 !important;
|
||||
border-radius: 17px !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $red-1;
|
||||
color: $primary;
|
||||
|
||||
&:hover {
|
||||
color: lighten($red-1, 10%)
|
||||
color: lighten($primary, 10%)
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
@ -57,3 +51,8 @@ a {
|
||||
.clamp-2 {
|
||||
@include line-clamp(2);
|
||||
}
|
||||
|
||||
.primary {
|
||||
color: $primary;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@ -1,12 +1,17 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@300;400;500;600;700&display=swap');
|
||||
@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
|
||||
@import '~ngx-toastr/toastr';
|
||||
@import "red-material-theme";
|
||||
@import "red-page-layout";
|
||||
@import "red-text-styles";
|
||||
@import "red-dialog";
|
||||
@import "red-input";
|
||||
@import "red-button";
|
||||
@import "red-select";
|
||||
@import "red-checkbox";
|
||||
@import "red-toggle";
|
||||
@import "red-menu";
|
||||
@import "red-media-queries";
|
||||
@import "red-tables";
|
||||
@import "red-components";
|
||||
|
||||
23
apps/red-ui/src/assets/styles/red-toggle.scss
Normal file
23
apps/red-ui/src/assets/styles/red-toggle.scss
Normal file
@ -0,0 +1,23 @@
|
||||
mat-slide-toggle {
|
||||
display: flex !important;
|
||||
flex-direction: row-reverse;
|
||||
gap: 8px;
|
||||
|
||||
.mat-slide-toggle-bar {
|
||||
height: 16px !important;
|
||||
width: 30px !important;
|
||||
border-radius: 16px !important;
|
||||
}
|
||||
|
||||
.mat-slide-toggle-thumb-container {
|
||||
top: 2px !important;
|
||||
left: 2px !important;
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
}
|
||||
|
||||
.mat-slide-toggle-thumb {
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ $light: #FFF;
|
||||
$dark: #000;
|
||||
|
||||
$grey-1: #283241;
|
||||
$grey-2: #ECECEE;
|
||||
$grey-2: #F4F5F7;
|
||||
$grey-3: #AAACB3;
|
||||
$grey-4: #E2E4E9;
|
||||
$grey-5: #D3D5DA;
|
||||
@ -16,7 +16,7 @@ $blue-1: #4875F7;
|
||||
$blue-2: #48C9F7;
|
||||
$blue-3: #5B97DB;
|
||||
$blue-4: #374C81;
|
||||
$red-1: #F65757;
|
||||
$red-1: #DD4D50;
|
||||
$yellow-1: #FFB83B;
|
||||
$green-1: #46CE7D;
|
||||
$green-2: #5CE594;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user