Pull request #13: Ui updates

Merge in RED/ui from ui-updates to master

* commit '43510a448f08cf48a6e2c8a235069ca415b2e626':
  post rebase code fixes
  Expandable filters
  Subfilters
  Cleanup some styles
  File preview right panel & annotation icons
  Project overview chart
This commit is contained in:
Timo Bejan 2020-10-19 09:58:34 +02:00
commit d18ab65a66
27 changed files with 629 additions and 497 deletions

View File

@ -53,6 +53,7 @@ import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart
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';
import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -76,6 +77,7 @@ export function HttpLoaderFactory(httpClient: HttpClient) {
LogoComponent,
SimpleDoughnutChartComponent,
ManualRedactionDialogComponent,
AnnotationIconComponent,
],
imports: [
BrowserModule,

View File

@ -0,0 +1,3 @@
<div [ngClass]="type" class="icon">
<span>{{ type[0] }}</span>
</div>

View File

@ -0,0 +1,64 @@
@import "../../../assets/styles/red-variables";
.icon {
height: 16px;
width: 16px;
font-size: 11px;
line-height: 14px;
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
text-transform: uppercase;
color: $white;
}
.suggestion {
width: 0;
height: 0;
border: 9px solid transparent;
border-bottom-color: $grey-1;
position: relative;
top: -9px;
&:after {
content: '';
position: absolute;
left: -9px;
top: 9px;
width: 0;
height: 0;
border: 9px solid transparent;
border-top-color: $grey-1;
}
span {
transform: translateY(9px);
z-index: 2;
}
}
.hint, .comment, .ignore {
border-radius: 50%;
}
.hint, .redaction, .comment {
background-color: $grey-1;
}
.ignore {
background-color: $grey-5;
}
.hint_only {
background-color: $orange-1;
}
.vertebrate {
background-color: $green-1;
}
.names {
background-color: $yellow-2;
}

View File

@ -0,0 +1,16 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'redaction-annotation-icon',
templateUrl: './annotation-icon.component.html',
styleUrls: ['./annotation-icon.component.scss']
})
export class AnnotationIconComponent implements OnInit {
@Input() public type: 'hint' | 'redaction' | 'suggestion' | 'ignore' | 'comment';
constructor() {
}
ngOnInit(): void {
}
}

View File

@ -1,4 +1,4 @@
<div class="container">
<div [class]="'container flex ' + direction">
<svg attr.height="{{size}}" attr.width="{{size}}" attr.viewBox="0 0 {{size}} {{size}}" class="donut-chart">
<g *ngFor="let value of parsedConfig; let i = index">
<circle attr.cx="{{cx}}"
@ -13,16 +13,16 @@
</g>
</svg>
<div class="text-container" [style]="'height: ' + size + 'px; width: ' + size + 'px;'">
<div class="text-container" [style]="'height: ' + size + 'px; width: ' + size + 'px; padding: ' + strokeWidth + 'px;'">
<div class="heading-xl">{{ dataTotal }}</div>
<div class="projects-text mt-5">{{ subtitle }}</div>
<div class="projects-text mt-5">{{ subtitle | translate }}</div>
</div>
<div class="mt-20 breakdown-container">
<div class="breakdown-container">
<div>
<div *ngFor="let val of parsedConfig">
<redaction-status-bar [small]="true"
[config]="[{ length: val.value, color: val.color, label: val.value + ' ' + val.label}]">
[config]="[{ length: val.value, color: val.color, label: val.value + ' ' + (val.label | translate | lowercase) }]">
</redaction-status-bar>
</div>
</div>

View File

