Pull request #19: Ui updates

Merge in RED/ui from ui-updates to master

* commit 'e8993da266e9d0fe4eb98b3546b00887aab2f602':
  fixed lint errors
  Some arrow navigation fixes
  Avatar initials color depending on role
  Project status bar using real data
  Redo tables layout with grid
  Some input styling
  Fixes
  Working arrow navigation
  Removed duplicate flash/lightning icon
This commit is contained in:
Timo Bejan 2020-10-27 14:42:19 +01:00
commit 32334c3299
30 changed files with 613 additions and 443 deletions

View File

@ -1,4 +1,4 @@
<div class="flex-row">
<div [className]="color + ' oval ' + size">{{initials}}</div>
<div [className]="colorClass + ' oval ' + size">{{initials}}</div>
<div *ngIf="withName" class="clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
</div>

View File

@ -1,16 +1,18 @@
import {Component, Input, OnInit} from '@angular/core';
import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { UserService } from '../../user/user.service';
import { User } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-initials-avatar',
templateUrl: './initials-avatar.component.html',
styleUrls: ['./initials-avatar.component.scss']
})
export class InitialsAvatarComponent implements OnInit {
export class InitialsAvatarComponent implements OnInit, OnChanges {
@Input()
public username: string;
public userId: string;
@Input()
public color = 'lightgray-dark';
public color = 'lightgray';
@Input()
public size: 'small' | 'large' = 'small';
@ -18,21 +20,40 @@ export class InitialsAvatarComponent implements OnInit {
@Input()
public withName = false;
constructor() {
public _user: User;
constructor(private readonly _userService: UserService) {
}
ngOnInit(): void {
}
ngOnChanges(): void {
this._user = this._userService.getUserById(this.userId);
}
public get username(): string {
return this._userService.getName(this._user);
}
public get initials(): string {
if (!this.username) {
if (!this._user) {
return '?'
}
return this.username
return this._userService.getName(this._user)
.split(' ')
.filter(value => value !== ' ')
.filter((value, idx) => idx < 2)
.map((str) => str[0])
.join('');
}
public get colorClass() {
if (this.color.includes('-')) {
return this.color;
}
const textColor = !this._user || !this._userService.isManager(this._user) ? 'dark' : 'red';
return `${this.color}-${textColor}`
}
}

View File

@ -1,5 +1,5 @@
<div class="rectangle-container" [ngClass]="{ small: small }">
<div *ngFor="let rect of config" [className]="'section-wrapper flex-' + rect.length">
<div *ngFor="let rect of config" class="section-wrapper" [style]="'flex: ' + rect.length + ';'">
<div [className]="'rectangle ' + rect.color "></div>
<div *ngIf="rect.label" [ngClass]="labelClass">{{ rect.label }}</div>
</div>

View File

@ -39,39 +39,39 @@
.rectangle {
height: 4px;
&.unassigned {
&.UNASSIGNED {
background-color: $grey-5;
}
&.under-review {
&.UNDER_REVIEW {
background-color: $yellow-1;
}
&.under-approval {
&.UNDER_APPROVAL {
background-color: $red-1;
}
&.approved {
&.APPROVED {
background-color: $blue-2;
}
&.submitted {
&.SUBMITTED {
background-color: $blue-3;
}
&.efsa {
&.EFSA {
background-color: $blue-4;
}
&.finished {
&.FINISHED {
background-color: $green-2;
}
&.active {
&.ACTIVE {
background-color: $primary;
}
&.archived {
&.ARCHIVED {
background-color: rgba($red-1, 0.1);
}
}

View File

@ -17,7 +17,7 @@
<textarea formControlName="description" name="description" type="text" rows="5"></textarea>
</div>
<mat-form-field>
<mat-form-field class="mt-20">
<mat-label>{{'project-listing.add-edit-dialog.form.due-date.label' | translate}}</mat-label>
<input matInput [matDatepicker]="picker" formControlName="dueDate">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>

View File

@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {FileDetailsDialogComponent} from './file-details-dialog/file-details-dialog.component';
import {MatDialog} from '@angular/material/dialog';
import { Injectable } from '@angular/core';
import { FileDetailsDialogComponent } from './file-details-dialog/file-details-dialog.component';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import {
FileStatus,
FileUploadControllerService,
@ -8,15 +8,15 @@ import {
ManualRedactionEntry,
Project
} from '@redaction/red-ui-http';
import {ConfirmationDialogComponent} from '../common/confirmation-dialog/confirmation-dialog.component';
import {NotificationService, NotificationType} from '../notification/notification.service';
import {TranslateService} from '@ngx-translate/core';
import {AppStateService, ProjectWrapper} from '../state/app-state.service';
import {AddEditProjectDialogComponent} from './add-edit-project-dialog/add-edit-project-dialog.component';
import {AssignOwnerDialogComponent} from './assign-owner-dialog/assign-owner-dialog.component';
import {ProjectDetailsDialogComponent} from './project-details-dialog/project-details-dialog.component';
import {ManualRedactionDialogComponent} from './manual-redaction-dialog/manual-redaction-dialog.component';
import {Annotations} from '@pdftron/webviewer';
import { ConfirmationDialogComponent } from '../common/confirmation-dialog/confirmation-dialog.component';
import { NotificationService, NotificationType } from '../notification/notification.service';
import { TranslateService } from '@ngx-translate/core';
import { AppStateService, ProjectWrapper } from '../state/app-state.service';
import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edit-project-dialog.component';
import { AssignOwnerDialogComponent } from './assign-owner-dialog/assign-owner-dialog.component';
import { ProjectDetailsDialogComponent } from './project-details-dialog/project-details-dialog.component';
import { ManualRedactionDialogComponent } from './manual-redaction-dialog/manual-redaction-dialog.component';
import { Annotations } from '@pdftron/webviewer';
const dialogConfig = {
width: '600px',
@ -38,17 +38,19 @@ export class DialogService {
}
public openFileDetailsDialog($event: MouseEvent, file: FileStatus) {
public openFileDetailsDialog($event: MouseEvent, file: FileStatus): MatDialogRef<FileDetailsDialogComponent> {
$event.stopPropagation();
this._dialog.open(FileDetailsDialogComponent, {
return this._dialog.open(FileDetailsDialogComponent, {
...dialogConfig,
data: file
});
}
public openDeleteFileDialog($event: MouseEvent, projectId: string, fileId: string, cb?: Function) {
public openDeleteFileDialog($event: MouseEvent, projectId: string, fileId: string, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
this._dialog.open(ConfirmationDialogComponent, dialogConfig).afterClosed().subscribe(result => {
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(result => {
if (result) {
const file = this._appStateService.getFileById(projectId, fileId);
this._fileUploadControllerService.deleteFile(file.projectId, file.fileId).subscribe(async () => {
@ -62,28 +64,34 @@ export class DialogService {
});
}
});
return ref;
}
public openManualRedactionDialog($event: ManualRedactionEntry, cb?: Function) {
this._dialog.open(ManualRedactionDialogComponent, {
public openManualRedactionDialog($event: ManualRedactionEntry, cb?: Function): MatDialogRef<ManualRedactionDialogComponent> {
const ref = this._dialog.open(ManualRedactionDialogComponent, {
...dialogConfig,
autoFocus: true,
data: $event
}).afterClosed().subscribe(result => {
});
ref.afterClosed().subscribe(result => {
if (cb) {
cb(result);
}
});
return ref;
}
public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string) {
public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const parts = annotation.Id.split(':');
const annotationId = parts[parts.length - 1];
this._dialog.open(ConfirmationDialogComponent, dialogConfig)
.afterClosed().subscribe(result => {
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(result => {
if (result) {
this._manualRedactionControllerService.approveRequest(projectId, fileId, annotationId).subscribe(() => {
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.confirm-annotation.success.label'), null, NotificationType.SUCCESS);
@ -92,18 +100,22 @@ export class DialogService {
});
}
});
return ref;
}
public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string) {
public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation, projectId: string, fileId: string): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
const parts = annotation.Id.split(':');
const annotationId = parts[parts.length - 1];
this._dialog.open(ConfirmationDialogComponent, {
const ref = this._dialog.open(ConfirmationDialogComponent, {
width: '400px',
maxWidth: '90vw'
}).afterClosed().subscribe(result => {
});
ref.afterClosed().subscribe(result => {
if (result) {
this._manualRedactionControllerService.undo(projectId, fileId, annotationId).subscribe(ok => {
this._notificationService.showToastNotification(this._translateService.instant('manual-redaction.remove-annotation.success.label'), null, NotificationType.SUCCESS);
@ -113,63 +125,74 @@ export class DialogService {
}
});
return ref;
}
public openEditProjectDialog($event: MouseEvent, project: Project) {
public openEditProjectDialog($event: MouseEvent, project: Project): MatDialogRef<AddEditProjectDialogComponent> {
$event.stopPropagation();
this._dialog.open(AddEditProjectDialogComponent, {
return this._dialog.open(AddEditProjectDialogComponent, {
...dialogConfig,
autoFocus: true,
data: project
});
}
public openDeleteProjectDialog($event: MouseEvent, project: Project, cb?: Function) {
public openDeleteProjectDialog($event: MouseEvent, project: Project, cb?: Function): MatDialogRef<ConfirmationDialogComponent> {
$event.stopPropagation();
this._dialog.open(ConfirmationDialogComponent, dialogConfig)
.afterClosed().subscribe(async result => {
const ref = this._dialog.open(ConfirmationDialogComponent, dialogConfig);
ref.afterClosed().subscribe(async result => {
if (result) {
await this._appStateService.deleteProject(project);
if (cb) cb();
}
});
return ref;
}
public openAssignProjectMembersAndOwnerDialog($event: MouseEvent, project: Project, cb?: Function) {
public openAssignProjectMembersAndOwnerDialog($event: MouseEvent, project: Project, cb?: Function): MatDialogRef<AssignOwnerDialogComponent> {
$event?.stopPropagation();
this._dialog.open(AssignOwnerDialogComponent, {
const ref = this._dialog.open(AssignOwnerDialogComponent, {
...dialogConfig,
data: {type: 'project', project: project}
}).afterClosed().subscribe(result => {
data: { type: 'project', project: project }
});
ref.afterClosed().subscribe(result => {
if (result && cb) cb();
});
return ref;
}
public openAssignFileOwnerDialog($event: MouseEvent, file: FileStatus, cb?: Function) {
public openAssignFileOwnerDialog($event: MouseEvent, file: FileStatus, cb?: Function): MatDialogRef<AssignOwnerDialogComponent> {
$event.stopPropagation();
this._dialog.open(AssignOwnerDialogComponent, {
const ref = this._dialog.open(AssignOwnerDialogComponent, {
...dialogConfig,
data: {type: 'file', file: file}
}).afterClosed().subscribe(() => {
data: { type: 'file', file: file }
});
ref.afterClosed().subscribe(() => {
if (cb) cb();
});
return ref;
}
public openProjectDetailsDialog($event: MouseEvent, project: ProjectWrapper) {
public openProjectDetailsDialog($event: MouseEvent, project: ProjectWrapper): MatDialogRef<ProjectDetailsDialogComponent> {
$event.stopPropagation();
this._dialog.open(ProjectDetailsDialogComponent, {
return this._dialog.open(ProjectDetailsDialogComponent, {
...dialogConfig,
data: project
});
}
public openAddProjectDialog(cb?: Function): void {
this._dialog.open(AddEditProjectDialogComponent, {
public openAddProjectDialog(cb?: Function): MatDialogRef<AddEditProjectDialogComponent> {
const ref = this._dialog.open(AddEditProjectDialogComponent, {
...dialogConfig,
autoFocus: true
}).afterClosed().subscribe(result => {
});
ref.afterClosed().subscribe(result => {
if (result && cb) cb();
});
}
return ref;
}
}

View File

@ -7,11 +7,11 @@
<div class="dialog-content">
<div class="file-details">
<div class="detail-row"
[innerHTML]="'manual-redaction.dialog.content.text.label' | translate:addRedactionRequest">
</div>
<div class="red-input-group">
<label translate="manual-redaction.dialog.content.text.label"></label>
</div>
{{ addRedactionRequest.value }}
<div class="red-input-group">
<label translate="manual-redaction.dialog.content.reason.label"></label>
@ -23,17 +23,17 @@
<textarea formControlName="comment" name="comment" type="text" rows="2"></textarea>
</div>
<mat-form-field >
<mat-label>{{'manual-redaction.dialog.content.dictionary.label' | translate}}</mat-label>
<div class="red-input-group">
<label translate="manual-redaction.dialog.content.dictionary.label"></label>
<mat-select formControlName="dictionary">
<mat-option *ngFor="let dictionary of dictionaries | async" [value]="dictionary.type">
{{dictionary.type}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="red-input-group">
<mat-checkbox
<mat-checkbox color="primary"
formControlName="addToDictionary">{{'manual-redaction.dialog.content.dictionary.add.label' | translate}}</mat-checkbox>
</div>

View File

@ -18,7 +18,7 @@ export class IconsModule {
'check', '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', 'check-alt',"flash"
'status', 'trash', 'user', 'check-alt',
];
for (const icon of icons) {

View File

@ -40,7 +40,7 @@
</div>
<div class="menu right flex-2">
<button [matMenuTriggerFor]="menu" mat-button class="arrow-button">
<redaction-initials-avatar color="red-white" size="small" [username]="user?.name" [withName]="true"></redaction-initials-avatar>
<redaction-initials-avatar color="red-white" size="small" [userId]="user?.id" [withName]="true"></redaction-initials-avatar>
<mat-icon svgIcon="red:arrow-down"></mat-icon>
</button>
<mat-menu #menu="matMenu">

View File

@ -0,0 +1,25 @@
@import "../../../assets/styles/red-variables";
@import "../../../assets/styles/red-mixins";
.breadcrumbs-container {
display: flex;
gap: 8px;
.breadcrumb {
text-decoration: none;
color: $accent;
font-weight: 600;
width: fit-content;
white-space: nowrap;
&:last-child {
color: $primary;
@include line-clamp(1);
}
.mat-icon {
vertical-align: middle;
width: 6px;
}
}
}

View File

@ -126,7 +126,11 @@
</div>
<div class="right-content">
<div class="pages" #quickNavigation>
<div class="pages" [class.activePanel]="pagesPanelActive"
tabindex="0"
(keyup)="$event.preventDefault()"
(keydown)="$event.preventDefault();"
#quickNavigation>
<div class="page-number pointer"
[ngClass]="{ active: pageNumber === activeViewerPage }"
*ngFor="let pageNumber of displayedPages"
@ -135,7 +139,11 @@
</div>
</div>
<div class="annotations" #annotations>
<div class="annotations" [class.activePanel]="!pagesPanelActive" #annotations
tabindex="1"
(keyup)="$event.preventDefault()"
(keydown)="$event.preventDefault();"
>
<div *ngFor="let page of displayedPages">
<div class="page-separator" attr.anotation-page-header="{{page}}">
<span class="all-caps-label"><span translate="page"></span> {{page}}</span>

View File

@ -62,12 +62,11 @@ redaction-pdf-viewer {
.pages, .annotations {
overflow-y: scroll;
@include no-scroll-bar();
outline: none;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
width: 0;
background: transparent; /* Chrome/Safari/Webkit */
&.activePanel {
background-color: #FAFAFA;
}
}

View File

@ -1,19 +1,20 @@
import {ChangeDetectorRef, Component, ElementRef, HostListener, NgZone, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {ManualRedactionEntry, ReanalysisControllerService} from '@redaction/red-ui-http';
import {AppStateService} from '../../../state/app-state.service';
import {Annotations, WebViewerInstance} from '@pdftron/webviewer';
import {PdfViewerComponent} from '../pdf-viewer/pdf-viewer.component';
import {AnnotationUtils} from '../../../utils/annotation-utils';
import {UserService} from '../../../user/user.service';
import {debounce} from '../../../utils/debounce';
import { ChangeDetectorRef, Component, ElementRef, HostListener, NgZone, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ManualRedactionEntry, ReanalysisControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '../../../state/app-state.service';
import { Annotations, WebViewerInstance } from '@pdftron/webviewer';
import { PdfViewerComponent } from '../pdf-viewer/pdf-viewer.component';
import { AnnotationUtils } from '../../../utils/annotation-utils';
import { UserService } from '../../../user/user.service';
import { debounce } from '../../../utils/debounce';
import scrollIntoView from 'scroll-into-view-if-needed';
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 {DialogService} from '../../../dialogs/dialog.service';
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 { DialogService } from '../../../dialogs/dialog.service';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
@Component({
selector: 'redaction-file-preview-screen',
@ -24,6 +25,8 @@ export class FilePreviewScreenComponent implements OnInit {
private projectId: string;
private _activeViewer: 'ANNOTATED' | 'REDACTED' = 'ANNOTATED';
private instance: WebViewerInstance;
private _dialogRef: MatDialogRef<any>;
@ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent;
@ViewChild('annotations') private _annotationsElement: ElementRef;
@ -36,8 +39,8 @@ export class FilePreviewScreenComponent implements OnInit {
public displayedAnnotations: { [key: number]: { annotations: Annotations.Annotation[] } } = {};
public selectedAnnotation: Annotations.Annotation;
public filters: AnnotationFilters;
public expandedFilters: AnnotationFilters = {hint: false};
private instance: WebViewerInstance;
public expandedFilters: AnnotationFilters = { hint: false };
public pagesPanelActive = true;
constructor(
public readonly appStateService: AppStateService,
@ -83,11 +86,11 @@ export class FilePreviewScreenComponent implements OnInit {
localStorage.clear();
this._reloadFiles();
this.appStateService.fileStatusChanged.subscribe((fileStatus) => {
if(fileStatus.fileId === this.fileId) {
if (fileStatus.fileId === this.fileId) {
console.log(fileStatus);
this._reloadFiles();
}
})
});
}
private _reloadFiles() {
@ -102,7 +105,7 @@ export class FilePreviewScreenComponent implements OnInit {
}
public openFileDetailsDialog($event: MouseEvent) {
this._dialogService.openFileDetailsDialog($event, this.appStateService.activeFile);
this._dialogRef = this._dialogService.openFileDetailsDialog($event, this.appStateService.activeFile);
}
public reanalyseFile($event: MouseEvent) {
@ -113,17 +116,16 @@ export class FilePreviewScreenComponent implements OnInit {
}
public openDeleteFileDialog($event: MouseEvent) {
this._dialogService.openDeleteFileDialog($event, this.projectId, this.fileId, () => {
this._dialogRef = this._dialogService.openDeleteFileDialog($event, this.projectId, this.fileId, () => {
this._router.navigate([`/ui/projects/${this.projectId}`]);
});
}
public openAssignFileOwnerDialog($event: MouseEvent) {
const file = this.appStateService.getFileById(this.projectId, this.fileId);
this._dialogService.openAssignFileOwnerDialog($event, file);
this._dialogRef = this._dialogService.openAssignFileOwnerDialog($event, file);
}
public get activeViewer() {
return this.instance;
}
@ -161,13 +163,12 @@ export class FilePreviewScreenComponent implements OnInit {
public openManualRedactionDialog($event: ManualRedactionEntry) {
this.ngZone.run(() => {
this._dialogService.openManualRedactionDialog($event, () => {
this._dialogRef = this._dialogService.openManualRedactionDialog($event, () => {
});
});
}
get activeViewerPage() {
return this.instance.docViewer.getCurrentPage();
}
@ -212,13 +213,13 @@ export class FilePreviewScreenComponent implements OnInit {
public acceptSuggestionAnnotation($event: MouseEvent, annotation: Annotations.Annotation) {
this.ngZone.run(() => {
this._dialogService.acceptSuggestionAnnotation($event, annotation, this.projectId, this.fileId);
this._dialogRef = this._dialogService.acceptSuggestionAnnotation($event, annotation, this.projectId, this.fileId);
});
}
public suggestRemoveAnnotation($event: MouseEvent, annotation: Annotations.Annotation) {
this.ngZone.run(() => {
this._dialogService.suggestRemoveAnnotation($event, annotation, this.projectId, this.fileId);
this._dialogRef = this._dialogService.suggestRemoveAnnotation($event, annotation, this.projectId, this.fileId);
});
}
@ -272,10 +273,41 @@ export class FilePreviewScreenComponent implements OnInit {
@HostListener('window:keyup', ['$event'])
handleKeyEvent($event: KeyboardEvent) {
$event.preventDefault();
const keyArray = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
if (!keyArray.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) {
return;
}
if ($event.key === 'ArrowLeft' || $event.key === 'ArrowRight') {
this.pagesPanelActive = !this.pagesPanelActive;
this._changeDetectorRef.detectChanges();
return;
}
if (!this.pagesPanelActive) {
this._navigateAnnotations($event);
} else {
this._navigatePages($event);
}
}
private _navigateAnnotations($event: KeyboardEvent) {
if (!this.selectedAnnotation || this.activeViewerPage !== this.selectedAnnotation.getPageNumber()) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if (pageIdx !== -1) { // Displayed page has annotations
this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]);
} else { // Displayed page doesn't have annotations
if ($event.key === 'ArrowDown') {
const nextPage = this._nextPageWithAnnotations();
this.selectAnnotation(this.displayedAnnotations[nextPage].annotations[0]);
} else {
const prevPage = this._prevPageWithAnnotations();
const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations;
this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]);
}
}
if (!this.selectedAnnotation) {
this.selectAnnotation(this.displayedAnnotations[this.displayedPages[0]].annotations[0]);
} else {
const page = this.selectedAnnotation.getPageNumber();
const pageIdx = this.displayedPages.indexOf(page);
@ -289,9 +321,7 @@ export class FilePreviewScreenComponent implements OnInit {
const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations;
this.selectAnnotation(nextPageAnnotations[0]);
}
}
if ($event.key === 'ArrowUp') {
} else {
if (idx !== 0) { // If not first item in page
this.selectAnnotation(annotationsOnPage[idx - 1]);
} else if (pageIdx) { // If not first page
@ -302,6 +332,58 @@ export class FilePreviewScreenComponent implements OnInit {
}
}
private _navigatePages($event: KeyboardEvent) {
const pageIdx = this.displayedPages.indexOf(this.activeViewerPage);
if ($event.key === 'ArrowDown') {
if (pageIdx !== -1) { // If active page has annotations
if (pageIdx !== this.displayedPages.length - 1) {
this.selectPage(this.displayedPages[pageIdx + 1]);
}
} else { // If active page doesn't have annotations
const nextPage = this._nextPageWithAnnotations();
if (nextPage) {
this.selectPage(nextPage);
}
}
} else {
if (pageIdx !== -1) { // If active page has annotations
if (pageIdx !== 0) {
this.selectPage(this.displayedPages[pageIdx - 1]);
}
} else { // If active page doesn't have annotations
const prevPage = this._prevPageWithAnnotations();
if (prevPage) {
this.selectPage(prevPage);
}
}
}
}
private _nextPageWithAnnotations() {
let idx = 0;
for (const page of this.displayedPages) {
if (page > this.activeViewerPage) {
break;
}
++idx;
}
return idx < this.displayedPages.length ? this.displayedPages[idx] : null;
}
private _prevPageWithAnnotations() {
let idx = this.displayedPages.length - 1;
for (const page of this.displayedPages.reverse()) {
if (page < this.activeViewerPage) {
this.selectPage(this.displayedPages[idx]);
this._scrollAnnotations();
break;
}
--idx;
}
return idx >= 0 ? this.displayedPages[idx] : null;
}
viewerPageChanged($event: number) {
this._scrollViews();
this._changeDetectorRef.detectChanges();
@ -316,9 +398,9 @@ export class FilePreviewScreenComponent implements OnInit {
// handle comments
annotations.forEach(a => {
a['comments'] = a['Mi'] ? a['Mi'].map(m => {
return {value: m.eC}
return { value: m.eC };
}) : [];
})
});
AnnotationUtils.addAnnotations(this.annotations, annotations);
this.applyFilters();

View File

@ -184,14 +184,14 @@ export class PdfViewerComponent implements OnInit, AfterViewInit, OnChanges {
public selectAnnotation(annotation: Annotations.Annotation) {
this.instance.annotManager.deselectAllAnnotations();
this.instance.annotManager.selectAnnotation(annotation);
this.instance.docViewer.displayPageLocation(
annotation.getPageNumber(),
0,
annotation.getY() - 100);
this.navigateToPage(annotation.getPageNumber());
}
public navigateToPage(pageNumber: number) {
this.instance.docViewer.displayPageLocation(pageNumber, 0, 0);
const activePage = this.instance.docViewer.getCurrentPage();
if (activePage !== pageNumber) {
this.instance.docViewer.displayPageLocation(pageNumber, 0, 0);
}
}

View File

@ -31,7 +31,7 @@
<div class="left-container">
<div class="table-header">
<span class="all-caps-label">
{{'project-listing.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="project-listing.table-header.bulk-select.label"></div>
@ -45,70 +45,74 @@
</div>
</div>
<div class="table-col-names">
<div class="flex-2 small-label min-width" translate="project-listing.table-col-names.name.label"></div>
<div class="flex-1 small-label min-width" translate="project-listing.table-col-names.owner.label"></div>
<div class="status small-label flex-end min-width"
translate="project-listing.table-col-names.status.label"></div>
</div>
<div class="grid-container">
<div class="table-col-name">
<span class="small-label" translate="project-listing.table-col-names.name.label"></span>
</div>
<div class="table-col-name">
<span class="small-label" translate="project-listing.table-col-names.owner.label"></span>
</div>
<div class="table-col-name flex-end">
<span class="small-label" translate="project-listing.table-col-names.status.label"></span>
</div>
<div *ngIf="appStateService.allProjects?.length === 0 " translate="project-listing.no-projects.label"></div>
<div class="no-data" *ngIf="appStateService.allProjects?.length === 0 "
translate="project-listing.no-projects.label"></div>
<div *ngFor="let pw of appStateService.allProjects | sortBy:sortingOption.order:sortingOption.column"
[routerLink]="'/ui/projects/'+pw.project.projectId"
class="table-item pointer"
>
<div class="flex-2">
<div class="table-item-title table-item-title--large">
{{pw.project.projectName}}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:document"></mat-icon>
{{documentCount(pw)}}</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
{{userCount(pw)}}</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>
{{pw.project.date | date:'mediumDate'}}
<div class="table-item pointer"
[routerLink]="'/ui/projects/'+pw.project.projectId"
*ngFor="let pw of appStateService.allProjects | sortBy:sortingOption.order:sortingOption.column"
>
<div>
<div class="table-item-title table-item-title--large">
{{pw.project.projectName}}
</div>
<div *ngIf="pw.project.dueDate">
<mat-icon svgIcon="red:flash"></mat-icon>
{{pw.project.dueDate | date:'mediumDate'}}
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:document"></mat-icon>
{{documentCount(pw)}}</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
{{userCount(pw)}}</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>
{{pw.project.date | date:'mediumDate'}}
</div>
<div *ngIf="pw.project.dueDate">
<mat-icon svgIcon="red:lightning"></mat-icon>
{{pw.project.dueDate | date:'mediumDate'}}
</div>
</div>
</div>
<div>
<redaction-initials-avatar [userId]="pw.project.ownerId"
withName="true"
></redaction-initials-avatar>
</div>
<div class="status-container">
<redaction-status-bar
[config]="getProjectStatusConfig(pw)"
></redaction-status-bar>
<div class="action-buttons">
<button mat-icon-button color="accent" (click)="openDeleteProjectDialog($event,pw.project)"
[matTooltip]="'project-listing.delete.action.label'|translate">
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<button mat-icon-button color="accent" (click)="openProjectDetailsDialog($event,pw)"
[matTooltip]="'project-listing.report.action.label'|translate">
<mat-icon svgIcon="red:report"></mat-icon>
</button>
<button color="accent" (click)="openAssignProjectOwnerDialog($event,pw.project)" mat-icon-button
[matTooltip]="'project-listing.assign.action.label'|translate">
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
</div>
</div>
</div>
<div class="flex-1">
<redaction-initials-avatar [username]="getOwnerName(pw)"
color="lightgray-red"
withName="true"
></redaction-initials-avatar>
</div>
<div class="status">
<redaction-status-bar
[config]="[{ color: 'under-review', length: 2}, { length: 1, color: 'finished'}]"
></redaction-status-bar>
</div>
<div class="action-buttons">
<button mat-icon-button color="accent" (click)="openDeleteProjectDialog($event,pw.project)"
[matTooltip]="'project-listing.delete.action.label'|translate">
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<button mat-icon-button color="accent" (click)="openProjectDetailsDialog($event,pw)"
[matTooltip]="'project-listing.report.action.label'|translate">
<mat-icon svgIcon="red:report"></mat-icon>
</button>
<!-- <button mat-icon-button (click)="editProject($event,pw.project)">-->
<!-- <mat-icon svgIcon="red:edit"></mat-icon>-->
<!-- </button>-->
<button color="accent" (click)="openAssignProjectOwnerDialog($event,pw.project)" mat-icon-button
[matTooltip]="'project-listing.assign.action.label'|translate">
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
</div>
</div>
</div>
<div class="right-fixed-container">

View File

@ -11,19 +11,15 @@
.left-container {
width: calc(100vw - #{$right-container-width} - 130px);
.table-item {
&:hover {
.status {
display: none;
}
}
.stats-subtitle {
margin-top: 6px;
}
.grid-container {
grid-template-columns: 2fr 1fr auto;
}
.status, .action-buttons {
.stats-subtitle {
margin-top: 6px;
}
.status-container {
width: 160px;
}
}

View File

@ -68,10 +68,6 @@ export class ProjectListingScreenComponent implements OnInit {
return this.appStateService.allProjects.length - this.activeProjects;
}
public getOwnerName(pw: ProjectWrapper) {
return this._userService.getNameForId(pw.project.ownerId);
}
public documentCount(project: ProjectWrapper) {
return project.files.length;
}
@ -99,4 +95,18 @@ export class ProjectListingScreenComponent implements OnInit {
public openAssignProjectOwnerDialog($event: MouseEvent, project: Project) {
this._dialogService.openAssignProjectMembersAndOwnerDialog($event, project);
}
public getProjectStatusConfig(pw: ProjectWrapper) {
const obj = pw.files.reduce((acc, file) => {
const status = file.status;
if (!acc[status]) {
acc[status] = 1;
} else {
acc[status]++;
}
return acc;
}, {})
return Object.keys(obj).sort().map(status => ({length: obj[status], color: status}));
}
}

View File

@ -51,75 +51,92 @@
</div>
</div>
<div class="grid-container">
<!-- Table column names-->
<div class="table-col-name"></div>
<div class="table-col-names">
<div class="select-oval placeholder"></div>
<div class="flex-6 small-label min-width" translate="project-overview.table-col-names.name.label"></div>
<div class="flex-4 min-width pointer" (click)="toggleSortByAddedOn()">
<div class="table-col-name">
<span class="small-label" translate="project-overview.table-col-names.name.label"></span>
</div>
<div class="table-col-name pointer" (click)="toggleSortByAddedOn()">
<span class="small-label" translate="project-overview.table-col-names.added-on.label"></span>
<div class="sort-arrows-container">
<mat-icon svgIcon="red:arrow-up" [color]="sortingOption === sortingOptions[0] ? 'primary' : 'currentColor'"></mat-icon>
<mat-icon svgIcon="red:arrow-down" [color]="sortingOption === sortingOptions[1] ? 'primary' : 'currentColor'"></mat-icon>
<mat-icon svgIcon="red:arrow-up"
[color]="sortingOption === sortingOptions[0] ? 'primary' : 'currentColor'"></mat-icon>
<mat-icon svgIcon="red:arrow-down"
[color]="sortingOption === sortingOptions[1] ? 'primary' : 'currentColor'"></mat-icon>
</div>
</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 flex-end min-width"
translate="project-overview.table-col-names.status.label"></div>
</div>
<div class="table-item"
[class.pointer]="canOpenFile(fileStatus.status)"
*ngFor="let fileStatus of appStateService.activeProject.files | sortBy: sortingOption.order:sortingOption.column; trackBy:fileId"
[routerLink]="canOpenFile(fileStatus.status) ? ['/ui/projects/'+activeProject.projectId+'/file/'+fileStatus.fileId] : []">
<div class="select-oval"
[class.active]="isFileSelected(fileStatus)"
(click)="toggleFileSelected($event, fileStatus)"></div>
<div class="flex-6 table-item-title min-width" [matTooltip]="'['+fileStatus.status+'] '+fileStatus.filename ">
{{ fileStatus.filename }}
<div class="table-col-name">
<span class="small-label" translate="project-overview.table-col-names.needs-work.label"></span>
</div>
<div class="small-label flex-4 min-width">
{{ fileStatus.added | date:'d MMM. yyyy, hh:mm a' }}
<div class="table-col-name">
<span class="small-label" translate="project-overview.table-col-names.assigned-to.label"></span>
</div>
<div class="flex-2 min-width needs-work">
<redaction-annotation-icon type="redaction"></redaction-annotation-icon>
<redaction-annotation-icon type="hint"></redaction-annotation-icon>
<div class="table-col-name flex-end">
<span class="small-label" translate="project-overview.table-col-names.status.label"></span>
</div>
<div class="small-label flex-2 assigned-to min-width">
<redaction-initials-avatar
withName="true"
[username]="getFileOwnerUsername(fileStatus)"
></redaction-initials-avatar>
</div>
<div class="table-item"
[class.pointer]="canOpenFile(fileStatus.status)"
*ngFor="let fileStatus of appStateService.activeProject.files | sortBy: sortingOption.order:sortingOption.column; trackBy:fileId"
[routerLink]="canOpenFile(fileStatus.status) ? ['/ui/projects/'+activeProject.projectId+'/file/'+fileStatus.fileId] : []">
<div class="status-container flex-1 min-width">
<redaction-status-bar
[config]="[{ color: (fileStatus.status === 'PROCESSED' ? 'finished': fileStatus.status ==='ERROR'?'under-approval' : 'under-review'), length: 1 }]"
></redaction-status-bar>
</div>
<div class="pr-0">
<div class="select-oval"
[class.active]="isFileSelected(fileStatus)"
(click)="toggleFileSelected($event, fileStatus)"></div>
</div>
<div class="table-item-title" [matTooltip]="'['+fileStatus.status+'] '+fileStatus.filename ">
{{ fileStatus.filename }}
</div>
<div class="small-label">
{{ fileStatus.added | date:'d MMM. yyyy, hh:mm a' }}
</div>
<div class="needs-work">
<redaction-annotation-icon type="redaction"></redaction-annotation-icon>
<redaction-annotation-icon type="hint"></redaction-annotation-icon>
</div>
<div class="assigned-to">
<redaction-initials-avatar
withName="true"
[userId]="fileStatus.currentReviewer"
></redaction-initials-avatar>
</div>
<div class="status-container">
<redaction-status-bar
[config]="[{ color: (fileStatus.status === 'PROCESSED' ? 'finished': fileStatus.status ==='ERROR'?'under-approval' : 'under-review'), length: 1 }]"
></redaction-status-bar>
<div class="action-buttons">
<button (click)="openDeleteFileDialog($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.delete.action.label'|translate">
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<button (click)="$event.stopPropagation()" color="accent" mat-icon-button
[matTooltip]="'project-overview.report.action.label'|translate">
<mat-icon svgIcon="red:report"></mat-icon>
</button>
<button (click)="openAssignFileOwnerDialog($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.assign.action.label'|translate">
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
<button (click)="reanalyseFile($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.bar-charts.action.label'|translate">
<mat-icon svgIcon="red:analyse"></mat-icon>
</button>
</div>
</div>
<div class="action-buttons flex-3 min-width">
<button (click)="openDeleteFileDialog($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.delete.action.label'|translate">
<mat-icon svgIcon="red:trash"></mat-icon>
</button>
<button (click)="$event.stopPropagation()" color="accent" mat-icon-button
[matTooltip]="'project-overview.report.action.label'|translate">
<mat-icon svgIcon="red:report"></mat-icon>
</button>
<button (click)="openAssignFileOwnerDialog($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.assign.action.label'|translate">
<mat-icon svgIcon="red:assign"></mat-icon>
</button>
<button (click)="reanalyseFile($event,fileStatus)" color="accent" mat-icon-button
[matTooltip]="'project-overview.bar-charts.action.label'|translate">
<mat-icon svgIcon="red:analyse"></mat-icon>
</button>
</div>
</div>
</div>
@ -151,7 +168,7 @@
{{ appStateService.activeProject.project.date | date:'d MMM. yyyy' }}
</div>
<div *ngIf="appStateService.activeProject.project.dueDate">
<mat-icon svgIcon="red:flash"></mat-icon>
<mat-icon svgIcon="red:lightning"></mat-icon>
{{appStateService.activeProject.project.dueDate | date:'mediumDate'}}
</div>
</div>
@ -161,9 +178,8 @@
</div>
<div class="owner flex-row mt-20">
<redaction-initials-avatar [username]="ownerName"
<redaction-initials-avatar [userId]="activeProject.ownerId"
size="large"
color="lightgray-red"
withName="true"
></redaction-initials-avatar>
</div>
@ -175,8 +191,8 @@
<div class="project-team mt-20">
<div class="all-caps-label" translate="project-overview.project-details.project-team.label"></div>
<div class="flex mt-20 members-container">
<div *ngFor="let username of displayMembers" class="member">
<redaction-initials-avatar [username]="username" size="large"></redaction-initials-avatar>
<div *ngFor="let userId of displayMembers" class="member">
<redaction-initials-avatar [userId]="userId" size="large"></redaction-initials-avatar>
</div>
<div class="member" *ngIf="overflowCount">
<div class="oval large white-dark">+{{overflowCount}}</div>

View File

@ -4,10 +4,6 @@
display: none;
}
.min-width {
min-width: 60px;
}
.select-all-container {
display: flex;
gap: 16px;
@ -18,14 +14,16 @@
}
}
.pr-0 {
padding-right: 0 !important;
}
.select-oval {
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid $grey-5;
background-color: $white;
padding: 0;
margin-left: 16px;
cursor: pointer;
&.active {
@ -37,25 +35,20 @@
}
}
.table-item {
.needs-work {
display: flex;
flex-direction: row;
gap: 4px;
}
.grid-container {
grid-template-columns: auto 3fr 2fr 1fr 2fr auto;
.status-container {
display: flex;
justify-content: flex-end;
}
&:hover {
.assigned-to, .status-container {
display: none;
.table-item {
.table-item-title {
line-height: 80px;
}
.action-buttons {
margin-left: 32px;
.needs-work {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 4px;
}
}
}

View File

@ -1,17 +1,17 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {FileStatus, ReanalysisControllerService, StatusControllerService} from '@redaction/red-ui-http';
import {NotificationService} from '../../notification/notification.service';
import {AppStateService} from '../../state/app-state.service';
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 {UserService} from '../../user/user.service';
import {SortingOption} from '../../utils/types';
import {DoughnutChartConfig} from '../../components/simple-doughnut-chart/simple-doughnut-chart.component';
import {groupBy} from '../../utils/functions';
import {DialogService} from '../../dialogs/dialog.service';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { FileStatus, ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http';
import { NotificationService } from '../../notification/notification.service';
import { AppStateService } from '../../state/app-state.service';
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 { UserService } from '../../user/user.service';
import { SortingOption } from '../../utils/types';
import { DoughnutChartConfig } from '../../components/simple-doughnut-chart/simple-doughnut-chart.component';
import { groupBy } from '../../utils/functions';
import { DialogService } from '../../dialogs/dialog.service';
@Component({
@ -23,11 +23,11 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
private _selectedFileIds: string[] = [];
public sortingOptions: SortingOption[] = [
{label: 'project-overview.sorting.recent.label', order: 'desc', column: 'lastUpdated'},
{label: 'project-overview.sorting.oldest.label', order: 'asc', 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.recent.label', order: 'desc', column: 'lastUpdated' },
{ label: 'project-overview.sorting.oldest.label', order: 'asc', 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' }
];
public sortingOption: SortingOption = this.sortingOptions[0];
public documentsChartData: DoughnutChartConfig[] = [];
@ -69,20 +69,12 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
return this._userService.user;
}
public get members() {
return this.activeProject.memberIds.map(m => this._userService.getName(this._userService.getUserById(m)));
}
public get displayMembers() {
return this.members.slice(0, 6);
return this.activeProject.memberIds.slice(0, 6);
}
public get overflowCount() {
return this.members.length > 6 ? this.members.length - 6 : 0;
}
public get ownerName() {
return this._userService.getNameForId(this.activeProject.ownerId);
return this.activeProject.memberIds.length > 6 ? this.activeProject.memberIds.length - 6 : 0;
}
private _getFileStatus() {
@ -95,7 +87,7 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
const groups = groupBy(this.appStateService.activeProject.files, 'status');
this.documentsChartData = [];
for (const key of Object.keys(groups)) {
this.documentsChartData.push({value: groups[key].length, color: key, label: key});
this.documentsChartData.push({ value: groups[key].length, color: key, label: key });
}
}
@ -118,7 +110,8 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
}
public areAllFilesSelected() {
return this._selectedFileIds.length === this.appStateService.activeProject.files.length;
return this.appStateService.activeProject.files.length !== 0 &&
this._selectedFileIds.length === this.appStateService.activeProject.files.length;
}
public isFileSelected(file: FileStatus) {
@ -189,10 +182,6 @@ export class ProjectOverviewScreenComponent implements OnInit, OnDestroy {
return fileStatus === 'PROCESSING' || fileStatus === 'REVIEWED' || true;
}
public getFileOwnerUsername(fileStatus: FileStatus) {
return this._userService.getNameForId(fileStatus.currentReviewer);
}
public toggleSortByAddedOn() {
const sortedByRecent: boolean = (this.sortingOption === this.sortingOptions[0]);
this.sortingOption = sortedByRecent ? this.sortingOptions[1] : this.sortingOptions[0];

View File

@ -128,7 +128,7 @@ export class AppStateService {
const projects = await this._projectControllerService.getProjects().toPromise();
if (projects) {
let mappedProjects = projects.map(p => {
const mappedProjects = projects.map(p => {
return new ProjectWrapper(p, this._getExistingFiles(p));
});
@ -150,9 +150,9 @@ export class AppStateService {
const files = await this._statusControllerService.getProjectStatus(project.project.projectId).toPromise();
const oldFiles = [...project.files];
for (let file of files) {
for (const file of files) {
let found = false;
for (let oldFile of oldFiles) {
for (const oldFile of oldFiles) {
if (oldFile.fileId === file.fileId) {
// emit when analysis count changed
if (oldFile.numberOfAnalyses !== file.numberOfAnalyses) {

View File

@ -52,7 +52,7 @@
},
"content": {
"text": {
"label": "<strong>Selected Text: </strong> {{value}}"
"label": "Selected text:"
},
"dictionary": {
"add": {

View File

@ -1,4 +0,0 @@
<svg height="511pt" viewBox="-91 0 511 511.99948" width="511pt" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="m315.5 211h-124.214844l107.253906-188.585938c2.640626-4.640624 2.609376-10.339843-.074218-14.957031-2.683594-4.617187-7.625-7.457031-12.964844-7.457031h-180c-6.460938 0-12.199219 4.140625-14.234375 10.273438l-90 270.996093c-1.519531 4.574219-.75 9.597657 2.070313 13.507813 2.820312 3.90625 7.34375 6.222656 12.164062 6.222656h127.292969l-81.089844 190.113281c-2.886719 6.769531-.441406 14.628907 5.777344 18.5625 6.21875 3.933594 14.371093 2.773438 19.25-2.730469l240-271c8.546875-9.65625 1.679687-24.945312-11.230469-24.945312zm-189.921875 206.832031 53.71875-125.945312c4.210937-9.875-3.039063-20.886719-13.796875-20.886719h-129.214844l80.039063-241h143.386719l-107.25 188.582031c-5.671876 9.972657 1.535156 22.417969 13.039062 22.417969h116.679688zm0 0"/>
</svg>

Before

Width:  |  Height:  |  Size: 902 B

View File

@ -10,3 +10,19 @@
width: 18px;
height: 18px;
}
.mat-checkbox-layout {
.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;
}
}

View File

@ -1,3 +1,7 @@
.mat-dialog-container {
border-radius: 8px;
}
.dialog {
position: relative;
min-height: 80px;

View File

@ -1,53 +1,63 @@
@import "red-variables";
@import "red-mixins";
.red-input-group {
display: flex;
flex-direction: column;
margin-top: 5px;
margin-bottom: 8px;
margin-top: 13px;
label {
height: 14px;
opacity: 0.6;
&:first-child {
margin-top: 0;
}
input, textarea, mat-select {
box-sizing: border-box;
width: 322px;
padding-left: 11px;
padding-right: 11px;
border: 1px solid $grey-5;
font-family: Inter, sans-serif;
font-size: 11px;
font-weight: 500;
letter-spacing: 0;
line-height: 14px;
margin-bottom: 5px;
background-color: #FFFFFF;
border-radius: 8px;
outline: none;
margin-top: 3px;
min-height: 34px;
&:focus {
border-color: $grey-1;
}
}
mat-select {
width: 220px;
.mat-select-trigger {
height: 32px;
}
.mat-select-value {
vertical-align: middle;
}
}
textarea {
resize: none;
box-sizing: border-box;
padding-left: 11px;
padding-right: 11px;
width: 300px;
border: 1px solid $grey-3;
opacity: 0.4;
border-radius: 2px;
background-color: #FFFFFF;
outline: none;
&:focus {
border-color: $grey-1;
}
padding-top: 7px;
padding-bottom: 7px;
@include no-scroll-bar();
}
input {
box-sizing: border-box;
padding-left: 11px;
padding-right: 11px;
height: 34px;
width: 300px;
border: 1px solid $grey-3;
opacity: 0.4;
border-radius: 2px;
background-color: #FFFFFF;
outline: none;
label {
opacity: 0.7;
font-size: 11px;
letter-spacing: 0;
line-height: 14px;
margin-bottom: 2px;
&:focus {
border-color: $grey-1;
&.mat-checkbox-layout {
opacity: 1;
}
}
}

View File

@ -11,7 +11,8 @@
color: $accent;
.arrow-wrapper {
width: 24px;
width: 16px;
margin-right: 8px;
text-align: center;
mat-icon {
@ -24,23 +25,5 @@
&.padding-left {
padding-left: 64px;
}
.mat-checkbox-layout {
margin-left: 4px;
.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;
}
}
}
}

View File

@ -8,3 +8,12 @@
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin no-scroll-bar {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
&::-webkit-scrollbar {
width: 0;
background: transparent; /* Chrome/Safari/Webkit */
}
}

View File

@ -1,5 +1,4 @@
@import "red-variables";
@import "red-mixins";
html, body {
margin: 0;
@ -89,22 +88,6 @@ html, body {
flex: 2;
}
.flex-3 {
flex: 3;
}
.flex-4 {
flex: 4;
}
.flex-5 {
flex: 5;
}
.flex-6 {
flex: 6;
}
.mt-5 {
margin-top: 5px;
}
@ -172,27 +155,6 @@ html, body {
}
}
.breadcrumbs-container {
display: flex;
gap: 8px;
.breadcrumb {
text-decoration: none;
color: $accent;
font-weight: 600;
&:last-child {
color: $primary;
@include line-clamp(1);
}
.mat-icon {
vertical-align: middle;
width: 6px;
}
}
}
.divider {
height: 1px;
opacity: 0.15;

View File

@ -16,59 +16,83 @@
}
}
.table-col-names {
display: flex;
text-transform: uppercase;
border-bottom: 1px solid rgba(226, 228, 233, 0.9);
align-items: center;
.grid-container {
display: grid;
position: relative;
> div {
padding: 8px 16px;
.no-data {
grid-column: 1/-1;
}
.table-col-name {
font-weight: 600;
display: flex;
gap: 8px;
padding: 8px 16px;
border-bottom: 1px solid rgba(226, 228, 233, 0.9);
align-items: center;
gap: 6px;
}
text-transform: uppercase;
.sort-arrows-container {
mat-icon {
display: block;
width: 6px;
height: 11px;
.sort-arrows-container {
mat-icon {
display: block;
width: 6px;
height: 11px;
}
}
}
}
.table-item {
display: flex;
align-items: center;
height: 80px;
border-bottom: 1px solid rgba(226, 228, 233, 0.9);
.table-item {
display: contents;
> div {
padding: 0 16px;
}
> div {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
width: 100%;
position: relative;
box-sizing: border-box;
}
.table-item-title {
font-weight: 600;
@include line-clamp(1);
}
.table-item-title {
font-weight: 600;
@include line-clamp(1);
}
.action-buttons {
display: none;
}
> div {
height: 80px;
border-bottom: 1px solid rgba(226, 228, 233, 0.9);
padding: 0 16px;
}
&:hover {
background-color: #F9FAFB;
.action-buttons {
display: flex;
position: absolute;
display: none;
right: 0;
top: 0;
height: 100%;
width: fit-content;
flex-direction: row;
align-items: center;
justify-content: flex-end;
padding-left: 100px;
padding-right: 8px;
background: linear-gradient(to right, rgba(244, 245, 247, 0) 0%, #F4F5F7 35%);
mat-icon {
width: 14px;
}
}
&:hover {
> div {
background-color: #F9FAFB;
}
.action-buttons {
display: flex;
}
}
}
}