@ -3,8 +3,12 @@
.container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
&.column {
flex-direction: column;
}
}
.text-container {
@ -14,6 +18,7 @@
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
}
.breakdown-container {

View File

@ -19,6 +19,7 @@ export class SimpleDoughnutChartComponent implements OnInit {
@Input() angleOffset = -90;
@Input() radius = 85;
@Input() strokeWidth = 20;
@Input() direction: 'row' | 'column' = 'column';
public chartData: any[] = [];
public perimeter: number;

View File

@ -15,7 +15,7 @@ export class IconsModule {
) {
const icons = [
'add', 'analyse', 'arrow-down', 'arrow-up', 'assign', 'calendar', 'check',
'close', 'document', 'double-chevron-right', 'download', 'drop-down-arrow',
'close', 'document', 'double-chevron-right', 'download',
'edit', 'error', 'folder', 'info', 'lightning', 'logout', 'menu', 'pages',
'plus', 'preview', 'refresh', 'report', 'secret', 'sort-asc', 'sort-desc',
'status', 'trash', 'user'

View File

@ -40,11 +40,8 @@
</div>
<div class="menu right">
<button [matMenuTriggerFor]="menu" mat-button>
<div class="account-button-wrapper">
<redaction-initials-avatar color="red-white" size="small" [username]="user?.name"></redaction-initials-avatar>
<span>{{user?.name}}</span>
<mat-icon svgIcon="red:drop-down-arrow"></mat-icon>
</div>
<redaction-initials-avatar color="red-white" size="small" [username]="user?.name" [withName]="true"></redaction-initials-avatar>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #menu="matMenu">

View File

@ -1,17 +0,0 @@
@import "../../../assets/styles/red-variables";
.account-button-wrapper {
display: flex;
justify-content: center;
align-items: center;
redaction-initials-avatar{
margin-right: 6px;
}
mat-icon {
width: 10px;
margin-left: 6px;
}
}

View File

@ -1,4 +1,4 @@
<section class="" [class.hidden]="!viewReady">
<section [class.hidden]="!viewReady">
<div class="page-header">
<mat-slide-toggle color="primary"
[(ngModel)]="redactedView"
@ -7,7 +7,7 @@
<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>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #downloadMenu="matMenu" xPosition="before">
<div mat-menu-item
@ -42,134 +42,98 @@
</div>
<div class="right-fixed-container">
<!-- Quick navigation tab-->
<div class="vertical" (click)="selectTab('NAVIGATION')" [ngClass]="{ active: navigationTab}"
#navigationTabElement>
<div class="tab-title" [ngClass]="navigationTab ? 'heading' : 'all-caps-label'"
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="all-caps-label" translate="file-preview.filter-menu.filter-types.label"></div>
<div class="actions">
<div class="all-caps-label primary pointer" translate="file-preview.filter-menu.all.label"
(click)="setAllFilters(true); $event.stopPropagation();"></div>
<div class="all-caps-label primary pointer" translate="file-preview.filter-menu.none.label"
(click)="setAllFilters(false); $event.stopPropagation();"></div>
</div>
<div class="right-title heading"
translate="file-preview.tabs.annotations.label">
<div>
<button color="accent" mat-button
[matMenuTriggerFor]="filterMenu" [ngClass]="{ overlay: hasActiveFilters }">
<span translate="file-preview.filter-menu.label"></span>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<div class="dot" *ngIf="hasActiveFilters"></div>
<mat-menu #filterMenu="matMenu" xPosition="before">
<div class="filter-menu-header">
<div class="all-caps-label" translate="file-preview.filter-menu.filter-types.label"></div>
<div class="actions">
<div class="all-caps-label primary pointer" translate="file-preview.filter-menu.all.label"
(click)="setAllFilters(filters, true); applyFilters(); $event.stopPropagation();"></div>
<div class="all-caps-label primary pointer" translate="file-preview.filter-menu.none.label"
(click)="setAllFilters(filters, false); applyFilters(); $event.stopPropagation();"></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 }}
</div>
<div *ngFor="let key of filterKeys()">
<div class="mat-menu-item flex" (click)="$event.stopPropagation()">
<div class="arrow-wrapper" *ngIf="hasSubsections(filters[key])">
<mat-icon *ngIf="expandedFilters[key]" (click)="setExpanded(key, false, $event)">arrow_drop_down</mat-icon>
<mat-icon *ngIf="!expandedFilters[key]" (click)="setExpanded(key, true, $event)">arrow_right</mat-icon>
</div>
<mat-checkbox [checked]="isChecked(key)"
[indeterminate]="isIndeterminate(key)"
(change)="setAllFilters(filters[key], $event.checked, hasSubsections(filters[key]) ? null : key)"
color="primary">
<redaction-annotation-icon [type]="key"></redaction-annotation-icon>
{{"file-preview.filter-menu." + 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, hidden: !showQuickNavigationItem(item) }"
(click)="selectPage(item.pageNumber)"
>
<div class="page-number small-label">{{ item.pageNumber }}</div>
<div *ngFor="let key of filterKeys"
[ngClass]="{ hidden: !showAnnotations(item, key)}" class="page-stats small-label">
<div [class]="filters[key].class + ' x-small'">{{ filters[key].symbol }}</div>
{{item[key]}}
<div *ngIf="hasSubsections(filters[key]) && expandedFilters[key]">
<div *ngFor="let subkey of filterKeys(key)"
class="padding-left mat-menu-item"
(click)="$event.stopPropagation()"
>
<mat-checkbox [(ngModel)]="filters[key][subkey]" (change)="applyFilters()" color="primary">
<redaction-annotation-icon [type]="key + ' ' + subkey"></redaction-annotation-icon>
{{"file-preview.filter-menu." + key + "." + subkey + ".label" | translate }}
</mat-checkbox>
</div>
</div>
</div>
</div>
</mat-menu>
</div>
</div>
<!-- Annotations tab-->
<div (click)="selectTab('ANNOTATIONS')" class="vertical" [ngClass]="{ active: annotationsTab }"
#annotationsContainer>
<div class="tab-title" [ngClass]="annotationsTab ? 'heading' : 'all-caps-label'"
translate="file-preview.tabs.annotations.label">
<mat-icon class="close-icon"
*ngIf="annotationsTab"
(click)="selectTab('NAVIGATION', $event)"
svgIcon="red:close"></mat-icon>
<div class="right-content">
<div class="pages" #quickNavigation>
<div class="page-number pointer"
[ngClass]="{ active: pageNumber === activeViewerPage }"
*ngFor="let pageNumber of displayedPages"
[id]="'quick-nav-page-'+pageNumber" (click)="selectPage(pageNumber)">
{{pageNumber}}
</div>
</div>
<div class="tab-content" [class.not-visible]="!annotationsTab">
<div *ngFor="let annotation of annotations"
class="annotation" attr.annotation-id="{{annotation.Id}}"
attr.annotation-page="{{annotation.getPageNumber()}}"
(click)="selectAnnotation(annotation)"
[ngClass]="{ active: selectedAnnotation === annotation }">
<div><strong>Type: </strong>{{getType(annotation.Id)}}</div>
<div><strong>Dictionary: </strong>{{getDictionary(annotation.Id)}}</div>
<div><strong>Page: </strong> {{annotation.getPageNumber()}}</div>
<div *ngIf="annotation.getContents()"><strong>Content: </strong>{{annotation.getContents()}}</div>
<div class="annotation-actions">
<button mat-icon-button (click)="suggestRemoveAnnotation($event, annotation)">
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<div class="annotations" #annotations>
<div *ngFor="let page of displayedPages">
<div class="page-separator">
<span class="all-caps-label"><span translate="page"></span> {{page}}</span>
</div>
</div>
</div>
</div>
<!-- Info tab-->
<div class="vertical" (click)="selectTab('INFO')" [ngClass]="{ active: infoTab}">
<div class="tab-title" [ngClass]="infoTab ? 'heading' : 'all-caps-label'"
translate="file-preview.tabs.info.label">
<mat-icon class="close-icon"
*ngIf="infoTab"
(click)="selectTab('NAVIGATION', $event)"
svgIcon="red:close"></mat-icon>
</div>
<div *ngFor="let annotation of displayedAnnotations[page].annotations"
class="annotation" attr.annotation-id="{{annotation.Id}}"
attr.annotation-page="{{page}}"
[ngClass]="{ active: selectedAnnotation === annotation }"
(click)="selectAnnotation(annotation)"
>
<div *ngIf="infoTab" class="tab-content info-container">
<redaction-status-bar [small]="true"
labelClass="small-label"
[config]="[{ length: 1, label: 'Unassigned', color: 'unassigned'}]"></redaction-status-bar>
<redaction-annotation-icon
[type]="getType(annotation) + ' ' + getDictionary(annotation)"></redaction-annotation-icon>
<div class="flex-1">
<div><strong>{{getType(annotation) | translate}}</strong></div>
<div><strong><span translate="dictionary"></span>: </strong>{{getDictionary(annotation)}}</div>
<div *ngIf="annotation.getContents()"><strong><span translate="content"></span>:
</strong>{{annotation.getContents()}}</div>
</div>
<div class="small-label stats-subtitle mt-5">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{appStateService.activeFile.numberOfPages}}</div>
<div>
<mat-icon svgIcon="red:analyse"></mat-icon>
{{annotations.length}}</div>
</div>
<div class="page-number">
{{annotation.getPageNumber()}}
</div>
<div class="flex-row mt-20">
<redaction-initials-avatar size="large" color="red-white"></redaction-initials-avatar>
<a class="assign-reviewer" translate="file-preview.tabs.info.assign-reviewer.label"></a>
</div>
<div class="all-caps-label mt-20" translate="file-preview.tabs.info.added-on.label">
</div>
<div class="small-label mt-5">
{{appStateService.activeFile.added | date:'medium'}}
</div>
<div class="all-caps-label mt-20" translate="file-preview.tabs.info.added-by.label">
</div>
<div class="small-label mt-5">
{{user.name}}
</div>
<div class="all-caps-label mt-20" translate="file-preview.tabs.info.last-modified-on.label">
</div>
<div class="small-label mt-5">
{{appStateService.activeFile.lastUpdated | date:'medium'}}
<div class="annotation-actions">
<button mat-icon-button>
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -13,142 +13,129 @@ redaction-pdf-viewer {
.right-fixed-container {
padding: 0;
width: calc(#{$right-container-width} - 1px);
display: flex;
width: $right-container-width;
box-sizing: border-box;
.vertical {
height: 100%;
border-right: 1px solid $separator;
.right-title {
height: 70px;
display: flex;
border-bottom: 1px solid $separator;
align-items: center;
justify-content: space-between;
padding: 0 24px;
&.active {
width: calc(#{$right-container-width} - 80px);
padding-top: 0;
> div {
position: relative;
.tab-title {
height: 70px;
display: flex;
.dot {
background: $primary;
height: 10px;
width: 10px;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
}
}
.close-icon {
height: 14px;
width: 14px;
cursor: pointer;
}
}
.right-content {
height: calc(100vh - 110px - 72px);
box-sizing: border-box;
display: flex;
.pages, .annotations {
overflow-y: scroll;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
width: 0;
background: transparent; /* Chrome/Safari/Webkit */
}
}
.pages {
border-right: 1px solid $separator;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 16px;
min-width: 28px;
}
.annotations {
width: 100%;
.page-separator {
border-bottom: 1px solid $separator;
align-items: center;
justify-content: space-between;
padding: 0 25px;
height: 40px;
box-sizing: border-box;
padding: 8px 10px;
display: flex;
align-items: flex-end;
background-color: $grey-6;
}
> div {
position: relative;
.annotation {
border-bottom: 1px solid $separator;
padding: 10px;
font-size: 12px;
cursor: pointer;
position: relative;
display: flex;
gap: 10px;
.dot {
background: $primary;
height: 10px;
width: 10px;
border-radius: 50%;
position: absolute;
top: 0;
left: 0;
redaction-annotation-icon {
margin-top: 6px;
}
&:hover {
background-color: #F9FAFB;
.annotation-actions {
display: flex;
}
}
.close-icon {
height: 14px;
width: 14px;
cursor: pointer;
&.active {
border-left: 2px solid $primary;
}
.annotation-actions {
position: absolute;
right: 0;
top: 0;
bottom: 0;
display: none;
width: 40px;
}
}
.tab-content {
overflow-y: scroll;
overflow-x: hidden;
height: calc(100vh - 110px - 73px);
box-sizing: border-box;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
width: 0;
background: transparent; /* Chrome/Safari/Webkit */
}
}
.info-container {
padding: 15px;
}
}
&:not(.active) {
width: 40px;
padding-top: 15px;
cursor: pointer;
.tab-title {
transform: translateX(27px) rotate(90deg);
transform-origin: 0 0;
position: absolute;
}
}
}
.assign-reviewer {
margin-left: 12px;
}
.annotation {
border-bottom: 1px solid $separator;
padding: 14px;
font-size: 12px;
cursor: pointer;
position: relative;
&:hover {
background-color: #F9FAFB;
.annotation-actions {
display: flex;
}
}
&.active {
border-left: 2px solid $primary;
}
.annotation-actions {
position: absolute;
right: 0;
top: 0;
bottom: 0;
display: none;
width: 40px;
}
}
.page-navigation {
.page-number {
border: 1px solid $separator;
padding: 5px 10px;
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
cursor: pointer;
border-bottom: 1px solid $separator;
padding: 14px;
border-left: 4px solid transparent;
&:hover {
background-color: $grey-2;
}
.page-number {
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;
}
height: 28px;
min-height: 28px;
min-width: 14px;
opacity: 0.7;
font-size: 11px;
line-height: 14px;
&.active {
border-left: 4px solid $primary;
border: 1px solid $primary;
}
}
}
@ -156,8 +143,8 @@ redaction-pdf-viewer {
.filter-menu-header {
display: flex;
justify-content: space-between;
width: 250px;
padding: 7px 15px 15px;
width: 350px;
.actions {
display: flex;

View File

@ -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 {
AddRedactionRequest,
FileUploadControllerService,
@ -8,34 +8,25 @@ 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 { 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';
import {FileDownloadService} from "../service/file-download.service";
import {saveAs} from 'file-saver';
import {FileType} from "../model/file-type";
class QuickNavigationItem {
pageNumber: number;
hints: number;
redactions: number;
comments: number;
suggestions: number;
ignored: number;
}
import { AnnotationFilters } from '../../../utils/types';
import { FiltersService } from '../service/filters.service';
import { FileDownloadService } from '../service/file-download.service';
import { saveAs } from 'file-saver';
import { FileType } from '../model/file-type';
import { ConfirmationDialogComponent } from '../../../common/confirmation-dialog/confirmation-dialog.component';
@Component({
selector: 'redaction-file-preview-screen',
@ -45,32 +36,20 @@ class QuickNavigationItem {
export class FilePreviewScreenComponent implements OnInit {
private _readyViewers: string[] = [];
private projectId: string;
private _selectedTab: 'NAVIGATION' | 'ANNOTATIONS' | 'INFO' = 'NAVIGATION';
private _activeViewer: 'ANNOTATED' | 'REDACTED' = 'ANNOTATED';
private _manualRedactionEntry: ManualRedactionEntry;
@ViewChild(PdfViewerComponent)
private _viewerComponent: PdfViewerComponent;
@ViewChild('annotationsContainer')
private _annotationsContainer: ElementRef;
@ViewChild('navigationTabElement')
private _navigationTabElement: ElementRef;
@ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent;
@ViewChild('annotations') private _annotationsElement: ElementRef;
@ViewChild('quickNavigation') private _quickNavigationElement: ElementRef;
public fileId: string;
public annotations: Annotations.Annotation[] = [];
public displayedAnnotations: { [key: number]: { annotations: Annotations.Annotation[] } } = {};
public selectedAnnotation: Annotations.Annotation;
public quickNavigation: QuickNavigationItem[] = [];
public filters: AnnotationFilters;
public get filterKeys() {
return Object.keys(this.filters);
}
private _manualRedactionEntry: AddRedactionRequest;
activeViewerPage: number;
public expandedFilters: AnnotationFilters = { hint: false };
public activeViewerPage: number;
constructor(
public readonly appStateService: AppStateService,
@ -96,10 +75,18 @@ export class FilePreviewScreenComponent implements OnInit {
this.filters = _filtersService.filters;
}
get user() {
public get user() {
return this._userService.user;
}
public filterKeys(key?: string) {
if (key) {
return Object.keys(this.filters[key]);
}
return Object.keys(this.filters);
}
public get redactedView() {
return this._activeViewer === 'REDACTED';
}
@ -141,56 +128,22 @@ export class FilePreviewScreenComponent implements OnInit {
this._viewerSyncService.activateViewer(value);
}
public selectTab(value: 'ANNOTATIONS' | 'INFO' | 'NAVIGATION', $event?: MouseEvent) {
if ($event) {
$event.stopPropagation();
}
if (value !== this._selectedTab) {
this._selectedTab = value;
setTimeout(() => {
this._scrollViews();
}, 50);
}
public applyFilters() {
this.displayedAnnotations = AnnotationUtils.parseAnnotations(this.annotations, this.filters);
}
public handleAnnotationsAdded(annotations: Annotations.Annotation[]) {
this._changeDetectorRef.detectChanges();
for (const annotation of annotations) {
if (annotation.Id.indexOf(':') > 0) {
this.annotations.push(annotation);
const pageNumber = annotation.getPageNumber();
let el = this.quickNavigation.find((page) => page.pageNumber === pageNumber);
if (!el) {
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.ignored++;
}
if (annotation.Id.startsWith('redaction:')) {
el.redactions++;
}
}
}
this.annotations = AnnotationUtils.sortAnnotations(this.annotations);
AnnotationUtils.addAnnotations(this.annotations, annotations);
this.applyFilters();
}
public showQuickNavigationItem(item: QuickNavigationItem): boolean {
let showItem = false;
Object.keys(this.filters).map((key) => {
if (this.showAnnotations(item, key)) {
showItem = true;
}
})
return showItem;
public get displayedPages(): number[] {
return Object.keys(this.displayedAnnotations).map(key => Number(key));
}
public handleAnnotationSelected(annotation: Annotations.Annotation) {
this.selectedAnnotation = annotation;
this.selectTab('ANNOTATIONS');
this.scrollToSelectedAnnotation();
this._changeDetectorRef.detectChanges();
}
@ -204,22 +157,10 @@ export class FilePreviewScreenComponent implements OnInit {
if (!this.selectedAnnotation) {
return;
}
const elements: any[] = this._annotationsContainer.nativeElement.querySelectorAll(`div[annotation-id="${this.selectedAnnotation.Id}"].active`);
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.selectedAnnotation.Id}"].active`);
this._scrollToFirstElement(elements);
}
public get navigationTab() {
return this._selectedTab === 'NAVIGATION';
}
public get annotationsTab() {
return this._selectedTab === 'ANNOTATIONS';
}
public get infoTab() {
return this._selectedTab === 'INFO';
}
public selectPage(pageNumber: number) {
this._viewerComponent.navigateToPage(pageNumber);
}
@ -241,7 +182,7 @@ export class FilePreviewScreenComponent implements OnInit {
});
}
viewerPageChanged(pageNumber: number) {
public viewerPageChanged(pageNumber: number) {
if (Number.isInteger(pageNumber)) {
this.activeViewerPage = this._viewerSyncService.activeViewerPage;
this._scrollViews();
@ -251,13 +192,12 @@ export class FilePreviewScreenComponent implements OnInit {
@debounce()
private _scrollViews() {
console.log('scroll views');
this._scrollQuickNavigation();
this._scrollAnnotations();
}
private _scrollQuickNavigation() {
const elements: any[] = this._navigationTabElement.nativeElement.querySelectorAll(`#quick-nav-page-${this.activeViewerPage}`);
const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${this.activeViewerPage}`);
this._scrollToFirstElement(elements);
}
@ -265,7 +205,7 @@ export class FilePreviewScreenComponent implements OnInit {
if (this.selectedAnnotation?.getPageNumber() === this.activeViewerPage) {
return;
}
const elements: any[] = this._annotationsContainer.nativeElement.querySelectorAll(`div[annotation-page="${this.activeViewerPage}"]`);
const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-page="${this.activeViewerPage}"]`);
this._scrollToFirstElement(elements);
}
@ -280,14 +220,12 @@ export class FilePreviewScreenComponent implements OnInit {
}
}
getType(id: string) {
const parts = id.split(':');
return parts.length >= 1 ? parts[0] : 'n/a';
getType(annotation: Annotations.Annotation): string {
return AnnotationUtils.getType(annotation);
}
getDictionary(id: string) {
const parts = id.split(':');
return parts.length >= 2 ? parts[1] : 'n/a';
getDictionary(annotation: Annotations.Annotation): string {
return AnnotationUtils.getDictionary(annotation);
}
// async getText(pageNumber: number, rect) {
@ -332,26 +270,44 @@ export class FilePreviewScreenComponent implements OnInit {
public downloadFile(type: FileType | string) {
this._fileDownloadService.loadFile(type, this.fileId).subscribe(data => {
saveAs(data, this.appStateService.activeFile.filename);
})
});
}
public setAllFilters(value: boolean) {
Object.keys(this.filters).map((key) => {
this.filters[key].value = value;
});
public setAllFilters(filter: AnnotationFilters, value: boolean, rootKey?: string) {
if (rootKey) {
this.filters[rootKey] = value;
} else {
for (const key of Object.keys(filter)) {
if (AnnotationUtils.hasSubsections(filter[key])) {
this.setAllFilters(filter[key], value);
} else {
filter[key] = value;
}
}
}
this.applyFilters();
}
public isChecked(key: string): boolean {
return AnnotationUtils.isChecked(this.filters[key]);
}
public isIndeterminate(key: string): boolean {
return AnnotationUtils.isIndeterminate(this.filters[key]);
}
public get hasActiveFilters(): boolean {
let activeFilters = false;
Object.keys(this.filters).map((key) => {
if (this.filters[key].value) {
activeFilters = true;
}
});
return activeFilters;
return AnnotationUtils.hasActiveFilters(this.filters);
}
public showAnnotations(item: QuickNavigationItem, type: string): boolean {
return item[type] && (!this.hasActiveFilters || (this.hasActiveFilters && this.filters[type]?.value));
public hasSubsections(filter: AnnotationFilters | boolean) {
return AnnotationUtils.hasSubsections(filter);
}
public setExpanded(key: string, value: boolean, $event: MouseEvent) {
$event.stopPropagation();
this.expandedFilters[key] = value;
}
}

View File

@ -9,11 +9,15 @@ export class FiltersService {
}
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' },
hint: {
hint_only: false,
vertebrate: false,
names: false,
},
redaction: false,
comment: false,
suggestion: false,
ignore: false,
}
public get filters(): AnnotationFilters {

View File

@ -1,24 +1,24 @@
<div class="page-header">
<div class="filters flex-row">
<span translate="filters.filter-by.label"></span>
<div translate="filters.status.label" class="icon-button">
<div translate="filters.filter-by.label"></div>
<button mat-button translate="filters.status.label">
<mat-icon svgIcon="red:status"></mat-icon>
</div>
<div translate="filters.people.label" class="icon-button">
</button>
<button mat-button translate="filters.people.label">
<mat-icon svgIcon="red:user"></mat-icon>
</div>
<div translate="filters.due-date.label" class="icon-button">
</button>
<button mat-button translate="filters.due-date.label">
<mat-icon svgIcon="red:lightning"></mat-icon>
</div>
<div translate="filters.created-on.label" class="icon-button">
</button>
<button mat-button translate="filters.created-on.label">
<mat-icon svgIcon="red:calendar"></mat-icon>
</div>
<div translate="filters.project.label" class="icon-button">
</button>
<button mat-button translate="filters.project.label">
<mat-icon svgIcon="red:folder"></mat-icon>
</div>
<div translate="filters.document.label" class="icon-button">
</button>
<button mat-button translate="filters.document.label">
<mat-icon svgIcon="red:document"></mat-icon>
</div>
</button>
</div>
<button (click)="openAddProjectDialog()" color="primary" mat-flat-button class="add-project-btn">
<mat-icon svgIcon="red:plus">
@ -105,7 +105,7 @@
<div>
<redaction-simple-doughnut-chart [config]="projectsChartData"
[strokeWidth]="15"
[subtitle]="'project-listing.stats.charts.projects.label' | translate"
[subtitle]="'project-listing.stats.charts.projects.label'"
></redaction-simple-doughnut-chart>
<div class="project-stats-container">
@ -129,7 +129,7 @@
<div>
<redaction-simple-doughnut-chart [config]="documentsChartData"
[strokeWidth]="15"
[subtitle]="'project-listing.stats.charts.total-documents.label' | translate"
[subtitle]="'project-listing.stats.charts.total-documents.label'"
></redaction-simple-doughnut-chart>
</div>
</div>

View File

@ -41,8 +41,8 @@ export class ProjectListingScreenComponent implements OnInit {
ngOnInit(): void {
this.appStateService.reset();
this.projectsChartData = [
{ value: this.activeProjects, color: 'active', label: 'active-projects' },
{ value: this.inactiveProjects, color: 'archived', label: 'Archived' }
{ value: this.activeProjects, color: 'active', label: 'active' },
{ value: this.inactiveProjects, color: 'archived', label: 'archived' }
];
this.documentsChartData = [
{ value: this.appStateService.totalDocuments, color: 'unassigned', label: 'unassigned' },

View File

@ -4,25 +4,25 @@
<div *ngIf="appStateService.activeProject" class="page-header">
<div class="filters flex-row">
<span translate="filters.filter-by.label"></span>
<div translate="filters.status.label" class="icon-button">
<div translate="filters.filter-by.label"></div>
<button mat-button translate="filters.status.label">
<mat-icon svgIcon="red:status"></mat-icon>
</div>
<div translate="filters.people.label" class="icon-button">
</button>
<button mat-button translate="filters.people.label">
<mat-icon svgIcon="red:user"></mat-icon>
</div>
<div translate="filters.due-date.label" class="icon-button">
</button>
<button mat-button translate="filters.due-date.label">
<mat-icon svgIcon="red:lightning"></mat-icon>
</div>
<div translate="filters.created-on.label" class="icon-button">
</button>
<button mat-button translate="filters.created-on.label">
<mat-icon svgIcon="red:calendar"></mat-icon>
</div>
<div translate="filters.project.label" class="icon-button">
</button>
<button mat-button translate="filters.project.label">
<mat-icon svgIcon="red:folder"></mat-icon>
</div>
<div translate="filters.document.label" class="icon-button">
</button>
<button mat-button translate="filters.document.label">
<mat-icon svgIcon="red:document"></mat-icon>
</div>
</button>
</div>
<button (click)="fileInput.click()" color="primary" mat-flat-button
translate="project-overview.upload-document.label"></button>
@ -53,7 +53,8 @@
<div class="flex-4 small-label min-width" translate="project-overview.table-col-names.added-on.label"></div>
<div class="flex-2 small-label min-width" translate="project-overview.table-col-names.needs-work.label"></div>
<div class="flex-2 small-label min-width" translate="project-overview.table-col-names.assigned-to.label"></div>
<div class="flex-1 small-label status-container min-width" translate="project-overview.table-col-names.status.label"></div>
<div class="flex-1 small-label status-container min-width"
translate="project-overview.table-col-names.status.label"></div>
</div>
<div class="table-item"
@ -69,8 +70,8 @@
</div>
<div class="flex-2 min-width needs-work">
<div class="oval darkgray-white x-small">R</div>
<div class="oval red-white x-small">S</div>
<redaction-annotation-icon type="redaction"></redaction-annotation-icon>
<redaction-annotation-icon type="hint"></redaction-annotation-icon>
</div>
<div class="small-label flex-2 assigned-to min-width">
@ -112,13 +113,13 @@
<div class="project-details-container right-fixed-container">
<div class="actions-row">
<button mat-icon-button class="icon-button" (click)="deleteProject($event)">
<button mat-icon-button (click)="deleteProject($event)">
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<button mat-icon-button class="icon-button" (click)="editProject($event)">
<button mat-icon-button (click)="editProject($event)">
<mat-icon svgIcon="red:edit"></mat-icon>
</button>
<button mat-icon-button class="icon-button" (click)="showDetailsDialog($event)">
<button mat-icon-button (click)="showDetailsDialog($event)">
<mat-icon svgIcon="red:report"></mat-icon>
</button>
</div>
@ -168,5 +169,14 @@
</div>
</div>
</div>
<div class="mt-20">
<redaction-simple-doughnut-chart [config]="documentsChartData"
[strokeWidth]="15"
[radius]="70"
[subtitle]="'project-overview.project-details.charts.total-documents.label'"
direction="row"
></redaction-simple-doughnut-chart>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import {ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
FileStatus,
FileUploadControllerService,
@ -7,19 +7,20 @@ import {
ReanalysisControllerService,
StatusControllerService
} from '@redaction/red-ui-http';
import {NotificationService, NotificationType} from '../../notification/notification.service';
import {TranslateService} from '@ngx-translate/core';
import {ConfirmationDialogComponent} from '../../common/confirmation-dialog/confirmation-dialog.component';
import {MatDialog} from '@angular/material/dialog';
import {AppStateService} from '../../state/app-state.service';
import {ProjectDetailsDialogComponent} from './project-details-dialog/project-details-dialog.component';
import {FileDropOverlayService} from '../../upload/file-drop/service/file-drop-overlay.service';
import {FileUploadModel} from "../../upload/model/file-upload.model";
import {FileUploadService} from "../../upload/file-upload.service";
import {UploadStatusOverlayService} from "../../upload/upload-status-dialog/service/upload-status-overlay.service";
import {AddEditProjectDialogComponent} from "../project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component";
import {UserService} from "../../user/user.service";
import { NotificationService, NotificationType } from '../../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { ConfirmationDialogComponent } from '../../common/confirmation-dialog/confirmation-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { AppStateService } from '../../state/app-state.service';
import { ProjectDetailsDialogComponent } from './project-details-dialog/project-details-dialog.component';
import { FileDropOverlayService } from '../../upload/file-drop/service/file-drop-overlay.service';
import { FileUploadModel } from '../../upload/model/file-upload.model';
import { FileUploadService } from '../../upload/file-upload.service';
import { UploadStatusOverlayService } from '../../upload/upload-status-dialog/service/upload-status-overlay.service';
import { AddEditProjectDialogComponent } from '../project-listing-screen/add-edit-project-dialog/add-edit-project-dialog.component';
import { UserService } from '../../user/user.service';
import { SortingOption } from '../../utils/types';
import { DoughnutChartConfig } from '../../components/simple-doughnut-chart/simple-doughnut-chart.component';
@Component({
@ -28,7 +29,7 @@ import { SortingOption } from '../../utils/types';
styleUrls: ['./project-overview-screen.component.scss']
})
export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
@ViewChild('dropzoneComponent', {static: true})
@ViewChild('dropzoneComponent', { static: true })
dropZoneComponent;
dragActive = false;
@ -37,10 +38,12 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
{ label: 'project-overview.sorting.recent.label', order: 'desc', column: 'lastUpdated' },
{ label: 'project-overview.sorting.alphabetically.label', order: 'asc', column: 'filename' },
{ label: 'project-overview.sorting.number-of-pages.label', order: 'asc', column: 'numberOfPages' },
{ label: 'project-overview.sorting.number-of-analyses.label', order: 'desc', column: 'numberOfAnalyses' },
{ label: 'project-overview.sorting.number-of-analyses.label', order: 'desc', column: 'numberOfAnalyses' }
];
public sortingOption: SortingOption = this.sortingOptions[0];
public documentsChartData: DoughnutChartConfig[] = [];
projectId: string;
private _fileStatusInterval;
@ -71,6 +74,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this._fileDropOverlayService.initFileDropHandling();
this._calculateChartConfig();
this._fileStatusInterval = setInterval(() => {
this._getFileStatus();
}, 5000);
@ -120,7 +124,22 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
}
private _getFileStatus() {
this.appStateService.reloadActiveProjectFiles();
this.appStateService.reloadActiveProjectFiles().then(() => {
this._calculateChartConfig();
});
}
private _calculateChartConfig() {
const obj = this.appStateService.activeProject.files.reduce((acc, file) => {
acc[file.status === 'PROCESSED' ? 'finished' : file.status === 'ERROR' ? 'under-approval' : 'under-review']++;
return acc;
}, { 'finished': 0, 'under-approval': 0, 'under-review': 0 });
this.documentsChartData = Object.keys(obj).map((key) => ({
value: obj[key],
color: key,
label: key
})) as DoughnutChartConfig[];
}
fileId(index, item) {
@ -137,7 +156,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
progress: 0,
completed: false,
error: null
})
});
}
this._fileUploadService.uploadFiles(uploadFiles);
@ -157,7 +176,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
$event.stopPropagation();
const dialogRef = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw',
maxWidth: '90vw'
});
dialogRef.afterClosed().subscribe(result => {

View File

@ -1,4 +1,5 @@
import { Annotations } from '@pdftron/webviewer';
import { AnnotationFilters } from './types';
export class AnnotationUtils {
public static sortAnnotations(annotations: Annotations.Annotation[]): Annotations.Annotation[] {
@ -15,4 +16,89 @@ export class AnnotationUtils {
return ann1.getPageNumber() < ann2.getPageNumber() ? -1 : 1;
});
}
public static hasSubsections(filter: AnnotationFilters | boolean) {
return filter instanceof Object;
}
public static checkedSubkeys(filter: AnnotationFilters | boolean) {
return Object.keys(filter).filter(subkey => this.isChecked(filter[subkey])).length;
}
// Only some of the sub-items are selected
public static isIndeterminate(filter: AnnotationFilters | boolean): boolean {
return this.hasSubsections(filter) ? AnnotationUtils.checkedSubkeys(filter) > 0 && !this.isChecked(filter) : false;
}
// All sub-items are selected
public static isChecked(filter: AnnotationFilters | boolean): boolean {
return this.hasSubsections(filter) ?
AnnotationUtils.checkedSubkeys(filter) === Object.keys(filter).length :
filter as boolean;
}
public static hasActiveFilters(filter: AnnotationFilters): boolean {
const activeFilters = Object.keys(filter).filter(key => {
return this.isChecked(filter[key]) || this.isIndeterminate(filter[key]);
});
return activeFilters.length > 0;
}
public static parseAnnotations(annotations: Annotations.Annotation[], filters: AnnotationFilters):
{ [key: number]: { annotations: Annotations.Annotation[] } } {
const obj = {};
for (const ann of annotations) {
const pageNumber = ann.getPageNumber();
const type = this.getType(ann);
const dictionary = this.getDictionary(ann);
if (this.hasActiveFilters(filters)) {
if (!this.hasSubsections(filters[type]) && !filters[type]) {
continue;
}
if (this.hasSubsections(filters[type]) && !filters[type][dictionary]) {
continue;
}
}
if (!obj[pageNumber]) {
obj[pageNumber] = {
annotations: [],
hint: 0,
redaction: 0,
comment: 0,
suggestion: 0,
ignore: 0
};
}
obj[pageNumber].annotations.push(ann);
obj[pageNumber][type]++;
}
Object.keys(obj).map(page => {
obj[page].annotations = this.sortAnnotations(obj[page].annotations);
});
return obj;
}
public static addAnnotations(initialAnnotations: Annotations.Annotation[], addedAnnotations: Annotations.Annotation[]) {
for (const annotation of addedAnnotations) {
if (annotation.Id.indexOf(':') > 0) {
initialAnnotations.push(annotation);
}
}
}
public static getType(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 1 ? parts[0] : 'n/a';
}
public static getDictionary(annotation: Annotations.Annotation): string {
const parts = annotation.Id.split(':');
return parts.length >= 2 ? parts[1] : 'n/a';
}
}

View File

@ -9,6 +9,13 @@ export type Color =
'active' |
'archived';
export type AnnotationType =
'hint' |
'redaction' |
'suggestion' |
'comment' |
'ignore'
export class SortingOption {
label: string;
order: string;
@ -16,10 +23,5 @@ export class SortingOption {
}
export class AnnotationFilters {
[key: string]: {
label: string,
value: boolean,
class: string,
symbol: string
}
[key: AnnotationType]: boolean
}

View File

@ -329,6 +329,11 @@
"project-details": {
"project-team": {
"label": "Project team"
},
"charts": {
"total-documents": {
"label": "Total Documents"
}
}
},
"header": {
@ -357,18 +362,27 @@
"label": "Filter types"
},
"hint": {
"label": "Hint annotation"
"label": "Hint annotation",
"hint_only": {
"label": "Hint only"
},
"vertebrate": {
"label": "Vertebrate"
},
"names": {
"label": "Names"
}
},
"redaction": {
"label": "Redaction"
},
"comment": {
"label": "Comment"
"label": "Comment annotation"
},
"suggestion": {
"label": "Suggested redaction"
},
"ignored": {
"ignore": {
"label": "Ignored redaction"
}
},
@ -414,5 +428,22 @@
"unassigned": {
"label": "Unassigned"
}
}
},
"unassigned": "Unassigned",
"under-review": "Under review",
"under-approval": "Under approval",
"efsa": "EFSA approval",
"finished": "Finished",
"approved": "Approved",
"submitted": "Submitted",
"active": "Active",
"archived": "Archived",
"hint": "Hint",
"ignore": "Ignore",
"redaction": "Redaction",
"comment": "Comment",
"suggestion": "Suggestion for redaction",
"dictionary": "Dictionary",
"content": "Content",
"page": "Page"
}

View File

@ -1,9 +1,7 @@
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="255px" height="255px" viewBox="0 0 255 255" style="enable-background:new 0 0 255 255;" xml:space="preserve">
<g>
<g id="arrow-drop-down">
<polygon points="0,63.75 127.5,191.25 255,63.75 "/>
</g>
</g>
</svg>
<?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>down</title>
<g id="down" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<polygon id="Fill-1" fill="#283241" points="7 9 10 5 4 5"></polygon>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 308 B

After

Width:  |  Height:  |  Size: 381 B

View File

@ -20,25 +20,16 @@
display: flex;
align-items: center;
}
.dropdown-icon {
width: 16px;
}
}
.mat-button[aria-expanded="true"], .mat-button.overlay {
background: rgba($primary, 0.1);
}
.icon-button {
display: flex;
flex-direction: row-reverse;
.mat-button, .mat-flat-button, .mat-icon-button {
gap: 6px;
align-items: center;
cursor: pointer;
mat-icon {
width: 14px;
color: $accent;
}
}

View File

@ -19,13 +19,6 @@
font-size: 13px;
}
&.x-small {
height: 16px;
width: 16px;
font-size: 11px;
line-height: 14px;
}
&.lightgray-dark {
background-color: $grey-4;
}

View File

@ -3,14 +3,27 @@
.mat-menu-panel {
border-radius: 8px !important;
box-shadow: 0 2px 6px 0 rgba(40, 50, 65, 0.3);
max-width: none !important;
.mat-menu-item {
font-family: 'Inter', sans-serif;
font-size: 13px;
color: $accent;
.arrow-wrapper {
width: 24px;
mat-icon {
width: 14px;
}
}
&.padding-left {
padding-left: 64px;
}
.mat-checkbox-layout {
width: 100%;
margin-left: 4px;
.mat-checkbox-inner-container {
margin-left: 0;

View File

@ -58,8 +58,12 @@ html, body {
}
.filters {
> div {
padding: 10px 14px;
div {
margin-right: 6px;
}
button {
flex-direction: row-reverse;
}
}
@ -69,7 +73,7 @@ html, body {
}
.flex {
display: flex;
display: flex !important;
}
.flex-1 {

View File

@ -1,16 +1,12 @@
$white: #FFF;
$black: #000;
$primary: #DD4D50;
$accent: #283241;
$light: #FFF;
$dark: #000;
$grey-1: #283241;
$grey-2: #F4F5F7;
$grey-3: #AAACB3;
$grey-4: #E2E4E9;
$grey-5: #D3D5DA;
$grey-6: #F0F1F4;
$blue-1: #4875F7;
$blue-2: #48C9F7;
@ -18,8 +14,15 @@ $blue-3: #5B97DB;
$blue-4: #374C81;
$red-1: #DD4D50;
$yellow-1: #FFB83B;
$green-1: #46CE7D;
$yellow-2: #FFFF02;
$green-1: #00FF00;
$green-2: #5CE594;
$orange-1: #FF801A;
$primary: $red-1;
$accent: $grey-1;
$light: $white;
$dark: $black;
$separator: rgba(226,228,233,0.9);