diff --git a/apps/red-ui/src/app/app-routing.module.ts b/apps/red-ui/src/app/app-routing.module.ts new file mode 100644 index 000000000..bf29d8918 --- /dev/null +++ b/apps/red-ui/src/app/app-routing.module.ts @@ -0,0 +1,212 @@ +import { AuthErrorComponent } from './screens/auth-error/auth-error.component'; +import { AuthGuard } from './auth/auth.guard'; +import { PdfViewerScreenComponent } from './screens/pdf-viewer-screen/pdf-viewer-screen.component'; +import { CompositeRouteGuard } from './utils/composite-route.guard'; +import { RedRoleGuard } from './auth/red-role.guard'; +import { HtmlDebugScreenComponent } from './screens/html-debug-screen/html-debug-screen.component'; +import { BaseScreenComponent } from './screens/base-screen/base-screen.component'; +import { ProjectListingScreenComponent } from './screens/project-listing-screen/project-listing-screen.component'; +import { AppStateGuard } from './state/app-state.guard'; +import { ProjectOverviewScreenComponent } from './screens/project-overview-screen/project-overview-screen.component'; +import { FilePreviewScreenComponent } from './screens/file/file-preview-screen/file-preview-screen.component'; +import { DownloadsListScreenComponent } from './screens/downloads-list-screen/downloads-list-screen.component'; +import { RuleSetsListingScreenComponent } from './screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component'; +import { DictionaryListingScreenComponent } from './screens/admin/dictionary-listing-screen/dictionary-listing-screen.component'; +import { DictionaryOverviewScreenComponent } from './screens/admin/dictionary-overview-screen/dictionary-overview-screen.component'; +import { PendingChangesGuard } from './utils/can-deactivate.guard'; +import { RulesScreenComponent } from './screens/admin/rules-screen/rules-screen.component'; +import { FileAttributesListingScreenComponent } from './screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component'; +import { WatermarkScreenComponent } from './screens/admin/watermark-screen/watermark-screen.component'; +import { DefaultColorsScreenComponent } from './screens/admin/default-colors-screen/default-colors-screen.component'; +import { UserListingScreenComponent } from './screens/admin/user-listing-screen/user-listing-screen.component'; +import { LicenseInformationScreenComponent } from './screens/admin/license-information-screen/license-information-screen.component'; +import { DigitalSignatureScreenComponent } from './screens/admin/digital-signature-screen/digital-signature-screen.component'; +import { AuditScreenComponent } from './screens/admin/audit-screen/audit-screen.component'; +import { RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; + +const routes = [ + { + path: '', + redirectTo: 'ui/projects', + pathMatch: 'full' + }, + { + path: 'auth-error', + component: AuthErrorComponent, + canActivate: [AuthGuard] + }, + { + path: 'pdf-preview/:projectId/:fileId', + component: PdfViewerScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard] + } + }, + { + path: 'html-debug/:projectId/:fileId', + component: HtmlDebugScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard] + } + }, + + { + path: 'ui', + component: BaseScreenComponent, + children: [ + { + path: 'projects', + component: ProjectListingScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'projects/:projectId', + component: ProjectOverviewScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'projects/:projectId/file/:fileId', + component: FilePreviewScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'downloads', + component: DownloadsListScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'admin', + children: [ + { path: '', redirectTo: 'project-templates', pathMatch: 'full' }, + { + path: 'project-templates', + children: [ + { + path: '', + component: RuleSetsListingScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: ':ruleSetId', + children: [ + { + path: 'dictionaries', + children: [ + { + path: '', + component: DictionaryListingScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: ':type', + component: DictionaryOverviewScreenComponent, + canActivate: [CompositeRouteGuard], + canDeactivate: [PendingChangesGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + } + ] + }, + { + path: 'rules', + component: RulesScreenComponent, + canActivate: [CompositeRouteGuard], + canDeactivate: [PendingChangesGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'file-attributes', + component: FileAttributesListingScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'watermark', + component: WatermarkScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'default-colors', + component: DefaultColorsScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { path: '', redirectTo: 'dictionaries', pathMatch: 'full' } + ] + } + ] + }, + { + path: 'users', + component: UserListingScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'license-info', + component: LicenseInformationScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'digital-signature', + component: DigitalSignatureScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + }, + { + path: 'audit', + component: AuditScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] + } + } + ] + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule {} diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts index ac4295fd6..f56c0f802 100644 --- a/apps/red-ui/src/app/app.module.ts +++ b/apps/red-ui/src/app/app.module.ts @@ -1,7 +1,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AppComponent } from './app.component'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http'; @@ -38,20 +38,16 @@ import { ServiceWorkerModule } from '@angular/service-worker'; import { environment } from '../environments/environment'; import { AuthModule } from './auth/auth.module'; import { FileUploadDownloadModule } from './upload-download/file-upload-download.module'; -import { FullPageLoadingIndicatorComponent } from './utils/full-page-loading-indicator/full-page-loading-indicator.component'; +import { FullPageLoadingIndicatorComponent } from './components/full-page-loading-indicator/full-page-loading-indicator.component'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; -import { InitialsAvatarComponent } from './common/initials-avatar/initials-avatar.component'; +import { InitialsAvatarComponent } from './components/initials-avatar/initials-avatar.component'; import { StatusBarComponent } from './components/status-bar/status-bar.component'; import { LogoComponent } from './logo/logo.component'; -import { CompositeRouteGuard } from './utils/composite-route.guard'; -import { AppStateGuard } from './state/app-state.guard'; import { SimpleDoughnutChartComponent } from './components/simple-doughnut-chart/simple-doughnut-chart.component'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { AnnotationIconComponent } from './components/annotation-icon/annotation-icon.component'; -import { AuthGuard } from './auth/auth.guard'; import { AuthErrorComponent } from './screens/auth-error/auth-error.component'; -import { RedRoleGuard } from './auth/red-role.guard'; import { MatListModule } from '@angular/material/list'; import { AssignOwnerDialogComponent } from './dialogs/assign-owner-dialog/assign-owner-dialog.component'; import { MatDatepickerModule } from '@angular/material/datepicker'; @@ -61,28 +57,28 @@ import { HumanizePipe } from './utils/humanize.pipe'; import { CommentsComponent } from './components/comments/comments.component'; import { ManualAnnotationDialogComponent } from './dialogs/manual-redaction-dialog/manual-annotation-dialog.component'; import { ToastComponent } from './components/toast/toast.component'; -import { FilterComponent } from './common/filter/filter.component'; +import { FilterComponent } from './components/filter/filter.component'; import { TableColNameComponent } from './components/table-col-name/table-col-name.component'; -import { ProjectDetailsComponent } from './screens/project-overview-screen/project-details/project-details.component'; +import { ProjectDetailsComponent } from './components/project-details/project-details.component'; import { PageIndicatorComponent } from './screens/file/page-indicator/page-indicator.component'; import { NeedsWorkBadgeComponent } from './components/needs-work-badge/needs-work-badge.component'; -import { ProjectListingEmptyComponent } from './screens/empty-states/project-listing-empty/project-listing-empty.component'; +import { ProjectListingEmptyComponent } from './components/empty-states/project-listing-empty/project-listing-empty.component'; import { AnnotationActionsComponent } from './screens/file/annotation-actions/annotation-actions.component'; -import { ProjectListingDetailsComponent } from './screens/project-listing-screen/project-listing-details/project-listing-details.component'; -import { FileActionsComponent } from './common/file-actions/file-actions.component'; +import { ProjectListingDetailsComponent } from './components/project-listing-details/project-listing-details.component'; +import { FileActionsComponent } from './components/file-actions/file-actions.component'; import { TypeAnnotationIconComponent } from './components/type-annotation-icon/type-annotation-icon.component'; import { TypeFilterComponent } from './components/type-filter/type-filter.component'; import { DictionaryAnnotationIconComponent } from './components/dictionary-annotation-icon/dictionary-annotation-icon.component'; -import { BulkActionsComponent } from './screens/project-overview-screen/bulk-actions/bulk-actions.component'; +import { ProjectOverviewBulkActionsComponent } from './components/bulk-actions/project-overview-bulk-actions.component'; import { HttpCacheInterceptor } from '@redaction/red-cache'; -import { HiddenActionComponent } from './common/hidden-action/hidden-action.component'; +import { HiddenActionComponent } from './components/hidden-action/hidden-action.component'; import { IconButtonComponent } from './components/buttons/icon-button/icon-button.component'; import { UserButtonComponent } from './components/buttons/user-button/user-button.component'; import { CircleButtonComponent } from './components/buttons/circle-button/circle-button.component'; import { ChevronButtonComponent } from './components/buttons/chevron-button/chevron-button.component'; import { DictionaryListingScreenComponent } from './screens/admin/dictionary-listing-screen/dictionary-listing-screen.component'; import { SyncWidthDirective } from './utils/sync-width.directive'; -import { AddEditDictionaryDialogComponent } from './screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; +import { AddEditDictionaryDialogComponent } from './dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; import { DictionaryOverviewScreenComponent } from './screens/admin/dictionary-overview-screen/dictionary-overview-screen.component'; import { ColorPickerModule } from 'ngx-color-picker'; import { AceEditorModule } from 'ng2-ace-editor'; @@ -94,208 +90,42 @@ import { RulesScreenComponent } from './screens/admin/rules-screen/rules-screen. import { WatermarkScreenComponent } from './screens/admin/watermark-screen/watermark-screen.component'; import { PdfViewerScreenComponent } from './screens/pdf-viewer-screen/pdf-viewer-screen.component'; import { HtmlDebugScreenComponent } from './screens/html-debug-screen/html-debug-screen.component'; -import { ProjectListingActionsComponent } from './screens/project-listing-screen/project-listing-actions/project-listing-actions.component'; +import { ProjectListingActionsComponent } from './components/project-listing-actions/project-listing-actions.component'; import { HasScrollbarDirective } from './utils/has-scrollbar.directive'; import { RuleSetsListingScreenComponent } from './screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component'; -import { AddEditRuleSetDialogComponent } from './screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component'; +import { AddEditRuleSetDialogComponent } from './dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component'; import { RuleSetActionsComponent } from './components/rule-set-actions/rule-set-actions.component'; import { TabsComponent } from './components/rule-set-view-switch/tabs.component'; import { MatSliderModule } from '@angular/material/slider'; -import { PendingChangesGuard } from './utils/can-deactivate.guard'; import { OverwriteFilesDialogComponent } from './dialogs/overwrite-files-dialog/overwrite-files-dialog.component'; import { KeycloakService } from 'keycloak-angular'; import { FileDownloadBtnComponent } from './components/buttons/file-download-btn/file-download-btn.component'; import { LicenseInformationScreenComponent } from './screens/admin/license-information-screen/license-information-screen.component'; import { DefaultColorsScreenComponent } from './screens/admin/default-colors-screen/default-colors-screen.component'; -import { EditColorDialogComponent } from './screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component'; +import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component'; import { DownloadsListScreenComponent } from './screens/downloads-list-screen/downloads-list-screen.component'; import { DigitalSignatureScreenComponent } from './screens/admin/digital-signature-screen/digital-signature-screen.component'; import { ScrollingModule } from '@angular/cdk/scrolling'; import { RemoveAnnotationsDialogComponent } from './dialogs/remove-annotations-dialog/remove-annotations-dialog.component'; import { NgxChartsModule } from '@swimlane/ngx-charts'; -import { ComboChartComponent, ComboSeriesVerticalComponent } from './screens/admin/license-information-screen/combo-chart'; +import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { ForceRedactionDialogComponent } from './dialogs/force-redaction-dialog/force-redaction-dialog.component'; import { AuditScreenComponent } from './screens/admin/audit-screen/audit-screen.component'; import { PaginationComponent } from './components/pagination/pagination.component'; +import { FileAttributesListingScreenComponent } from './screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component'; import { SearchInputComponent } from './components/search-input/search-input.component'; +import { AppRoutingModule } from './app-routing.module'; +import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component'; +import { ConfirmDeleteFileAttributeDialogComponent } from './dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component'; +import { DocumentInfoDialogComponent } from './dialogs/document-info-dialog/document-info-dialog.component'; +import { DocumentInfoComponent } from './components/document-info/document-info.component'; +import { FileWorkloadComponent } from './components/file-workload/file-workload.component'; export function HttpLoaderFactory(httpClient: HttpClient) { return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json'); } -const routes = [ - { - path: '', - redirectTo: 'ui/projects', - pathMatch: 'full' - }, - { - path: 'auth-error', - component: AuthErrorComponent, - canActivate: [AuthGuard] - }, - { - path: 'pdf-preview/:projectId/:fileId', - component: PdfViewerScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard] - } - }, - { - path: 'html-debug/:projectId/:fileId', - component: HtmlDebugScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard] - } - }, - - { - path: 'ui', - component: BaseScreenComponent, - children: [ - { - path: 'projects', - component: ProjectListingScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'projects/:projectId', - component: ProjectOverviewScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'projects/:projectId/file/:fileId', - component: FilePreviewScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'downloads', - component: DownloadsListScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'admin', - children: [ - { path: '', redirectTo: 'project-templates', pathMatch: 'full' }, - { - path: 'project-templates', - children: [ - { - path: '', - component: RuleSetsListingScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: ':ruleSetId', - children: [ - { - path: 'dictionaries', - children: [ - { - path: '', - component: DictionaryListingScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: ':type', - component: DictionaryOverviewScreenComponent, - canActivate: [CompositeRouteGuard], - canDeactivate: [PendingChangesGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - } - ] - }, - { - path: 'rules', - component: RulesScreenComponent, - canActivate: [CompositeRouteGuard], - canDeactivate: [PendingChangesGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'watermark', - component: WatermarkScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'default-colors', - component: DefaultColorsScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { path: '', redirectTo: 'dictionaries', pathMatch: 'full' } - ] - } - ] - }, - { - path: 'users', - component: UserListingScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'license-info', - component: LicenseInformationScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'digital-signature', - component: DigitalSignatureScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - }, - { - path: 'audit', - component: AuditScreenComponent, - canActivate: [CompositeRouteGuard], - data: { - routeGuards: [AuthGuard, RedRoleGuard, AppStateGuard] - } - } - ] - } - ] - } -]; - const matImports = [ MatDialogModule, MatNativeDateModule, @@ -356,7 +186,7 @@ const matImports = [ TypeAnnotationIconComponent, TypeFilterComponent, DictionaryAnnotationIconComponent, - BulkActionsComponent, + ProjectOverviewBulkActionsComponent, FileActionsComponent, HiddenActionComponent, IconButtonComponent, @@ -392,7 +222,13 @@ const matImports = [ ComboSeriesVerticalComponent, AuditScreenComponent, PaginationComponent, - SearchInputComponent + FileAttributesListingScreenComponent, + SearchInputComponent, + AddEditFileAttributeDialogComponent, + ConfirmDeleteFileAttributeDialogComponent, + DocumentInfoDialogComponent, + DocumentInfoComponent, + FileWorkloadComponent ], imports: [ BrowserModule, @@ -411,7 +247,7 @@ const matImports = [ deps: [HttpClient] } }), - RouterModule.forRoot(routes), + AppRoutingModule, NgpSortModule, ...matImports, ToastrModule.forRoot({ diff --git a/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.ts b/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.ts index c4318ebc3..d81a46fa9 100644 --- a/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.ts +++ b/apps/red-ui/src/app/components/admin-page-header/admin-breadcrumbs.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { AppStateService } from '../../state/app-state.service'; -import { UserPreferenceService } from '../../common/service/user-preference.service'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { UserPreferenceService } from '../../utils/user-preference.service'; +import { PermissionsService } from '../../utils/permissions.service'; @Component({ selector: 'redaction-admin-breadcrumbs', diff --git a/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.html b/apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.html similarity index 100% rename from apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.html rename to apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.html diff --git a/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.scss b/apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.scss rename to apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.scss diff --git a/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts b/apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.ts similarity index 86% rename from apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts rename to apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.ts index 52a25d881..9c61f246d 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/bulk-actions/bulk-actions.component.ts +++ b/apps/red-ui/src/app/components/bulk-actions/project-overview-bulk-actions.component.ts @@ -1,20 +1,20 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; -import { AppStateService } from '../../../state/app-state.service'; -import { UserService } from '../../../user/user.service'; +import { AppStateService } from '../../state/app-state.service'; +import { UserService } from '../../user/user.service'; import { FileManagementControllerService, ReanalysisControllerService } from '@redaction/red-ui-http'; -import { DialogService } from '../../../dialogs/dialog.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; -import { FileStatusWrapper } from '../../file/model/file-status.wrapper'; -import { FileActionService } from '../../file/service/file-action.service'; +import { DialogService } from '../../dialogs/dialog.service'; +import { PermissionsService } from '../../utils/permissions.service'; +import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; +import { FileActionService } from '../../screens/file/service/file-action.service'; import { Observable } from 'rxjs'; -import { StatusOverlayService } from '../../../upload-download/status-overlay.service'; +import { StatusOverlayService } from '../../upload-download/status-overlay.service'; @Component({ - selector: 'redaction-bulk-actions', - templateUrl: './bulk-actions.component.html', - styleUrls: ['./bulk-actions.component.scss'] + selector: 'redaction-project-overview-bulk-actions', + templateUrl: './project-overview-bulk-actions.component.html', + styleUrls: ['./project-overview-bulk-actions.component.scss'] }) -export class BulkActionsComponent { +export class ProjectOverviewBulkActionsComponent { @Input() selectedFileIds: string[]; @Output() private reload = new EventEmitter(); public loading = false; diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html index bdad4ece7..8dcc3efc8 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html @@ -7,7 +7,9 @@ [disabled]="disabled" [class.small]="small" [class.overlay]="showDot" + [class.dummy]="dummy" mat-icon-button + [disableRipple]="dummy" > diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss index b2af7125f..5e1167f4b 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.scss @@ -36,4 +36,8 @@ button { background-color: $yellow-2; } } + + &.dummy { + cursor: default; + } } diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts index 085885e2a..e8f899791 100644 --- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts +++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts @@ -14,6 +14,7 @@ export class CircleButtonComponent implements OnInit { @Input() disabled = false; @Input() small = false; @Input() type: 'default' | 'primary' | 'warn' | 'dark-bg' = 'default'; + @Input() dummy = false; @Output() action = new EventEmitter(); constructor() {} diff --git a/apps/red-ui/src/app/components/buttons/file-download-btn/file-download-btn.component.ts b/apps/red-ui/src/app/components/buttons/file-download-btn/file-download-btn.component.ts index 1686b0f38..21b7edb01 100644 --- a/apps/red-ui/src/app/components/buttons/file-download-btn/file-download-btn.component.ts +++ b/apps/red-ui/src/app/components/buttons/file-download-btn/file-download-btn.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { ProjectWrapper } from '../../../state/model/project.wrapper'; import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapper'; import { FileDownloadService } from '../../../upload-download/file-download.service'; diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.html b/apps/red-ui/src/app/components/combo-chart/combo-chart.component.html similarity index 100% rename from apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.html rename to apps/red-ui/src/app/components/combo-chart/combo-chart.component.html diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.scss b/apps/red-ui/src/app/components/combo-chart/combo-chart.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.scss rename to apps/red-ui/src/app/components/combo-chart/combo-chart.component.scss diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.ts b/apps/red-ui/src/app/components/combo-chart/combo-chart.component.ts similarity index 100% rename from apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-chart.component.ts rename to apps/red-ui/src/app/components/combo-chart/combo-chart.component.ts diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-series-vertical.component.ts b/apps/red-ui/src/app/components/combo-chart/combo-series-vertical.component.ts similarity index 100% rename from apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/combo-series-vertical.component.ts rename to apps/red-ui/src/app/components/combo-chart/combo-series-vertical.component.ts diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/index.ts b/apps/red-ui/src/app/components/combo-chart/index.ts similarity index 100% rename from apps/red-ui/src/app/screens/admin/license-information-screen/combo-chart/index.ts rename to apps/red-ui/src/app/components/combo-chart/index.ts diff --git a/apps/red-ui/src/app/components/document-info/document-info.component.html b/apps/red-ui/src/app/components/document-info/document-info.component.html new file mode 100644 index 000000000..aafddbd4f --- /dev/null +++ b/apps/red-ui/src/app/components/document-info/document-info.component.html @@ -0,0 +1,44 @@ +
+
+ + +
+
+ +
+
+
+
{{ attr.label }}:
+
{{ file.fileAttributes.attributeIdToValue[attr.id] || '-' }}
+
+
+ +
+
+ + {{ 'file-preview.tabs.document-info.details.project' | translate: { projectName: project.name } }} +
+
+ + {{ 'file-preview.tabs.document-info.details.pages' | translate: { pages: file.numberOfPages } }} +
+
+ + {{ 'file-preview.tabs.document-info.details.created-on' | translate: { date: file.added | date: 'mediumDate' } }} +
+
+ + {{ 'file-preview.tabs.document-info.details.due' | translate: { date: project.project.dueDate | date: 'mediumDate' } }} +
+
+
diff --git a/apps/red-ui/src/app/components/document-info/document-info.component.scss b/apps/red-ui/src/app/components/document-info/document-info.component.scss new file mode 100644 index 000000000..0a3a68fc2 --- /dev/null +++ b/apps/red-ui/src/app/components/document-info/document-info.component.scss @@ -0,0 +1,58 @@ +@import '../../../assets/styles/red-variables'; +@import '../../../assets/styles/red-mixins'; + +:host { + display: block; + position: absolute; + height: 100%; + background: white; + z-index: 1; + width: 100%; + @include inset-shadow; +} + +.right-content { + flex-direction: column; + + @include scroll-bar; + overflow: hidden; + + &:hover { + overflow: auto; + } + + &.has-scrollbar .section { + padding-right: 13px; + } +} + +.right-title > div { + display: flex; + + > redaction-circle-button:not(:last-child) { + margin-right: 2px; + } +} + +.section { + padding: 24px; + flex-direction: column; + + > div { + justify-content: flex-start; + } + + &:not(:last-child) { + border-bottom: 1px solid $separator; + } +} + +.attribute { + > .small-label { + margin-bottom: 6px; + } + + &:not(:last-child) { + margin-bottom: 16px; + } +} diff --git a/apps/red-ui/src/app/components/document-info/document-info.component.ts b/apps/red-ui/src/app/components/document-info/document-info.component.ts new file mode 100644 index 000000000..aea583951 --- /dev/null +++ b/apps/red-ui/src/app/components/document-info/document-info.component.ts @@ -0,0 +1,34 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FileAttributeConfig, FileAttributesControllerService, FileStatus } from '@redaction/red-ui-http'; +import { AppStateService } from '../../state/app-state.service'; +import { DialogService } from '../../dialogs/dialog.service'; + +@Component({ + selector: 'redaction-document-info', + templateUrl: './document-info.component.html', + styleUrls: ['./document-info.component.scss'] +}) +export class DocumentInfoComponent implements OnInit { + @Input() file: FileStatus; + @Output() closeDocumentInfoView = new EventEmitter(); + + public fileAttributesConfig: FileAttributeConfig[]; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _fileAttributesService: FileAttributesControllerService, + private readonly _dialogService: DialogService + ) { + this.fileAttributesConfig = this._appStateService.fileAttributesConfig.filter((attr) => attr.visible); + } + + ngOnInit(): void {} + + public get project() { + return this._appStateService.getProjectById(this.file.projectId); + } + + public edit() { + this._dialogService.openDocumentInfoDialog(this.file); + } +} diff --git a/apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.html b/apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.html similarity index 56% rename from apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.html rename to apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.html index a2fd1faef..a89955182 100644 --- a/apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.html +++ b/apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.html @@ -1,12 +1,6 @@
- diff --git a/apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.scss b/apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.scss rename to apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.scss diff --git a/apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.ts b/apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.ts similarity index 100% rename from apps/red-ui/src/app/screens/empty-states/project-listing-empty/project-listing-empty.component.ts rename to apps/red-ui/src/app/components/empty-states/project-listing-empty/project-listing-empty.component.ts diff --git a/apps/red-ui/src/app/common/file-actions/file-actions.component.html b/apps/red-ui/src/app/components/file-actions/file-actions.component.html similarity index 93% rename from apps/red-ui/src/app/common/file-actions/file-actions.component.html rename to apps/red-ui/src/app/components/file-actions/file-actions.component.html index fba698805..72b362611 100644 --- a/apps/red-ui/src/app/common/file-actions/file-actions.component.html +++ b/apps/red-ui/src/app/components/file-actions/file-actions.component.html @@ -49,6 +49,15 @@ > + + (); actionMenuOpen: boolean; @@ -40,6 +41,10 @@ export class FileActionsComponent implements OnInit { } } + public toggleViewDocumentInfo() { + this.actionPerformed.emit('view-document-info'); + } + public get tooltipPosition() { return this.screen === 'file-preview' ? 'below' : 'above'; } diff --git a/apps/red-ui/src/app/components/file-workload/file-workload.component.html b/apps/red-ui/src/app/components/file-workload/file-workload.component.html new file mode 100644 index 000000000..54f1c8512 --- /dev/null +++ b/apps/red-ui/src/app/components/file-workload/file-workload.component.html @@ -0,0 +1,133 @@ +
+
+ +
+
+
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }} + +
+ +
+
+ {{ 'file-preview.no-annotations-for-page' | translate }} +
+ +
+
+
+ +
+ +
+
+ {{ annotation.typeLabel | translate }} +
+
+ {{ annotation.descriptor | translate }}: {{ annotation.dictionary | humanize: false }} +
+
+ : {{ annotation.content }} +
+
+ + + + + + + + +
+
+ +
+
+
+
+
+ + + + + + {{ filter.key | humanize: false }} + + + + + + + + + diff --git a/apps/red-ui/src/app/components/file-workload/file-workload.component.scss b/apps/red-ui/src/app/components/file-workload/file-workload.component.scss new file mode 100644 index 000000000..149a4fb93 --- /dev/null +++ b/apps/red-ui/src/app/components/file-workload/file-workload.component.scss @@ -0,0 +1,134 @@ +@import '../../../assets/styles/red-variables'; +@import '../../../assets/styles/red-mixins'; + +.right-content { + .no-annotations { + padding: 24px; + text-align: center; + } + + .quick-navigation, + .annotations { + overflow-y: scroll; + outline: none; + + &.active-panel { + background-color: #fafafa; + } + } + + .quick-navigation { + border-right: 1px solid $separator; + min-width: 61px; + overflow: hidden; + display: flex; + flex-direction: column; + + .jump { + min-height: 32px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: background-color 0.25s; + + &:not(.disabled):hover { + background-color: $grey-6; + } + + mat-icon { + width: 16px; + height: 16px; + } + + &.disabled { + cursor: default; + + mat-icon { + opacity: 0.3; + } + } + } + + .pages { + @include no-scroll-bar(); + overflow: auto; + flex: 1; + } + } + + .page-separator { + border-bottom: 1px solid $separator; + height: 32px; + box-sizing: border-box; + padding: 0 10px; + display: flex; + align-items: center; + background-color: $grey-6; + } + + .annotations { + overflow: hidden; + width: 100%; + height: calc(100% - 32px); + + .annotation-wrapper { + display: flex; + border-bottom: 1px solid $separator; + + .active-marker { + min-width: 4px; + min-height: 100%; + } + + &.active { + .active-marker { + background-color: $primary; + } + } + + .annotation { + padding: 10px 21px 10px 6px; + font-size: 11px; + line-height: 14px; + cursor: pointer; + display: flex; + flex-direction: column; + + &.removed { + text-decoration: line-through; + color: $grey-7; + } + + .details { + display: flex; + position: relative; + } + + redaction-type-annotation-icon { + margin-top: 6px; + margin-right: 10px; + } + } + + &:hover { + background-color: #f9fafb; + + ::ng-deep .annotation-actions { + display: flex; + } + } + } + + &:hover { + overflow-y: auto; + @include scroll-bar; + } + + &.has-scrollbar:hover { + .annotation { + padding-right: 10px; + } + } + } +} diff --git a/apps/red-ui/src/app/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/components/file-workload/file-workload.component.ts new file mode 100644 index 000000000..6d261bdfa --- /dev/null +++ b/apps/red-ui/src/app/components/file-workload/file-workload.component.ts @@ -0,0 +1,296 @@ +import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild } from '@angular/core'; +import { FilterModel } from '../filter/model/filter.model'; +import { AnnotationWrapper } from '../../screens/file/model/annotation.wrapper'; +import { AnnotationProcessingService } from '../../screens/file/service/annotation-processing.service'; +import { MatDialogRef, MatDialogState } from '@angular/material/dialog'; +import scrollIntoView from 'scroll-into-view-if-needed'; +import { debounce } from '../../utils/debounce'; +import { FileDataModel } from '../../screens/file/model/file-data.model'; + +const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; +const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; + +@Component({ + selector: 'redaction-file-workload', + templateUrl: './file-workload.component.html', + styleUrls: ['./file-workload.component.scss'] +}) +export class FileWorkloadComponent { + public displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {}; + private _annotations: AnnotationWrapper[]; + + @Input() + set annotations(value: AnnotationWrapper[]) { + this._annotations = value; + // this.computeQuickNavButtonsState(); + } + + @Input() selectedAnnotations: AnnotationWrapper[]; + @Input() activeViewerPage: number; + @Input() shouldDeselectAnnotationsOnPageChange: boolean; + @Input() dialogRef: MatDialogRef; + @Input() annotationFilters: FilterModel[]; + @Input() fileData: FileDataModel; + @Input() hideSkipped: boolean; + @Input() annotationActionsTemplate: TemplateRef; + + @Output() selectAnnotation = new EventEmitter(); + @Output() selectPage = new EventEmitter(); + @Output() toggleSkipped = new EventEmitter(); + + public quickScrollFirstEnabled = false; + public quickScrollLastEnabled = false; + public displayedPages: number[] = []; + public pagesPanelActive = true; + + @ViewChild('annotationsElement') private _annotationsElement: ElementRef; + @ViewChild('quickNavigation') private _quickNavigationElement: ElementRef; + + constructor(private _changeDetectorRef: ChangeDetectorRef, private _annotationProcessingService: AnnotationProcessingService) {} + + private get firstSelectedAnnotation() { + return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null; + } + + private static _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') { + if (elements.length > 0) { + scrollIntoView(elements[0], { + behavior: 'smooth', + scrollMode: mode, + block: 'start', + inline: 'start' + }); + } + } + + public annotationIsSelected(annotation: AnnotationWrapper) { + return this.selectedAnnotations?.find((a) => a.id === annotation.id); + } + + public logAnnotation(annotation: AnnotationWrapper) { + console.log(annotation); + } + + @debounce(0) + public filtersChanged(filters: FilterModel[]) { + this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this._annotations, filters); + this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key)); + this.computeQuickNavButtonsState(); + this._changeDetectorRef.markForCheck(); + } + + public computeQuickNavButtonsState() { + setTimeout(() => { + const element: HTMLElement = this._quickNavigationElement.nativeElement.querySelector(`#pages`); + const { scrollTop, scrollHeight, clientHeight } = element; + this.quickScrollFirstEnabled = scrollTop !== 0; + this.quickScrollLastEnabled = scrollHeight !== scrollTop + clientHeight; + }, 0); + } + + public annotationClicked(annotation: AnnotationWrapper) { + this.pagesPanelActive = false; + this.selectAnnotation.emit(annotation); + } + + @HostListener('window:keyup', ['$event']) + handleKeyEvent($event: KeyboardEvent) { + if (!ALL_HOTKEY_ARRAY.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN) { + return; + } + + if ($event.key === 'ArrowLeft') { + this.pagesPanelActive = true; + return; + } + + if ($event.key === 'ArrowRight') { + this.pagesPanelActive = false; + // if we activated annotationsPanel - select first annotation from this page in case there is no + // selected annotation on this page + if (!this.pagesPanelActive) { + this._selectFirstAnnotationOnCurrentPageIfNecessary(); + } + return; + } + + if (!this.pagesPanelActive) { + this._navigateAnnotations($event); + } else { + this._navigatePages($event); + } + + this._changeDetectorRef.detectChanges(); + } + + public scrollAnnotations() { + if (this.firstSelectedAnnotation?.pageNumber === this.activeViewerPage) { + return; + } + this.scrollAnnotationsToPage(this.activeViewerPage, 'always'); + } + + public scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') { + const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`); + FileWorkloadComponent._scrollToFirstElement(elements, mode); + } + + @debounce() + public scrollToSelectedAnnotation() { + if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) { + return; + } + const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.firstSelectedAnnotation.id}"].active`); + FileWorkloadComponent._scrollToFirstElement(elements); + } + + public scrollQuickNavigation() { + let quickNavPageIndex = this.displayedPages.findIndex((p) => p >= this.activeViewerPage); + if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== this.activeViewerPage) { + quickNavPageIndex = Math.max(0, quickNavPageIndex - 1); + } + this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]); + } + + public scrollQuickNavFirst() { + if (this.displayedPages.length > 0) { + this._scrollQuickNavigationToPage(this.displayedPages[0]); + } + } + + public scrollQuickNavLast() { + if (this.displayedPages.length > 0) { + this._scrollQuickNavigationToPage(this.displayedPages[this.displayedPages.length - 1]); + } + } + + public pageSelectedByClick($event: number) { + this.pagesPanelActive = true; + this.selectPage.emit($event); + } + + public preventKeyDefault($event: KeyboardEvent) { + if (COMMAND_KEY_ARRAY.includes($event.key)) { + $event.preventDefault(); + } + } + + private _selectFirstAnnotationOnCurrentPageIfNecessary() { + if ( + (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) && + this.displayedPages.indexOf(this.activeViewerPage) >= 0 + ) { + this.selectAnnotation.emit(this.displayedAnnotations[this.activeViewerPage].annotations[0]); + } + } + + private _navigateAnnotations($event: KeyboardEvent) { + if (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) { + const pageIdx = this.displayedPages.indexOf(this.activeViewerPage); + if (pageIdx !== -1) { + // Displayed page has annotations + this.selectAnnotation.emit(this.displayedAnnotations[this.activeViewerPage].annotations[0]); + } else { + // Displayed page doesn't have annotations + if ($event.key === 'ArrowDown') { + const nextPage = this._nextPageWithAnnotations(); + this.shouldDeselectAnnotationsOnPageChange = false; + this.selectAnnotation.emit(this.displayedAnnotations[nextPage].annotations[0]); + } else { + const prevPage = this._prevPageWithAnnotations(); + this.shouldDeselectAnnotationsOnPageChange = false; + const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations; + this.selectAnnotation.emit(prevPageAnnotations[prevPageAnnotations.length - 1]); + } + } + } else { + const page = this.firstSelectedAnnotation.pageNumber; + const pageIdx = this.displayedPages.indexOf(page); + const annotationsOnPage = this.displayedAnnotations[page].annotations; + const idx = annotationsOnPage.findIndex((a) => a.id === this.firstSelectedAnnotation.id); + + if ($event.key === 'ArrowDown') { + if (idx + 1 !== annotationsOnPage.length) { + // If not last item in page + this.selectAnnotation.emit(annotationsOnPage[idx + 1]); + } else if (pageIdx + 1 < this.displayedPages.length) { + // If not last page + const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations; + this.shouldDeselectAnnotationsOnPageChange = false; + this.selectAnnotation.emit(nextPageAnnotations[0]); + } + } else { + if (idx !== 0) { + // If not first item in page + this.selectAnnotation.emit(annotationsOnPage[idx - 1]); + } else if (pageIdx) { + // If not first page + const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations; + this.shouldDeselectAnnotationsOnPageChange = false; + this.selectAnnotation.emit(prevPageAnnotations[prevPageAnnotations.length - 1]); + } + } + } + } + + 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.emit(this.displayedPages[pageIdx + 1]); + } + } else { + // If active page doesn't have annotations + const nextPage = this._nextPageWithAnnotations(); + if (nextPage) { + this.selectPage.emit(nextPage); + } + } + } else { + if (pageIdx !== -1) { + // If active page has annotations + if (pageIdx !== 0) { + this.selectPage.emit(this.displayedPages[pageIdx - 1]); + } + } else { + // If active page doesn't have annotations + const prevPage = this._prevPageWithAnnotations(); + if (prevPage) { + this.selectPage.emit(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.emit(this.displayedPages[idx]); + this.scrollAnnotations(); + break; + } + --idx; + } + return idx >= 0 ? this.displayedPages[idx] : null; + } + + private _scrollQuickNavigationToPage(page: number) { + const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`); + FileWorkloadComponent._scrollToFirstElement(elements); + } +} diff --git a/apps/red-ui/src/app/common/filter/filter.component.html b/apps/red-ui/src/app/components/filter/filter.component.html similarity index 100% rename from apps/red-ui/src/app/common/filter/filter.component.html rename to apps/red-ui/src/app/components/filter/filter.component.html diff --git a/apps/red-ui/src/app/common/filter/filter.component.scss b/apps/red-ui/src/app/components/filter/filter.component.scss similarity index 100% rename from apps/red-ui/src/app/common/filter/filter.component.scss rename to apps/red-ui/src/app/components/filter/filter.component.scss diff --git a/apps/red-ui/src/app/common/filter/filter.component.ts b/apps/red-ui/src/app/components/filter/filter.component.ts similarity index 100% rename from apps/red-ui/src/app/common/filter/filter.component.ts rename to apps/red-ui/src/app/components/filter/filter.component.ts diff --git a/apps/red-ui/src/app/common/filter/model/filter.model.ts b/apps/red-ui/src/app/components/filter/model/filter.model.ts similarity index 100% rename from apps/red-ui/src/app/common/filter/model/filter.model.ts rename to apps/red-ui/src/app/components/filter/model/filter.model.ts diff --git a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts b/apps/red-ui/src/app/components/filter/utils/filter-utils.ts similarity index 98% rename from apps/red-ui/src/app/common/filter/utils/filter-utils.ts rename to apps/red-ui/src/app/components/filter/utils/filter-utils.ts index 4b5458596..4941d5baa 100644 --- a/apps/red-ui/src/app/common/filter/utils/filter-utils.ts +++ b/apps/red-ui/src/app/components/filter/utils/filter-utils.ts @@ -1,7 +1,7 @@ import { FilterModel } from '../model/filter.model'; import { FileStatusWrapper } from '../../../screens/file/model/file-status.wrapper'; import { ProjectWrapper } from '../../../state/model/project.wrapper'; -import { PermissionsService } from '../../service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) { copySettings(oldFilters, newFilters); diff --git a/apps/red-ui/src/app/utils/full-page-loading-indicator/full-page-loading-indicator.component.html b/apps/red-ui/src/app/components/full-page-loading-indicator/full-page-loading-indicator.component.html similarity index 100% rename from apps/red-ui/src/app/utils/full-page-loading-indicator/full-page-loading-indicator.component.html rename to apps/red-ui/src/app/components/full-page-loading-indicator/full-page-loading-indicator.component.html diff --git a/apps/red-ui/src/app/utils/full-page-loading-indicator/full-page-loading-indicator.component.scss b/apps/red-ui/src/app/components/full-page-loading-indicator/full-page-loading-indicator.component.scss similarity index 100% rename from apps/red-ui/src/app/utils/full-page-loading-indicator/full-page-loading-indicator.component.scss rename to apps/red-ui/src/app/components/full-page-loading-indicator/full-page-loading-indicator.component.scss diff --git a/apps/red-ui/src/app/utils/full-page-loading-indicator/full-page-loading-indicator.component.ts b/apps/red-ui/src/app/components/full-page-loading-indicator/full-page-loading-indicator.component.ts similarity index 100% rename from apps/red-ui/src/app/utils/full-page-loading-indicator/full-page-loading-indicator.component.ts rename to apps/red-ui/src/app/components/full-page-loading-indicator/full-page-loading-indicator.component.ts diff --git a/apps/red-ui/src/app/common/hidden-action/hidden-action.component.html b/apps/red-ui/src/app/components/hidden-action/hidden-action.component.html similarity index 100% rename from apps/red-ui/src/app/common/hidden-action/hidden-action.component.html rename to apps/red-ui/src/app/components/hidden-action/hidden-action.component.html diff --git a/apps/red-ui/src/app/common/hidden-action/hidden-action.component.scss b/apps/red-ui/src/app/components/hidden-action/hidden-action.component.scss similarity index 100% rename from apps/red-ui/src/app/common/hidden-action/hidden-action.component.scss rename to apps/red-ui/src/app/components/hidden-action/hidden-action.component.scss diff --git a/apps/red-ui/src/app/common/hidden-action/hidden-action.component.ts b/apps/red-ui/src/app/components/hidden-action/hidden-action.component.ts similarity index 100% rename from apps/red-ui/src/app/common/hidden-action/hidden-action.component.ts rename to apps/red-ui/src/app/components/hidden-action/hidden-action.component.ts diff --git a/apps/red-ui/src/app/common/initials-avatar/initials-avatar.component.html b/apps/red-ui/src/app/components/initials-avatar/initials-avatar.component.html similarity index 100% rename from apps/red-ui/src/app/common/initials-avatar/initials-avatar.component.html rename to apps/red-ui/src/app/components/initials-avatar/initials-avatar.component.html diff --git a/apps/red-ui/src/app/common/initials-avatar/initials-avatar.component.scss b/apps/red-ui/src/app/components/initials-avatar/initials-avatar.component.scss similarity index 100% rename from apps/red-ui/src/app/common/initials-avatar/initials-avatar.component.scss rename to apps/red-ui/src/app/components/initials-avatar/initials-avatar.component.scss diff --git a/apps/red-ui/src/app/common/initials-avatar/initials-avatar.component.ts b/apps/red-ui/src/app/components/initials-avatar/initials-avatar.component.ts similarity index 100% rename from apps/red-ui/src/app/common/initials-avatar/initials-avatar.component.ts rename to apps/red-ui/src/app/components/initials-avatar/initials-avatar.component.ts diff --git a/apps/red-ui/src/app/components/needs-work-badge/needs-work-badge.component.ts b/apps/red-ui/src/app/components/needs-work-badge/needs-work-badge.component.ts index 2334c9b48..65b5a007f 100644 --- a/apps/red-ui/src/app/components/needs-work-badge/needs-work-badge.component.ts +++ b/apps/red-ui/src/app/components/needs-work-badge/needs-work-badge.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { AppStateService } from '../../state/app-state.service'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; import { ProjectWrapper } from '../../state/model/project.wrapper'; diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html b/apps/red-ui/src/app/components/project-details/project-details.component.html similarity index 98% rename from apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html rename to apps/red-ui/src/app/components/project-details/project-details.component.html index 035acb908..6c27d7bc5 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.html +++ b/apps/red-ui/src/app/components/project-details/project-details.component.html @@ -58,7 +58,7 @@
-
+
{{ 'project-overview.project-details.stats.documents' | translate: { count: appStateService.activeProject.files.length } }} diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.scss b/apps/red-ui/src/app/components/project-details/project-details.component.scss similarity index 94% rename from apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.scss rename to apps/red-ui/src/app/components/project-details/project-details.component.scss index cb4f0bd9e..0ab55b1f0 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.scss +++ b/apps/red-ui/src/app/components/project-details/project-details.component.scss @@ -1,4 +1,4 @@ -@import '../../../../assets/styles/red-variables'; +@import '../../../assets/styles/red-variables'; .header-wrapper { display: flex; diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts b/apps/red-ui/src/app/components/project-details/project-details.component.ts similarity index 80% rename from apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts rename to apps/red-ui/src/app/components/project-details/project-details.component.ts index b8c6f55ec..eea25d3ed 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-details/project-details.component.ts +++ b/apps/red-ui/src/app/components/project-details/project-details.component.ts @@ -1,13 +1,13 @@ import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { AppStateService } from '../../../state/app-state.service'; -import { groupBy } from '../../../utils/functions'; -import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { DialogService } from '../../../dialogs/dialog.service'; +import { AppStateService } from '../../state/app-state.service'; +import { groupBy } from '../../utils/functions'; +import { DoughnutChartConfig } from '../simple-doughnut-chart/simple-doughnut-chart.component'; +import { DialogService } from '../../dialogs/dialog.service'; import { Router } from '@angular/router'; -import { FilterModel } from '../../../common/filter/model/filter.model'; -import { PermissionsService } from '../../../common/service/permissions.service'; -import { TranslateChartService } from '../../../utils/translate-chart.service'; -import { StatusSorter } from '../../../common/sorters/status-sorter'; +import { FilterModel } from '../filter/model/filter.model'; +import { PermissionsService } from '../../utils/permissions.service'; +import { TranslateChartService } from '../../utils/translate-chart.service'; +import { StatusSorter } from '../../utils/sorters/status-sorter'; @Component({ selector: 'redaction-project-details', diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.html b/apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.html similarity index 95% rename from apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.html rename to apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.html index 55303082c..8d93d75d7 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.html +++ b/apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.html @@ -36,5 +36,5 @@ > - +
diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.scss b/apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.scss rename to apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.scss diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.ts b/apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.ts similarity index 85% rename from apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.ts rename to apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.ts index 5344a95b3..73f8a77ca 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-actions/project-listing-actions.component.ts +++ b/apps/red-ui/src/app/components/project-listing-actions/project-listing-actions.component.ts @@ -1,12 +1,12 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; -import { ProjectWrapper } from '../../../state/model/project.wrapper'; -import { StatusSorter } from '../../../common/sorters/status-sorter'; -import { download } from '../../../utils/file-download-utils'; -import { computerize } from '../../../utils/functions'; +import { PermissionsService } from '../../utils/permissions.service'; +import { ProjectWrapper } from '../../state/model/project.wrapper'; +import { StatusSorter } from '../../utils/sorters/status-sorter'; +import { download } from '../../utils/file-download-utils'; +import { computerize } from '../../utils/functions'; import { FileManagementControllerService } from '@redaction/red-ui-http'; -import { AppStateService } from '../../../state/app-state.service'; -import { DialogService } from '../../../dialogs/dialog.service'; +import { AppStateService } from '../../state/app-state.service'; +import { DialogService } from '../../dialogs/dialog.service'; @Component({ selector: 'redaction-project-listing-actions', diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.html b/apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.html similarity index 100% rename from apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.html rename to apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.html diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.scss b/apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.scss similarity index 92% rename from apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.scss rename to apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.scss index 05197f10b..91d537dd3 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.scss +++ b/apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.scss @@ -1,4 +1,4 @@ -@import '../../../../assets/styles/red-variables'; +@import '../../../assets/styles/red-variables'; :host { flex: 1; diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.ts b/apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.ts similarity index 81% rename from apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.ts rename to apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.ts index 5ee602320..586e1c6af 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-details/project-listing-details.component.ts +++ b/apps/red-ui/src/app/components/project-listing-details/project-listing-details.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { DoughnutChartConfig } from '../../../components/simple-doughnut-chart/simple-doughnut-chart.component'; -import { AppStateService } from '../../../state/app-state.service'; -import { FilterModel } from '../../../common/filter/model/filter.model'; +import { DoughnutChartConfig } from '../simple-doughnut-chart/simple-doughnut-chart.component'; +import { AppStateService } from '../../state/app-state.service'; +import { FilterModel } from '../filter/model/filter.model'; @Component({ selector: 'redaction-project-listing-details', diff --git a/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.ts b/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.ts index 902c3387a..5f6d4c15a 100644 --- a/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.ts +++ b/apps/red-ui/src/app/components/rule-set-actions/rule-set-actions.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { DialogService } from '../../dialogs/dialog.service'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; import { AppStateService } from '../../state/app-state.service'; import { Router } from '@angular/router'; diff --git a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html index 0ffea530a..c93576d79 100644 --- a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html +++ b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.html @@ -1,7 +1,7 @@
diff --git a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts index d73b195b5..93307b4c1 100644 --- a/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts +++ b/apps/red-ui/src/app/components/rule-set-view-switch/tabs.component.ts @@ -1,7 +1,8 @@ import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AppStateService } from '../../state/app-state.service'; -import { UserPreferenceService } from '../../common/service/user-preference.service'; +import { UserPreferenceService } from '../../utils/user-preference.service'; +import { PermissionsService } from '../../utils/permissions.service'; @Component({ selector: 'redaction-tabs', @@ -9,17 +10,19 @@ import { UserPreferenceService } from '../../common/service/user-preference.serv styleUrls: ['./tabs.component.scss'] }) export class TabsComponent implements OnInit { - @Input() public screen: 'rules' | 'dictionaries' | 'watermark' | 'default-colors'; + @Input() public screen: 'rules' | 'dictionaries' | 'watermark' | 'default-colors' | 'file-attributes'; - public tabs: { screen: string; onlyDevMode?: boolean; label?: string }[] = [ + public tabs: { screen: string; onlyDevMode?: boolean; onlyAdmin?: boolean; label?: string }[] = [ { screen: 'dictionaries' }, { screen: 'rules', onlyDevMode: true, label: 'rule-editor' }, { screen: 'default-colors' }, - { screen: 'watermark' } + { screen: 'watermark' }, + { screen: 'file-attributes', onlyAdmin: true, onlyDevMode: true } ]; constructor( public readonly userPreferenceService: UserPreferenceService, + public readonly permissionsService: PermissionsService, private readonly _router: Router, private readonly _appStateService: AppStateService ) {} diff --git a/apps/red-ui/src/app/components/simple-doughnut-chart/simple-doughnut-chart.component.ts b/apps/red-ui/src/app/components/simple-doughnut-chart/simple-doughnut-chart.component.ts index 4705f8767..9828a12f8 100644 --- a/apps/red-ui/src/app/components/simple-doughnut-chart/simple-doughnut-chart.component.ts +++ b/apps/red-ui/src/app/components/simple-doughnut-chart/simple-doughnut-chart.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Color } from '../../utils/types'; -import { FilterModel } from '../../common/filter/model/filter.model'; +import { FilterModel } from '../filter/model/filter.model'; export class DoughnutChartConfig { value: number; diff --git a/apps/red-ui/src/app/components/team-members/team-members.component.ts b/apps/red-ui/src/app/components/team-members/team-members.component.ts index f1f94c557..55e13d956 100644 --- a/apps/red-ui/src/app/components/team-members/team-members.component.ts +++ b/apps/red-ui/src/app/components/team-members/team-members.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; @Component({ selector: 'redaction-team-members', diff --git a/apps/red-ui/src/app/components/type-filter/type-filter.component.ts b/apps/red-ui/src/app/components/type-filter/type-filter.component.ts index ed7bbc11f..fe3702f18 100644 --- a/apps/red-ui/src/app/components/type-filter/type-filter.component.ts +++ b/apps/red-ui/src/app/components/type-filter/type-filter.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; -import { FilterModel } from '../../common/filter/model/filter.model'; +import { FilterModel } from '../filter/model/filter.model'; import { AppStateService } from '../../state/app-state.service'; import { DEFAULT_RUL_SET_UUID } from '../../utils/rule-set-default'; diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html b/apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html rename to apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.html diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss b/apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss similarity index 77% rename from apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss rename to apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss index c5eefa9a6..f13f823c5 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss +++ b/apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.scss @@ -1,4 +1,4 @@ -@import '../../../../../assets/styles/red-variables'; +@import '../../../assets/styles/red-variables'; .first-row { display: flex; diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts b/apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts similarity index 96% rename from apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts rename to apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts index 2d5d2a2e9..d5fcc1b64 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component.ts @@ -1,10 +1,10 @@ import { Component, Inject } from '@angular/core'; -import { AppStateService } from '../../../../state/app-state.service'; +import { AppStateService } from '../../state/app-state.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; import { Observable } from 'rxjs'; -import { NotificationService, NotificationType } from '../../../../notification/notification.service'; +import { NotificationService, NotificationType } from '../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; @Component({ diff --git a/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html b/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html new file mode 100644 index 000000000..52179a7da --- /dev/null +++ b/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.html @@ -0,0 +1,39 @@ +
+
+ {{ (fileAttribute ? 'add-edit-file-attribute.title.edit' : 'add-edit-file-attribute.title.new') | translate: { name: fileAttribute?.label } }} +
+ +
+
+
+ + +
+ +
+ + +
+ +
+ {{ 'add-edit-file-attribute.form.read-only' | translate }} +
+ +
+ {{ 'add-edit-file-attribute.form.visible' | translate }} +
+
+
+ +
+
+ + +
diff --git a/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.scss b/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts b/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts new file mode 100644 index 000000000..2eac7977f --- /dev/null +++ b/apps/red-ui/src/app/dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component.ts @@ -0,0 +1,60 @@ +import { Component, Inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { AppStateService } from '../../state/app-state.service'; +import { FileAttributeConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'redaction-add-edit-file-attribute-dialog', + templateUrl: './add-edit-file-attribute-dialog.component.html', + styleUrls: ['./add-edit-file-attribute-dialog.component.scss'] +}) +export class AddEditFileAttributeDialogComponent { + public fileAttributeForm: FormGroup; + public fileAttribute: FileAttributeConfig; + public ruleSetId: string; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _formBuilder: FormBuilder, + private readonly _fileAttributesService: FileAttributesControllerService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { fileAttribute: FileAttributeConfig; ruleSetId: string } + ) { + this.fileAttribute = data.fileAttribute; + this.ruleSetId = data.ruleSetId; + + this.fileAttributeForm = this._formBuilder.group({ + label: [this.fileAttribute?.label, Validators.required], + csvColumnHeader: [this.fileAttribute?.csvColumnHeader, Validators.required], + readonly: [this.fileAttribute ? !this.fileAttribute.editable : false], + visible: [this.fileAttribute?.visible] + }); + } + + public get changed(): boolean { + if (!this.fileAttribute) return true; + + for (const key of Object.keys(this.fileAttributeForm.getRawValue())) { + if (key === 'readonly') { + if (this.fileAttribute.editable === this.fileAttributeForm.get(key).value) { + return true; + } + } else if (this.fileAttribute[key] !== this.fileAttributeForm.get(key).value) { + return true; + } + } + + return false; + } + + async saveFileAttribute() { + const fileAttribute: FileAttributeConfig = { + id: this.fileAttribute?.id, + editable: !this.fileAttributeForm.get('readonly').value, + ...this.fileAttributeForm.getRawValue() + }; + await this._fileAttributesService.setFileAttributesConfiguration(fileAttribute, this.ruleSetId).toPromise(); + this.dialogRef.close(fileAttribute); + } +} diff --git a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.html b/apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.html rename to apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.html diff --git a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.scss b/apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.scss rename to apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.scss diff --git a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.ts b/apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.ts similarity index 97% rename from apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.ts rename to apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.ts index 73d38c520..1556eb426 100644 --- a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component.ts @@ -1,5 +1,5 @@ import { Component, Inject } from '@angular/core'; -import { AppStateService } from '../../../../state/app-state.service'; +import { AppStateService } from '../../state/app-state.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import * as moment from 'moment'; diff --git a/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html b/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html new file mode 100644 index 000000000..a9f8b28d8 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.html @@ -0,0 +1,21 @@ +
+
+ {{ 'confirm-delete-file-attribute.title' | translate: { name: fileAttribute.label } }} +
+ +
+
+ + + {{ 'confirm-delete-file-attribute.checkbox-' + (idx + 1) | translate }} + +
+ +
+ +
+
+ +
diff --git a/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss b/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss new file mode 100644 index 000000000..7ae5f7b64 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.scss @@ -0,0 +1,13 @@ +@import '../../../assets/styles/red-variables'; + +.dialog-header { + color: $primary; +} + +.heading { + margin-bottom: 24px; +} + +mat-checkbox:not(:last-of-type) { + margin-bottom: 6px; +} diff --git a/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts b/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts new file mode 100644 index 000000000..1dff590e8 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component, Inject } from '@angular/core'; +import { AppStateService } from '../../state/app-state.service'; +import { FileAttributeConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'redaction-confirm-delete-file-attribute-dialog', + templateUrl: './confirm-delete-file-attribute-dialog.component.html', + styleUrls: ['./confirm-delete-file-attribute-dialog.component.scss'] +}) +export class ConfirmDeleteFileAttributeDialogComponent { + public fileAttribute: FileAttributeConfig; + public ruleSetId: string; + public checkboxes = [{ value: false }, { value: false }]; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _fileAttributesService: FileAttributesControllerService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { fileAttribute: FileAttributeConfig; ruleSetId: string } + ) { + this.fileAttribute = data.fileAttribute; + this.ruleSetId = data.ruleSetId; + } + + public get valid() { + return this.checkboxes[0].value && this.checkboxes[1].value; + } + + async deleteFileAttribute() { + await this._fileAttributesService.deleteFileAttributesConfiguration(this.ruleSetId, this.fileAttribute.id).toPromise(); + this.dialogRef.close(true); + } + + public cancel() { + this.dialogRef.close(); + } +} diff --git a/apps/red-ui/src/app/dialogs/dialog.service.ts b/apps/red-ui/src/app/dialogs/dialog.service.ts index 03e7fa51b..d35edbca1 100644 --- a/apps/red-ui/src/app/dialogs/dialog.service.ts +++ b/apps/red-ui/src/app/dialogs/dialog.service.ts @@ -3,6 +3,7 @@ import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Colors, DictionaryControllerService, + FileAttributeConfig, FileManagementControllerService, FileStatus, ManualRedactionControllerService, @@ -21,12 +22,15 @@ import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper'; import { ManualAnnotationDialogComponent } from './manual-redaction-dialog/manual-annotation-dialog.component'; import { ManualAnnotationService } from '../screens/file/service/manual-annotation.service'; import { ProjectWrapper } from '../state/model/project.wrapper'; -import { AddEditDictionaryDialogComponent } from '../screens/admin/dictionary-listing-screen/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; -import { AddEditRuleSetDialogComponent } from '../screens/admin/rule-sets-listing-screen/add-edit-rule-set-dialog/add-edit-rule-set-dialog.component'; +import { AddEditDictionaryDialogComponent } from './add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; +import { AddEditRuleSetDialogComponent } from './add-edit-rule-set-dialog/add-edit-rule-set-dialog.component'; import { OverwriteFilesDialogComponent } from './overwrite-files-dialog/overwrite-files-dialog.component'; -import { EditColorDialogComponent } from '../screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component'; +import { EditColorDialogComponent } from './edit-color-dialog/edit-color-dialog.component'; import { RemoveAnnotationsDialogComponent } from './remove-annotations-dialog/remove-annotations-dialog.component'; import { ForceRedactionDialogComponent } from './force-redaction-dialog/force-redaction-dialog.component'; +import { AddEditFileAttributeDialogComponent } from './add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component'; +import { ConfirmDeleteFileAttributeDialogComponent } from './confirm-delete-file-attribute-dialog/confirm-delete-file-attribute-dialog.component'; +import { DocumentInfoDialogComponent } from './document-info-dialog/document-info-dialog.component'; const dialogConfig = { width: '662px', @@ -336,6 +340,62 @@ export class DialogService { return ref; } + public openAddEditFileAttributeDialog( + fileAttribute: FileAttributeConfig, + ruleSetId: string, + cb?: Function + ): MatDialogRef { + const ref = this._dialog.open(AddEditFileAttributeDialogComponent, { + ...dialogConfig, + data: { fileAttribute, ruleSetId }, + autoFocus: true + }); + + ref.afterClosed().subscribe((result) => { + if (result && cb) { + cb(result); + } + }); + + return ref; + } + + public openConfirmDeleteFileAttributeDialog( + fileAttribute: FileAttributeConfig, + ruleSetId: string, + cb?: Function + ): MatDialogRef { + const ref = this._dialog.open(ConfirmDeleteFileAttributeDialogComponent, { + ...dialogConfig, + data: { fileAttribute, ruleSetId }, + autoFocus: true + }); + + ref.afterClosed().subscribe((result) => { + if (result && cb) { + cb(result); + } + }); + + return ref; + } + + public openDocumentInfoDialog(file: FileStatus, cb?: Function): MatDialogRef { + const ref = this._dialog.open(DocumentInfoDialogComponent, { + ...dialogConfig, + data: file, + autoFocus: true + }); + + ref.afterClosed().subscribe((result) => { + if (result && cb) { + cb(result); + } + }); + + return ref; + } + openRemoveAnnotationModal($event: MouseEvent, annotation: AnnotationWrapper, callback: () => void) { $event?.stopPropagation(); diff --git a/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.html b/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.html new file mode 100644 index 000000000..73b6e159a --- /dev/null +++ b/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.html @@ -0,0 +1,19 @@ +
+
+ +
+
+
+ + +
+
+
+ +
+
+ + +
diff --git a/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.scss b/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.ts b/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.ts new file mode 100644 index 000000000..493d1ec47 --- /dev/null +++ b/apps/red-ui/src/app/dialogs/document-info-dialog/document-info-dialog.component.ts @@ -0,0 +1,45 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { FileAttributeConfig, FileAttributesControllerService, FileStatus } from '@redaction/red-ui-http'; +import { AppStateService } from '../../state/app-state.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ProjectWrapper } from '../../state/model/project.wrapper'; + +@Component({ + selector: 'redaction-document-info-dialog', + templateUrl: './document-info-dialog.component.html', + styleUrls: ['./document-info-dialog.component.scss'] +}) +export class DocumentInfoDialogComponent implements OnInit { + public documentInfoForm: FormGroup; + public file: FileStatus; + public attributes: FileAttributeConfig[]; + + private _project: ProjectWrapper; + + constructor( + private readonly _appStateService: AppStateService, + private readonly _formBuilder: FormBuilder, + private readonly _fileAttributesService: FileAttributesControllerService, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: FileStatus + ) { + this.file = this.data; + this._project = this._appStateService.getProjectById(this.file.projectId); + } + + async ngOnInit() { + this.attributes = (await this._fileAttributesService.getFileAttributesConfiguration(this._project.ruleSetId).toPromise()).fileAttributeConfigs.filter( + (attr) => attr.visible && attr.editable + ); + const formConfig = this.attributes.reduce((acc, attr) => ({ ...acc, [attr.id]: [this.file.fileAttributes.attributeIdToValue[attr.id]] }), {}); + this.documentInfoForm = this._formBuilder.group(formConfig); + } + + public async saveDocumentInfo() { + const attributeIdToValue = { ...this.file.fileAttributes.attributeIdToValue, ...this.documentInfoForm.getRawValue() }; + await this._fileAttributesService.setFileAttributes({ attributeIdToValue }, this.file.projectId, this.file.fileId).toPromise(); + this.file.fileAttributes = { attributeIdToValue }; + this.dialogRef.close(true); + } +} diff --git a/apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.html b/apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.html similarity index 100% rename from apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.html rename to apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.html diff --git a/apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.scss b/apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.scss similarity index 100% rename from apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.scss rename to apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.scss diff --git a/apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.ts b/apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.ts similarity index 95% rename from apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.ts rename to apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.ts index d7290ff44..00c52b071 100644 --- a/apps/red-ui/src/app/screens/admin/default-colors-screen/edit-color-dialog/edit-color-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/edit-color-dialog/edit-color-dialog.component.ts @@ -1,7 +1,7 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Colors, DictionaryControllerService } from '@redaction/red-ui-http'; -import { NotificationService, NotificationType } from '../../../../notification/notification.service'; +import { NotificationService, NotificationType } from '../../notification/notification.service'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; diff --git a/apps/red-ui/src/app/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts b/apps/red-ui/src/app/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts index f79ef8af3..fa4fd1e35 100644 --- a/apps/red-ui/src/app/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/force-redaction-dialog/force-redaction-dialog.component.ts @@ -7,7 +7,7 @@ import { NotificationService } from '../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { UserService } from '../../user/user.service'; import { ManualAnnotationService } from '../../screens/file/service/manual-annotation.service'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; export interface LegalBasisOption { label?: string; diff --git a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts index 0e27b091c..38ce3848a 100644 --- a/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts +++ b/apps/red-ui/src/app/dialogs/manual-redaction-dialog/manual-annotation-dialog.component.ts @@ -9,7 +9,7 @@ import { UserService } from '../../user/user.service'; import { ManualRedactionEntryWrapper } from '../../screens/file/model/manual-redaction-entry.wrapper'; import { ManualAnnotationService } from '../../screens/file/service/manual-annotation.service'; import { ManualAnnotationResponse } from '../../screens/file/model/manual-annotation-response'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; export interface LegalBasisOption { label?: string; diff --git a/apps/red-ui/src/app/icons/icons.module.ts b/apps/red-ui/src/app/icons/icons.module.ts index 7a0945248..164c8c62a 100644 --- a/apps/red-ui/src/app/icons/icons.module.ts +++ b/apps/red-ui/src/app/icons/icons.module.ts @@ -55,6 +55,7 @@ export class IconsModule { 'preview', 'radio-indeterminate', 'radio-selected', + 'read-only', 'ready-for-approval', 'refresh', 'report', @@ -63,8 +64,9 @@ export class IconsModule { 'sort-asc', 'sort-desc', 'status', - 'status-expand', 'status-collapse', + 'status-expand', + 'status-info', 'template', 'thumb-down', 'thumb-up', diff --git a/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts index 0d0dbfae7..7df53cc82 100644 --- a/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/audit-screen/audit-screen.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { AuditControllerService, AuditResponse, AuditSearchRequest } from '@redaction/red-ui-http'; import { TranslateService } from '@ngx-translate/core'; diff --git a/apps/red-ui/src/app/screens/admin/default-colors-screen/default-colors-screen.component.ts b/apps/red-ui/src/app/screens/admin/default-colors-screen/default-colors-screen.component.ts index eb686c824..7fdaf535b 100644 --- a/apps/red-ui/src/app/screens/admin/default-colors-screen/default-colors-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/default-colors-screen/default-colors-screen.component.ts @@ -3,7 +3,7 @@ import { AppStateService } from '../../../state/app-state.service'; import { Colors, DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; import { ActivatedRoute } from '@angular/router'; import { SortingOption, SortingService } from '../../../utils/sorting.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { DialogService } from '../../../dialogs/dialog.service'; @Component({ diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html index 558ca32e0..695d1740e 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html +++ b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.html @@ -42,7 +42,7 @@ {{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }} -
+
-
+
{{ dict.rank }}
-
+
diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss index 7ea9ffd49..defb8b5e7 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss +++ b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.scss @@ -4,7 +4,7 @@ .header-item { padding: 0 16px 0 10px; - .dictionary-actions-container { + .attributes-actions-container { display: flex; flex: 1; justify-content: flex-end; @@ -36,8 +36,7 @@ redaction-table-col-name::ng-deep { align-items: center; justify-content: flex-start; - &.analyzed, - &.rank { + &.center { justify-content: center; } diff --git a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts index b511b141a..fd6b9f05a 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/dictionary-listing-screen/dictionary-listing-screen.component.ts @@ -6,7 +6,7 @@ import { DialogService } from '../../../dialogs/dialog.service'; import { AppStateService } from '../../../state/app-state.service'; import { tap } from 'rxjs/operators'; import { forkJoin } from 'rxjs'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounce } from '../../../utils/debounce'; import { ActivatedRoute } from '@angular/router'; diff --git a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts index e36ab7b20..e150eb501 100644 --- a/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/dictionary-overview-screen/dictionary-overview-screen.component.ts @@ -2,7 +2,7 @@ import { Component, ElementRef, ViewChild } from '@angular/core'; import { DictionaryControllerService, TypeValue } from '@redaction/red-ui-http'; import { DialogService } from '../../../dialogs/dialog.service'; import { AppStateService } from '../../../state/app-state.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { ActivatedRoute, Router } from '@angular/router'; import { AceEditorComponent } from 'ng2-ace-editor'; import { debounce } from '../../../utils/debounce'; diff --git a/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts b/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts index 327553587..bbeb6b39b 100644 --- a/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/digital-signature-screen/digital-signature-screen.component.ts @@ -3,7 +3,7 @@ import { DigitalSignature, DigitalSignatureControllerService } from '@redaction/ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { NotificationService, NotificationType } from '../../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { lastIndexOfEnd } from '../../../utils/functions'; @Component({ diff --git a/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html new file mode 100644 index 000000000..b4feb342a --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.html @@ -0,0 +1,128 @@ +
+ + +
+
+
+
+
+ + +
+ + + {{ 'file-attributes-listing.table-header.title' | translate: { length: displayedAttributes.length } }} + + +
+ +
+ +
+
+
+ +
+
+ + + + + + + +
+ +
+
+ +
+ + + +
+
+
+ +
+ +
+ {{ attribute.label }} +
+ + + + +
+ +
+
+
+ + + + +
+
+
+
+
+
+ +
+
+
+ + diff --git a/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.scss b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.scss new file mode 100644 index 000000000..fcf232534 --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.scss @@ -0,0 +1,54 @@ +.page-header .actions { + display: flex; + justify-content: flex-end; +} + +.header-item { + padding: 0 24px 0 10px; +} + +redaction-table-col-name::ng-deep { + > div { + padding-left: 10px !important; + } +} + +.left-container { + width: 100vw; + + .header-item { + .attributes-actions-container { + display: flex; + flex: 1; + justify-content: flex-end; + + > *:not(:last-child) { + margin-right: 16px; + } + } + } + + cdk-virtual-scroll-viewport { + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: auto 1fr 1fr 3fr 11px; + + .table-item { + > div:not(.scrollbar-placeholder) { + padding-left: 10px; + } + + > div { + &.center { + align-items: center; + } + } + } + } + + &.has-scrollbar:hover { + ::ng-deep.cdk-virtual-scroll-content-wrapper { + grid-template-columns: auto 1fr 1fr 3fr; + } + } + } +} diff --git a/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts new file mode 100644 index 000000000..bd4ba6d31 --- /dev/null +++ b/apps/red-ui/src/app/screens/admin/file-attributes-listing-screen/file-attributes-listing-screen.component.ts @@ -0,0 +1,120 @@ +import { Component, OnInit } from '@angular/core'; +import { PermissionsService } from '../../../utils/permissions.service'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { FileAttributeConfig, FileAttributesControllerService } from '@redaction/red-ui-http'; +import { AppStateService } from '../../../state/app-state.service'; +import { ActivatedRoute } from '@angular/router'; +import { debounce } from '../../../utils/debounce'; +import { SortingOption, SortingService } from '../../../utils/sorting.service'; +import { DialogService } from '../../../dialogs/dialog.service'; + +@Component({ + selector: 'redaction-file-attributes-listing-screen', + templateUrl: './file-attributes-listing-screen.component.html', + styleUrls: ['./file-attributes-listing-screen.component.scss'] +}) +export class FileAttributesListingScreenComponent implements OnInit { + public searchForm: FormGroup; + public attributes: FileAttributeConfig[] = []; + public displayedAttributes: FileAttributeConfig[] = []; + public selectedFileAttributeIds: string[] = []; + public viewReady = false; + + constructor( + public readonly permissionsService: PermissionsService, + public readonly _sortingService: SortingService, + private readonly _formBuilder: FormBuilder, + private readonly _fileAttributesService: FileAttributesControllerService, + private readonly _appStateService: AppStateService, + private readonly _activatedRoute: ActivatedRoute, + private readonly _dialogService: DialogService + ) { + this._appStateService.activateRuleSet(_activatedRoute.snapshot.params.ruleSetId); + + this.searchForm = this._formBuilder.group({ + query: [''] + }); + + this.searchForm.valueChanges.subscribe((value) => this._executeSearch(value)); + } + + async ngOnInit() { + await this._loadData(); + } + + private async _loadData() { + this.viewReady = false; + try { + const response = await this._fileAttributesService.getFileAttributesConfiguration(this._appStateService.activeRuleSetId).toPromise(); + this.attributes = response?.fileAttributeConfigs || []; + } catch (e) { + } finally { + this._executeSearch(); + this.viewReady = true; + } + } + + public get noData(): boolean { + return this.displayedAttributes.length === 0; + } + + public get sortingOption(): SortingOption { + return this._sortingService.getSortingOption('file-attributes-listing'); + } + + public toggleSort($event) { + this._sortingService.toggleSort('file-attributes-listing', $event); + } + + @debounce(200) + private _executeSearch(value?: { query: string }) { + if (!value) { + value = { query: this.searchForm.get('query').value }; + } + this.displayedAttributes = this.attributes.filter((attribute) => attribute.label.toLowerCase().includes(value.query.toLowerCase())); + } + + public openAddEditAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) { + $event.stopPropagation(); + this._dialogService.openAddEditFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async () => { + await this._loadData(); + }); + } + + public openConfirmDeleteAttributeDialog($event: MouseEvent, fileAttribute?: FileAttributeConfig) { + $event.stopPropagation(); + this._dialogService.openConfirmDeleteFileAttributeDialog(fileAttribute, this._appStateService.activeRuleSetId, async () => { + await this._loadData(); + }); + } + + public toggleAttributeSelected($event: MouseEvent, attribute: FileAttributeConfig) { + $event.stopPropagation(); + const idx = this.selectedFileAttributeIds.indexOf(attribute.id); + if (idx === -1) { + this.selectedFileAttributeIds.push(attribute.id); + } else { + this.selectedFileAttributeIds.splice(idx, 1); + } + } + + public toggleSelectAll() { + if (this.areSomeAttributesSelected) { + this.selectedFileAttributeIds = []; + } else { + this.selectedFileAttributeIds = this.displayedAttributes.map((a) => a.id); + } + } + + public get areAllAttributesSelected() { + return this.displayedAttributes.length !== 0 && this.selectedFileAttributeIds.length === this.displayedAttributes.length; + } + + public get areSomeAttributesSelected() { + return this.selectedFileAttributeIds.length > 0; + } + + public isAttributeSelected(attribute: FileAttributeConfig) { + return this.selectedFileAttributeIds.indexOf(attribute.id) !== -1; + } +} diff --git a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts index 263d57718..1e5081dba 100644 --- a/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/license-information-screen/license-information-screen.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { LicenseReport, LicenseReportControllerService } from '@redaction/red-ui-http'; import { AppConfigService } from '../../../app-config/app-config.service'; import * as moment from 'moment'; diff --git a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts index 798199927..06cc809fa 100644 --- a/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/rule-sets-listing-screen/rule-sets-listing-screen.component.ts @@ -2,11 +2,11 @@ import { Component, OnInit } from '@angular/core'; import { SortingOption, SortingService } from '../../../utils/sorting.service'; import { DialogService } from '../../../dialogs/dialog.service'; import { AppStateService } from '../../../state/app-state.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounce } from '../../../utils/debounce'; import { RuleSetModel } from '@redaction/red-ui-http'; -import { UserPreferenceService } from '../../../common/service/user-preference.service'; +import { UserPreferenceService } from '../../../utils/user-preference.service'; @Component({ selector: 'redaction-rule-sets-listing-screen', diff --git a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts index 33be7fbc2..21c30d8d9 100644 --- a/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/rules-screen/rules-screen.component.ts @@ -1,5 +1,5 @@ import { Component, ElementRef, ViewChild } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { AceEditorComponent } from 'ng2-ace-editor'; import { RulesControllerService, RuleSetModel } from '@redaction/red-ui-http'; import { NotificationService, NotificationType } from '../../../notification/notification.service'; diff --git a/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts index 99d12a890..f987df0de 100644 --- a/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/user-listing-screen/user-listing-screen.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { UserService } from '../../../user/user.service'; import { User } from '@redaction/red-ui-http'; import { FormBuilder, FormGroup } from '@angular/forms'; diff --git a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html index ea6c8c85b..d4c0ca1ff 100644 --- a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html +++ b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.html @@ -5,14 +5,7 @@
- +
diff --git a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts index d0238e7f3..c6f6b89f6 100644 --- a/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts +++ b/apps/red-ui/src/app/screens/admin/watermark-screen/watermark-screen.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import WebViewer, { WebViewerInstance } from '@pdftron/webviewer'; import { AppStateService } from '../../../state/app-state.service'; import { environment } from '../../../../environments/environment'; diff --git a/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts b/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts index 9f9e37fd8..e592b5e7f 100644 --- a/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts +++ b/apps/red-ui/src/app/screens/base-screen/base-screen.component.ts @@ -2,8 +2,8 @@ import { Component } from '@angular/core'; import { UserService } from '../../user/user.service'; import { AppStateService } from '../../state/app-state.service'; import { LanguageService } from '../../i18n/language.service'; -import { PermissionsService } from '../../common/service/permissions.service'; -import { UserPreferenceService } from '../../common/service/user-preference.service'; +import { PermissionsService } from '../../utils/permissions.service'; +import { UserPreferenceService } from '../../utils/user-preference.service'; import { Router } from '@angular/router'; import { AppConfigService } from '../../app-config/app-config.service'; import { Title } from '@angular/platform-browser'; diff --git a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts index a56185a0d..bb07606c9 100644 --- a/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts +++ b/apps/red-ui/src/app/screens/file/annotation-actions/annotation-actions.component.ts @@ -1,9 +1,9 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { AnnotationWrapper } from '../model/annotation.wrapper'; import { AppStateService } from '../../../state/app-state.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { AnnotationPermissions } from '../model/annotation.permissions'; -import { AnnotationActionsService } from '../../../common/service/annotation-actions.service'; +import { AnnotationActionsService } from '../../../utils/annotation-actions.service'; import { Annotations, WebViewerInstance } from '@pdftron/webviewer'; @Component({ diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html index 94f66155c..60dd47c3d 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.html @@ -129,7 +129,11 @@
- +
-
-
- -
-
-
-
-
- -
-
- - -
-
- -
-
+ + -
-
- {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }} - -
- -
-
- {{ 'file-preview.no-annotations-for-page' | translate }} -
- -
-
-
- -
- -
-
- {{ annotation.typeLabel | translate }} -
-
- {{ annotation.descriptor | translate }}: {{ annotation.dictionary | humanize: false }} -
-
- : {{ annotation.content }} -
-
- -
-
- -
-
-
-
-
+
@@ -323,17 +237,11 @@
- - - - - {{ filter.key | humanize: false }} - - - - - - - - + + diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss index 965a39992..00691a2d4 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.scss @@ -29,166 +29,154 @@ padding: 0; width: 350px; min-width: 350px; + position: relative; - .right-title { + ::ng-deep .right-title { height: 70px; display: flex; border-bottom: 1px solid $separator; align-items: center; justify-content: space-between; padding: 0 24px; - - .close-icon { - height: 14px; - width: 14px; - cursor: pointer; - } - - > div { - display: flex; - - redaction-circle-button { - margin-left: 2px; - } - } } - .right-content { + ::ng-deep .right-content { height: calc(100% - 72px); box-sizing: border-box; display: flex; - - .quick-navigation, - .annotations { - overflow-y: scroll; - outline: none; - - &.active-panel { - background-color: #fafafa; - } - } - - .quick-navigation { - border-right: 1px solid $separator; - min-width: 61px; - overflow: hidden; - display: flex; - flex-direction: column; - - .jump { - min-height: 32px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - transition: background-color 0.25s; - - &:not(.disabled):hover { - background-color: $grey-6; - } - - mat-icon { - width: 16px; - height: 16px; - } - - &.disabled { - cursor: default; - - mat-icon { - opacity: 0.3; - } - } - } - - .pages { - @include no-scroll-bar(); - overflow: auto; - flex: 1; - } - } - - .page-separator { - border-bottom: 1px solid $separator; - height: 32px; - box-sizing: border-box; - padding: 0 10px; - display: flex; - align-items: center; - background-color: $grey-6; - } - - .annotations { - overflow: hidden; - width: 100%; - height: calc(100% - 32px); - - .annotation-wrapper { - display: flex; - border-bottom: 1px solid $separator; - - .active-marker { - min-width: 4px; - min-height: 100%; - } - - &.active { - .active-marker { - background-color: $primary; - } - } - - .annotation { - padding: 10px 21px 10px 6px; - font-size: 11px; - line-height: 14px; - cursor: pointer; - display: flex; - flex-direction: column; - - &.removed { - text-decoration: line-through; - color: $grey-7; - } - - .details { - display: flex; - position: relative; - } - - redaction-type-annotation-icon { - margin-top: 6px; - margin-right: 10px; - } - } - - &:hover { - background-color: #f9fafb; - - ::ng-deep .annotation-actions { - display: flex; - } - } - } - - &:hover { - overflow-y: auto; - @include scroll-bar; - } - - &.has-scrollbar:hover { - .annotation { - padding-right: 10px; - } - } - } } + // + // .quick-navigation, + // .annotations { + // overflow-y: scroll; + // outline: none; + // + // &.active-panel { + // background-color: #fafafa; + // } + // } + // + // .quick-navigation { + // border-right: 1px solid $separator; + // min-width: 61px; + // overflow: hidden; + // display: flex; + // flex-direction: column; + // + // .jump { + // min-height: 32px; + // display: flex; + // justify-content: center; + // align-items: center; + // cursor: pointer; + // transition: background-color 0.25s; + // + // &:not(.disabled):hover { + // background-color: $grey-6; + // } + // + // mat-icon { + // width: 16px; + // height: 16px; + // } + // + // &.disabled { + // cursor: default; + // + // mat-icon { + // opacity: 0.3; + // } + // } + // } + // + // .pages { + // @include no-scroll-bar(); + // overflow: auto; + // flex: 1; + // } + // } + // + // .page-separator { + // border-bottom: 1px solid $separator; + // height: 32px; + // box-sizing: border-box; + // padding: 0 10px; + // display: flex; + // align-items: center; + // background-color: $grey-6; + // } + // + // .annotations { + // overflow: hidden; + // width: 100%; + // height: calc(100% - 32px); + // + // .annotation-wrapper { + // display: flex; + // border-bottom: 1px solid $separator; + // + // .active-marker { + // min-width: 4px; + // min-height: 100%; + // } + // + // &.active { + // .active-marker { + // background-color: $primary; + // } + // } + // + // .annotation { + // padding: 10px 21px 10px 6px; + // font-size: 11px; + // line-height: 14px; + // cursor: pointer; + // display: flex; + // flex-direction: column; + // + // &.removed { + // text-decoration: line-through; + // color: $grey-7; + // } + // + // .details { + // display: flex; + // position: relative; + // } + // + // redaction-type-annotation-icon { + // margin-top: 6px; + // margin-right: 10px; + // } + // } + // + // &:hover { + // background-color: #f9fafb; + // + // ::ng-deep .annotation-actions { + // display: flex; + // } + // } + // } + // + // &:hover { + // overflow-y: auto; + // @include scroll-bar; + // } + // + // &.has-scrollbar:hover { + // .annotation { + // padding-right: 10px; + // } + // } + // } + //} } -.no-annotations { - padding: 24px; - text-align: center; -} +//.no-annotations { +// padding: 24px; +// text-align: center; +//} .assign-actions-wrapper { display: flex; diff --git a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts index 54111d07d..4eb298fc9 100644 --- a/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts +++ b/apps/red-ui/src/app/screens/file/file-preview-screen/file-preview-screen.component.ts @@ -15,24 +15,24 @@ import { AnnotationData, FileDataModel } from '../model/file-data.model'; import { FileActionService } from '../service/file-action.service'; import { AnnotationDrawService } from '../service/annotation-draw.service'; import { AnnotationProcessingService } from '../service/annotation-processing.service'; -import { FilterModel } from '../../../common/filter/model/filter.model'; +import { FilterModel } from '../../../components/filter/model/filter.model'; import { tap } from 'rxjs/operators'; import { NotificationService } from '../../../notification/notification.service'; import { TranslateService } from '@ngx-translate/core'; import { FileStatusWrapper } from '../model/file-status.wrapper'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { Subscription, timer } from 'rxjs'; -import { handleFilterDelta, processFilters } from '../../../common/filter/utils/filter-utils'; -import { UserPreferenceService } from '../../../common/service/user-preference.service'; +import { handleFilterDelta, processFilters } from '../../../components/filter/utils/filter-utils'; +import { UserPreferenceService } from '../../../utils/user-preference.service'; import { UserService } from '../../../user/user.service'; import { FormBuilder, FormGroup } from '@angular/forms'; import { FileManagementControllerService, StatusControllerService } from '@redaction/red-ui-http'; import { PdfViewerDataService } from '../service/pdf-viewer-data.service'; import { download } from '../../../utils/file-download-utils'; import { ViewMode } from '../model/view-mode'; +import { FileWorkloadComponent } from '../../../components/file-workload/file-workload.component'; -const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; -const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape', 'F', 'f']; +const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f']; @Component({ selector: 'redaction-file-preview-screen', @@ -40,6 +40,33 @@ const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Es styleUrls: ['./file-preview-screen.component.scss'] }) export class FilePreviewScreenComponent implements OnInit, OnDestroy { + public dialogRef: MatDialogRef; + public viewMode: ViewMode = 'STANDARD'; + public fullScreen = false; + public editingReviewer = false; + public reviewerForm: FormGroup; + public shouldDeselectAnnotationsOnPageChange = true; + public analysisProgressInSeconds = 0; + public analysisProgress: number; + public analysisInterval: number; + fileData: FileDataModel; + fileId: string; + annotationData: AnnotationData; + selectedAnnotations: AnnotationWrapper[]; + viewReady = false; + annotationFilters: FilterModel[]; + loadingMessage: string; + canPerformAnnotationActions: boolean; + filesAutoUpdateTimer: Subscription; + fileReanalysedSubscription: Subscription; + hideSkipped = false; + public viewDocumentInfo = false; + private projectId: string; + private _instance: WebViewerInstance; + + @ViewChild('fileWorkloadComponent') private _workloadComponent: FileWorkloadComponent; + @ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent; + constructor( public readonly appStateService: AppStateService, public readonly permissionsService: PermissionsService, @@ -72,7 +99,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { }); } - get annotations() { + get annotations(): AnnotationWrapper[] { return this.annotationData ? this.annotationData.visibleAnnotations : []; } @@ -92,60 +119,16 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { return this.fileData?.redactionChangeLog?.redactionLogEntry?.length > 0; } - get ignoreColor() { - return this.appStateService.getDictionaryColor('skipped'); - } - get displayData() { return this.fileData?.fileData; } - private projectId: string; - private _instance: WebViewerInstance; - private _dialogRef: MatDialogRef; - - public viewMode: ViewMode = 'STANDARD'; - public fullScreen = false; - public editingReviewer = false; - public reviewerForm: FormGroup; - public shouldDeselectAnnotationsOnPageChange = true; - - public analysisProgressInSeconds = 0; - public analysisProgress: number; - public analysisInterval: number; - - public quickScrollFirstEnabled = false; - public quickScrollLastEnabled = false; - - public displayedPages: number[] = []; - get indeterminateMode() { return ( this.analysisProgress > 100 || this.appStateService.activeFile.analysisDuration < 3 * 1000 // it takes longer than usual - switch to indeterminate ); // on less than 3 seconds show indeterminate } - @ViewChild(PdfViewerComponent) private _viewerComponent: PdfViewerComponent; - @ViewChild('annotationsElement') private _annotationsElement: ElementRef; - @ViewChild('quickNavigation') private _quickNavigationElement: ElementRef; - - fileData: FileDataModel; - fileId: string; - annotationData: AnnotationData; - - displayedAnnotations: { [key: number]: { annotations: AnnotationWrapper[] } } = {}; - selectedAnnotations: AnnotationWrapper[]; - pagesPanelActive = true; - viewReady = false; - annotationFilters: FilterModel[]; - - loadingMessage: string; - canPerformAnnotationActions: boolean; - filesAutoUpdateTimer: Subscription; - fileReanalysedSubscription: Subscription; - - hideSkipped = false; - updateViewMode() { const allAnnotations = this._instance.annotManager.getAnnotationsList(); @@ -181,15 +164,11 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { break; } - this._rebuildFilters(); + this.rebuildFilters(); this._updateCanPerformActions(); } - private _updateCanPerformActions() { - this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD'; - } - ngOnInit(): void { document.documentElement.addEventListener('fullscreenchange', (event) => { if (!document.fullscreenElement) { @@ -225,32 +204,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this.fileReanalysedSubscription.unsubscribe(); } - private _loadFileData(performUpdate: boolean = false) { - return this._fileDownloadService.loadActiveFileData().pipe( - tap((fileDataModel) => { - if (fileDataModel.fileStatus.isWorkable) { - if (performUpdate) { - this.fileData.redactionLog = fileDataModel.redactionLog; - this.fileData.redactionChangeLog = fileDataModel.redactionChangeLog; - this.fileData.fileStatus = fileDataModel.fileStatus; - this.fileData.manualRedactions = fileDataModel.manualRedactions; - this._rebuildFilters(true); - } else { - this.fileData = fileDataModel; - this._rebuildFilters(); - } - } else { - if (fileDataModel.fileStatus.isError) { - this._router.navigate(['/ui/projects/' + this.appStateService.activeProjectId]); - } else { - this.loadingMessage = 'file-preview.reanalyse-file'; - } - } - }) - ); - } - - private _rebuildFilters(deletePreviousAnnotations: boolean = false) { + public rebuildFilters(deletePreviousAnnotations: boolean = false) { const startTime = new Date().getTime(); if (deletePreviousAnnotations) { this.activeViewer.annotManager.deleteAnnotations(this.activeViewer.annotManager.getAnnotationsList(), { @@ -268,7 +222,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { ); const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations); this.annotationFilters = processFilters(this.annotationFilters, annotationFilters); - this.filtersChanged(this.annotationFilters); + this._workloadComponent.filtersChanged(this.annotationFilters); console.log('[REDACTION] Process time: ' + (new Date().getTime() - processStartTime) + 'ms'); console.log( '[REDACTION] Annotation Redraw and filter rebuild time: ' + @@ -281,27 +235,22 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { handleAnnotationSelected(annotationIds: string[]) { this.selectedAnnotations = annotationIds.map((annotationId) => this.annotations.find((annotationWrapper) => annotationWrapper.id === annotationId)); - this.scrollToSelectedAnnotation(); + this._workloadComponent.scrollToSelectedAnnotation(); this._changeDetectorRef.detectChanges(); } - annotationClicked(annotation: AnnotationWrapper) { - this.pagesPanelActive = false; - this.selectAnnotation(annotation); - } - selectAnnotation(annotation: AnnotationWrapper) { this._viewerComponent.selectAnnotation(annotation); } selectPage(pageNumber: number) { this._viewerComponent.navigateToPage(pageNumber); - this._scrollAnnotationsToPage(pageNumber, 'always'); + this._workloadComponent.scrollAnnotationsToPage(pageNumber, 'always'); } openManualAnnotationDialog($event: ManualRedactionEntryWrapper) { this.ngZone.run(() => { - this._dialogRef = this._dialogService.openManualAnnotationDialog($event, async (response: ManualAnnotationResponse) => { + this.dialogRef = this._dialogService.openManualAnnotationDialog($event, async (response: ManualAnnotationResponse) => { if (response?.annotationId) { const annotation = this.activeViewer.annotManager.getAnnotationById(response.manualRedactionEntryWrapper.rectId); this.activeViewer.annotManager.deleteAnnotation(annotation); @@ -315,81 +264,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { }); } - annotationIsSelected(annotation: AnnotationWrapper) { - return this.selectedAnnotations?.find((a) => a.id === annotation.id); - } - - private get firstSelectedAnnotation() { - return this.selectedAnnotations?.length ? this.selectedAnnotations[0] : null; - } - - private get lastSelectedAnnotation() { - return this.selectedAnnotations?.length ? this.selectedAnnotations[this.selectedAnnotations.length - 1] : null; - } - - @debounce() - private _scrollViews() { - this._scrollQuickNavigation(); - this._scrollAnnotations(); - } - - @debounce() - private scrollToSelectedAnnotation() { - if (!this.selectedAnnotations || this.selectedAnnotations.length === 0) { - return; - } - const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[annotation-id="${this.firstSelectedAnnotation.id}"].active`); - this._scrollToFirstElement(elements); - } - - private _scrollQuickNavigationToPage(page: number) { - const elements: any[] = this._quickNavigationElement.nativeElement.querySelectorAll(`#quick-nav-page-${page}`); - this._scrollToFirstElement(elements); - } - - private _scrollQuickNavigation() { - let quickNavPageIndex = this.displayedPages.findIndex((p) => p >= this.activeViewerPage); - if (quickNavPageIndex === -1 || this.displayedPages[quickNavPageIndex] !== this.activeViewerPage) { - quickNavPageIndex = Math.max(0, quickNavPageIndex - 1); - } - this._scrollQuickNavigationToPage(this.displayedPages[quickNavPageIndex]); - } - - public scrollQuickNavFirst() { - if (this.displayedPages.length > 0) { - this._scrollQuickNavigationToPage(this.displayedPages[0]); - } - } - - public scrollQuickNavLast() { - if (this.displayedPages.length > 0) { - this._scrollQuickNavigationToPage(this.displayedPages[this.displayedPages.length - 1]); - } - } - - private _scrollAnnotations() { - if (this.firstSelectedAnnotation?.pageNumber === this.activeViewerPage) { - return; - } - this._scrollAnnotationsToPage(this.activeViewerPage, 'always'); - } - - private _scrollAnnotationsToPage(page: number, mode: 'always' | 'if-needed' = 'if-needed') { - const elements: any[] = this._annotationsElement.nativeElement.querySelectorAll(`div[anotation-page-header="${page}"]`); - this._scrollToFirstElement(elements, mode); - } - - private _scrollToFirstElement(elements: HTMLElement[], mode: 'always' | 'if-needed' = 'if-needed') { - if (elements.length > 0) { - scrollIntoView(elements[0], { - behavior: 'smooth', - scrollMode: mode, - block: 'start', - inline: 'start' - }); - } - } - public toggleFullScreen() { this.fullScreen = !this.fullScreen; if (this.fullScreen) { @@ -401,7 +275,7 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { @HostListener('window:keyup', ['$event']) handleKeyEvent($event: KeyboardEvent) { - if (!ALL_HOTKEY_ARRAY.includes($event.key) || this._dialogRef?.getState() === MatDialogState.OPEN) { + if (!ALL_HOTKEY_ARRAY.includes($event.key) || this.dialogRef?.getState() === MatDialogState.OPEN) { return; } @@ -419,145 +293,9 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { return; } - if ($event.key === 'ArrowLeft') { - this.pagesPanelActive = true; - } - if ($event.key === 'ArrowRight') { - this.pagesPanelActive = false; - // if we activated annotationsPanel - select first annotation from this page in case there is no - // selected annotation on this page - if (!this.pagesPanelActive) { - this._selectFirstAnnotationOnCurrentPageIfNecessary(); - } - } - - if ($event.key === 'ArrowLeft' || $event.key === 'ArrowRight') { - return; - } - - if (!this.pagesPanelActive) { - this._navigateAnnotations($event); - } else { - this._navigatePages($event); - } - this._changeDetectorRef.detectChanges(); } - private _selectFirstAnnotationOnCurrentPageIfNecessary() { - if ( - (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) && - this.displayedPages.indexOf(this.activeViewerPage) >= 0 - ) { - this.selectAnnotation(this.displayedAnnotations[this.activeViewerPage].annotations[0]); - } - } - - private _navigateAnnotations($event: KeyboardEvent) { - if (!this.firstSelectedAnnotation || this.activeViewerPage !== this.firstSelectedAnnotation.pageNumber) { - 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.shouldDeselectAnnotationsOnPageChange = false; - this.selectAnnotation(this.displayedAnnotations[nextPage].annotations[0]); - } else { - const prevPage = this._prevPageWithAnnotations(); - this.shouldDeselectAnnotationsOnPageChange = false; - const prevPageAnnotations = this.displayedAnnotations[prevPage].annotations; - this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]); - } - } - } else { - const page = this.firstSelectedAnnotation.pageNumber; - const pageIdx = this.displayedPages.indexOf(page); - const annotationsOnPage = this.displayedAnnotations[page].annotations; - const idx = annotationsOnPage.findIndex((a) => a.id === this.firstSelectedAnnotation.id); - - if ($event.key === 'ArrowDown') { - if (idx + 1 !== annotationsOnPage.length) { - // If not last item in page - this.selectAnnotation(annotationsOnPage[idx + 1]); - } else if (pageIdx + 1 < this.displayedPages.length) { - // If not last page - const nextPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx + 1]].annotations; - this.shouldDeselectAnnotationsOnPageChange = false; - this.selectAnnotation(nextPageAnnotations[0]); - } - } else { - if (idx !== 0) { - // If not first item in page - this.selectAnnotation(annotationsOnPage[idx - 1]); - } else if (pageIdx) { - // If not first page - const prevPageAnnotations = this.displayedAnnotations[this.displayedPages[pageIdx - 1]].annotations; - this.shouldDeselectAnnotationsOnPageChange = false; - this.selectAnnotation(prevPageAnnotations[prevPageAnnotations.length - 1]); - } - } - } - } - - 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: any) { if (typeof $event === 'number') { this._scrollViews(); @@ -588,74 +326,10 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { } } - filtersChanged(filters: FilterModel[]) { - this.displayedAnnotations = this._annotationProcessingService.filterAndGroupAnnotations(this.annotations, filters); - this.displayedPages = Object.keys(this.displayedAnnotations).map((key) => Number(key)); - this.computeQuickNavButtonsState(); - this._changeDetectorRef.markForCheck(); - } - - preventKeyDefault($event: KeyboardEvent) { - if (COMMAND_KEY_ARRAY.includes($event.key)) { - $event.preventDefault(); - } - } - - public computeQuickNavButtonsState() { - const element: HTMLElement = this._quickNavigationElement.nativeElement.querySelector(`#pages`); - const { scrollTop, scrollHeight, clientHeight } = element; - this.quickScrollFirstEnabled = scrollTop !== 0; - this.quickScrollLastEnabled = scrollHeight !== scrollTop + clientHeight; - } - - private _cleanupAndRedrawManualAnnotations() { - this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => { - this.fileData.manualRedactions = manualRedactions; - this._rebuildFilters(); - this._annotationDrawService.drawAnnotations(this._instance, this.annotationData.allAnnotations, this.hideSkipped); - }); - } - - private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) { - const currentPageAnnotations = this.annotations.filter((a) => a.pageNumber === page); - const currentPageAnnotationIds = currentPageAnnotations.map((a) => a.id); - this.fileData.fileStatus = await this.appStateService.reloadActiveFile(); - - this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => { - this.fileData.manualRedactions = manualRedactions; - this._rebuildFilters(); - if (this.viewMode === 'STANDARD') { - currentPageAnnotationIds.forEach((id) => { - this._findAndDeleteAnnotation(id); - }); - const newPageAnnotations = this.annotations.filter((item) => item.pageNumber === page); - this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations); - this._annotationDrawService.drawAnnotations(this._instance, newPageAnnotations, this.hideSkipped); - } - }); - } - - private _handleDeltaAnnotationFilters(currentPageAnnotations: AnnotationWrapper[], newPageAnnotations: AnnotationWrapper[]) { - const hasAnyFilterSet = this.annotationFilters.find((f) => f.checked || f.indeterminate); - if (hasAnyFilterSet) { - const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations); - const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations); - handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.annotationFilters); - this.filtersChanged(this.annotationFilters); - } - } - async annotationsChangedByReviewAction(annotation: AnnotationWrapper) { await this._cleanupAndRedrawManualAnnotationsForEntirePage(annotation.pageNumber); } - private _findAndDeleteAnnotation(id: string) { - const viewerAnnotation = this.activeViewer.annotManager.getAnnotationById(id); - if (viewerAnnotation) { - this.activeViewer.annotManager.deleteAnnotation(viewerAnnotation, { imported: true, force: true }); - } - } - async fileActionPerformed(action: string) { switch (action) { case 'delete': @@ -670,36 +344,16 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this._updateCanPerformActions(); await this.appStateService.reloadActiveProjectFiles(); return; + + case 'view-document-info': + this.viewDocumentInfo = !this.viewDocumentInfo; + return; + default: this._updateCanPerformActions(); } } - private _startAnalysisTimer() { - this._stopAnalysisTimer(); - - if (this.appStateService.activeFile.analysisDuration > 0) { - this.analysisProgress = 0; - this.analysisProgressInSeconds = 0; - - this.analysisInterval = setInterval(() => { - this.analysisProgressInSeconds += 1; - this.analysisProgress = (this.analysisProgressInSeconds * 100) / (this.appStateService.activeFile.analysisDuration / 1000); - }, 1000); - } else { - this.analysisInterval = 0; - this.analysisProgress = 0; - this.analysisProgressInSeconds = 0; - } - } - - private _stopAnalysisTimer() { - if (this.analysisInterval) { - clearInterval(this.analysisInterval); - this.analysisInterval = 0; - } - } - public async assignToMe() { await this._fileActionService.assignToMe(this.fileData.fileStatus, async () => { await this.appStateService.reloadActiveFile(); @@ -725,21 +379,6 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { this.reviewerForm.setValue({ reviewer: this.appStateService.activeFile.currentReviewer }); } - pageSelectedByClick($event: number) { - this.pagesPanelActive = true; - this.selectPage($event); - } - - /* Get the documentElement () to display the page in fullscreen */ - - /* View in fullscreen */ - private _openFullScreen() { - const documentElement = document.documentElement; - if (documentElement.requestFullscreen) { - documentElement.requestFullscreen(); - } - } - /* Close fullscreen */ closeFullScreen() { if (document.exitFullscreen) { @@ -780,8 +419,118 @@ export class FilePreviewScreenComponent implements OnInit, OnDestroy { window.open(`/html-debug/${this.projectId}/${this.fileId}`, '_blank'); } - logAnnotation(annotation: AnnotationWrapper) { - console.log(annotation); + private _updateCanPerformActions() { + this.canPerformAnnotationActions = this.permissionsService.canPerformAnnotationActions() && this.viewMode === 'STANDARD'; + } + + private _loadFileData(performUpdate: boolean = false) { + return this._fileDownloadService.loadActiveFileData().pipe( + tap((fileDataModel) => { + if (fileDataModel.fileStatus.isWorkable) { + if (performUpdate) { + this.fileData.redactionLog = fileDataModel.redactionLog; + this.fileData.redactionChangeLog = fileDataModel.redactionChangeLog; + this.fileData.fileStatus = fileDataModel.fileStatus; + this.fileData.manualRedactions = fileDataModel.manualRedactions; + this.rebuildFilters(true); + } else { + this.fileData = fileDataModel; + this.rebuildFilters(); + } + } else { + if (fileDataModel.fileStatus.isError) { + this._router.navigate(['/ui/projects/' + this.appStateService.activeProjectId]); + } else { + this.loadingMessage = 'file-preview.reanalyse-file'; + } + } + }) + ); + } + + @debounce() + private _scrollViews() { + this._workloadComponent.scrollQuickNavigation(); + this._workloadComponent.scrollAnnotations(); + } + + /* Get the documentElement () to display the page in fullscreen */ + + private _cleanupAndRedrawManualAnnotations() { + this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => { + this.fileData.manualRedactions = manualRedactions; + this.rebuildFilters(); + this._annotationDrawService.drawAnnotations(this._instance, this.annotationData.allAnnotations, this.hideSkipped); + }); + } + + private async _cleanupAndRedrawManualAnnotationsForEntirePage(page: number) { + const currentPageAnnotations = this.annotations.filter((a) => a.pageNumber === page); + const currentPageAnnotationIds = currentPageAnnotations.map((a) => a.id); + this.fileData.fileStatus = await this.appStateService.reloadActiveFile(); + + this._fileDownloadService.loadActiveFileManualAnnotations().subscribe((manualRedactions) => { + this.fileData.manualRedactions = manualRedactions; + this.rebuildFilters(); + if (this.viewMode === 'STANDARD') { + currentPageAnnotationIds.forEach((id) => { + this._findAndDeleteAnnotation(id); + }); + const newPageAnnotations = this.annotations.filter((item) => item.pageNumber === page); + this._handleDeltaAnnotationFilters(currentPageAnnotations, newPageAnnotations); + this._annotationDrawService.drawAnnotations(this._instance, newPageAnnotations, this.hideSkipped); + } + }); + } + + private _handleDeltaAnnotationFilters(currentPageAnnotations: AnnotationWrapper[], newPageAnnotations: AnnotationWrapper[]) { + const hasAnyFilterSet = this.annotationFilters.find((f) => f.checked || f.indeterminate); + if (hasAnyFilterSet) { + const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations); + const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations); + handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.annotationFilters); + this._workloadComponent.filtersChanged(this.annotationFilters); + } + } + + private _findAndDeleteAnnotation(id: string) { + const viewerAnnotation = this.activeViewer.annotManager.getAnnotationById(id); + if (viewerAnnotation) { + this.activeViewer.annotManager.deleteAnnotation(viewerAnnotation, { imported: true, force: true }); + } + } + + private _startAnalysisTimer() { + this._stopAnalysisTimer(); + + if (this.appStateService.activeFile.analysisDuration > 0) { + this.analysisProgress = 0; + this.analysisProgressInSeconds = 0; + + this.analysisInterval = setInterval(() => { + this.analysisProgressInSeconds += 1; + this.analysisProgress = (this.analysisProgressInSeconds * 100) / (this.appStateService.activeFile.analysisDuration / 1000); + }, 1000); + } else { + this.analysisInterval = 0; + this.analysisProgress = 0; + this.analysisProgressInSeconds = 0; + } + } + + private _stopAnalysisTimer() { + if (this.analysisInterval) { + clearInterval(this.analysisInterval); + this.analysisInterval = 0; + } + } + + /* View in fullscreen */ + private _openFullScreen() { + const documentElement = document.documentElement; + if (documentElement.requestFullscreen) { + documentElement.requestFullscreen(); + } } private _handleIgnoreAnnotationsDrawing() { diff --git a/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts b/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts index 0c827b641..234879708 100644 --- a/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts +++ b/apps/red-ui/src/app/screens/file/model/file-status.wrapper.ts @@ -1,5 +1,5 @@ import { FileStatus } from '@redaction/red-ui-http'; -import { StatusSorter } from '../../../common/sorters/status-sorter'; +import { StatusSorter } from '../../../utils/sorters/status-sorter'; export class FileStatusWrapper { constructor(public fileStatus: FileStatus, public reviewerName: string) {} diff --git a/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts b/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts index 29d7fbe39..4045a09c1 100644 --- a/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts +++ b/apps/red-ui/src/app/screens/file/page-indicator/page-indicator.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core'; import { ViewedPages, ViewedPagesControllerService } from '@redaction/red-ui-http'; import { AppStateService } from '../../../state/app-state.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { AppConfigKey, AppConfigService } from '../../../app-config/app-config.service'; import { Subscription } from 'rxjs'; diff --git a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts index 1e23aa9ee..171014c2d 100644 --- a/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts +++ b/apps/red-ui/src/app/screens/file/pdf-viewer/pdf-viewer.component.ts @@ -24,8 +24,8 @@ import { FileStatusWrapper } from '../model/file-status.wrapper'; import { KeycloakService } from 'keycloak-angular'; import { environment } from '../../../../environments/environment'; import { AnnotationDrawService } from '../service/annotation-draw.service'; -import { AnnotationActionsService } from '../../../common/service/annotation-actions.service'; -import { UserPreferenceService } from '../../../common/service/user-preference.service'; +import { AnnotationActionsService } from '../../../utils/annotation-actions.service'; +import { UserPreferenceService } from '../../../utils/user-preference.service'; import { translateQuads } from '../../../utils/pdf-coordinates'; import Tool = Tools.Tool; diff --git a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts index 64c39f020..b3d23f7c4 100644 --- a/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts +++ b/apps/red-ui/src/app/screens/file/service/annotation-draw.service.ts @@ -4,7 +4,7 @@ import { Rectangle, RedactionLogControllerService, SectionGrid, SectionRectangle import { hexToRgb } from '../../../utils/functions'; import { AppStateService } from '../../../state/app-state.service'; import { AnnotationWrapper } from '../model/annotation.wrapper'; -import { UserPreferenceService } from '../../../common/service/user-preference.service'; +import { UserPreferenceService } from '../../../utils/user-preference.service'; @Injectable({ providedIn: 'root' diff --git a/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts b/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts index f5943932f..379774b64 100644 --- a/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts +++ b/apps/red-ui/src/app/screens/file/service/annotation-processing.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; import { AppStateService } from '../../../state/app-state.service'; import { AnnotationWrapper } from '../model/annotation.wrapper'; -import { FilterModel } from '../../../common/filter/model/filter.model'; -import { handleCheckedValue } from '../../../common/filter/utils/filter-utils'; -import { SuperTypeSorter } from '../../../common/sorters/super-type-sorter'; +import { FilterModel } from '../../../components/filter/model/filter.model'; +import { handleCheckedValue } from '../../../components/filter/utils/filter-utils'; +import { SuperTypeSorter } from '../../../utils/sorters/super-type-sorter'; import { getFirstRelevantTextPart } from '../../../utils/functions'; @Injectable({ diff --git a/apps/red-ui/src/app/screens/file/service/file-action.service.ts b/apps/red-ui/src/app/screens/file/service/file-action.service.ts index 8b46665ca..bd2e063f3 100644 --- a/apps/red-ui/src/app/screens/file/service/file-action.service.ts +++ b/apps/red-ui/src/app/screens/file/service/file-action.service.ts @@ -4,7 +4,7 @@ import { AppStateService } from '../../../state/app-state.service'; import { UserService } from '../../../user/user.service'; import { FileStatus, ReanalysisControllerService, StatusControllerService } from '@redaction/red-ui-http'; import { FileStatusWrapper } from '../model/file-status.wrapper'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { isArray } from 'rxjs/internal-compatibility'; @Injectable({ diff --git a/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts b/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts index b8237d1b0..80c5a496e 100644 --- a/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts +++ b/apps/red-ui/src/app/screens/file/service/manual-annotation.service.ts @@ -6,7 +6,7 @@ import { NotificationService, NotificationType } from '../../../notification/not import { TranslateService } from '@ngx-translate/core'; import { tap } from 'rxjs/operators'; import { UserService } from '../../../user/user.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; @Injectable({ providedIn: 'root' diff --git a/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts b/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts index a97e95c0e..9d0378de4 100644 --- a/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts +++ b/apps/red-ui/src/app/screens/file/service/pdf-viewer-data.service.ts @@ -10,7 +10,7 @@ import { import { FileType } from '../model/file-type'; import { FileDataModel } from '../model/file-data.model'; import { AppStateService } from '../../../state/app-state.service'; -import { PermissionsService } from '../../../common/service/permissions.service'; +import { PermissionsService } from '../../../utils/permissions.service'; import { FileStatusWrapper } from '../model/file-status.wrapper'; @Injectable({ diff --git a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts index d7736785c..f2ec6af0a 100644 --- a/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-listing-screen/project-listing-screen.component.ts @@ -5,7 +5,7 @@ import { UserService } from '../../user/user.service'; import { DoughnutChartConfig } from '../../components/simple-doughnut-chart/simple-doughnut-chart.component'; import { groupBy, humanize } from '../../utils/functions'; import { DialogService } from '../../dialogs/dialog.service'; -import { FilterModel } from '../../common/filter/model/filter.model'; +import { FilterModel } from '../../components/filter/model/filter.model'; import { annotationFilterChecker, getFilteredEntities, @@ -13,20 +13,20 @@ import { projectMemberChecker, projectStatusChecker, ruleSetChecker -} from '../../common/filter/utils/filter-utils'; +} from '../../components/filter/utils/filter-utils'; import { TranslateService } from '@ngx-translate/core'; import { SortingOption, SortingService } from '../../utils/sorting.service'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; import { ProjectWrapper } from '../../state/model/project.wrapper'; import { Subscription, timer } from 'rxjs'; import { tap } from 'rxjs/operators'; import { TranslateChartService } from '../../utils/translate-chart.service'; -import { RedactionFilterSorter } from '../../common/sorters/redaction-filter-sorter'; -import { StatusSorter } from '../../common/sorters/status-sorter'; +import { RedactionFilterSorter } from '../../utils/sorters/redaction-filter-sorter'; +import { StatusSorter } from '../../utils/sorters/status-sorter'; import { Router } from '@angular/router'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounce } from '../../utils/debounce'; -import { FilterComponent } from '../../common/filter/filter.component'; +import { FilterComponent } from '../../components/filter/filter.component'; @Component({ selector: 'redaction-project-listing-screen', diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html index 8193c9626..3a1886295 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.html @@ -112,7 +112,10 @@ {{ 'project-overview.table-header.title' | translate: { length: displayedFiles.length || 0 } }} - +
diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss index 34b886b01..4f7e8b28f 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.scss @@ -85,6 +85,10 @@ cdk-virtual-scroll-viewport { &.has-scrollbar:hover { padding-right: 13px; } + + redaction-project-details { + width: 100%; + } } .reanalyse-link { diff --git a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts index c30ec9fa2..426be5039 100644 --- a/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts +++ b/apps/red-ui/src/app/screens/project-overview-screen/project-overview-screen.component.ts @@ -9,23 +9,23 @@ import { StatusOverlayService } from '../../upload-download/status-overlay.servi import { DialogService } from '../../dialogs/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { FileActionService } from '../file/service/file-action.service'; -import { FilterModel } from '../../common/filter/model/filter.model'; +import { FilterModel } from '../../components/filter/model/filter.model'; import * as moment from 'moment'; -import { ProjectDetailsComponent } from './project-details/project-details.component'; +import { ProjectDetailsComponent } from '../../components/project-details/project-details.component'; import { FileStatusWrapper } from '../file/model/file-status.wrapper'; -import { annotationFilterChecker, getFilteredEntities, keyChecker, processFilters } from '../../common/filter/utils/filter-utils'; +import { annotationFilterChecker, getFilteredEntities, keyChecker, processFilters } from '../../components/filter/utils/filter-utils'; import { SortingOption, SortingService } from '../../utils/sorting.service'; -import { PermissionsService } from '../../common/service/permissions.service'; +import { PermissionsService } from '../../utils/permissions.service'; import { UserService } from '../../user/user.service'; import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http'; import { Subscription, timer } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { RedactionFilterSorter } from '../../common/sorters/redaction-filter-sorter'; -import { StatusSorter } from '../../common/sorters/status-sorter'; +import { RedactionFilterSorter } from '../../utils/sorters/redaction-filter-sorter'; +import { StatusSorter } from '../../utils/sorters/status-sorter'; import { FormBuilder, FormGroup } from '@angular/forms'; import { debounce } from '../../utils/debounce'; import { convertFiles, handleFileDrop } from '../../utils/file-drop-utils'; -import { FilterComponent } from '../../common/filter/filter.component'; +import { FilterComponent } from '../../components/filter/filter.component'; @Component({ selector: 'redaction-project-overview-screen', diff --git a/apps/red-ui/src/app/state/app-state.service.ts b/apps/red-ui/src/app/state/app-state.service.ts index 424f94bb3..b90fc0ba6 100644 --- a/apps/red-ui/src/app/state/app-state.service.ts +++ b/apps/red-ui/src/app/state/app-state.service.ts @@ -1,6 +1,8 @@ import { EventEmitter, Injectable } from '@angular/core'; import { DictionaryControllerService, + FileAttributeConfig, + FileAttributesControllerService, FileManagementControllerService, FileStatus, Project, @@ -29,6 +31,7 @@ export interface AppState { activeProjectId: string; activeFileId: string; activeRuleSetId: string; + activeFileAttributesConfig: FileAttributeConfig[]; activeDictionaryType: string; totalAnalysedPages?: number; totalDocuments?: number; @@ -56,7 +59,8 @@ export class AppStateService { private readonly _dictionaryControllerService: DictionaryControllerService, private readonly _ruleSetControllerService: RuleSetControllerService, private readonly _statusControllerService: StatusControllerService, - private readonly _versionsControllerService: VersionsControllerService + private readonly _versionsControllerService: VersionsControllerService, + private readonly _fileAttributesService: FileAttributesControllerService ) { this._appState = { projects: [], @@ -64,6 +68,7 @@ export class AppStateService { activeProjectId: null, activeFileId: null, activeRuleSetId: null, + activeFileAttributesConfig: [], activeDictionaryType: null, versions: {} }; @@ -141,6 +146,10 @@ export class AppStateService { return this.ruleSets.find((rs) => rs.ruleSetId === id); } + public get fileAttributesConfig(): FileAttributeConfig[] { + return this._appState.activeFileAttributesConfig; + } + get activeDictionaryType(): string { return this._appState.activeDictionaryType; } @@ -306,8 +315,16 @@ export class AppStateService { this._appState.activeProjectId = projectId; if (!this.activeProject) { this._appState.activeProjectId = null; + this._appState.activeFileAttributesConfig = []; this._router.navigate(['/ui/projects']); + return; } + this._fileAttributesService + .getFileAttributesConfiguration(this.getProjectById(projectId).ruleSetId) + .toPromise() + .then((data) => { + this._appState.activeFileAttributesConfig = data.fileAttributeConfigs; + }); } activateFile(projectId: string, fileId: string) { diff --git a/apps/red-ui/src/app/upload-download/file-download.service.ts b/apps/red-ui/src/app/upload-download/file-download.service.ts index 70956f07f..6038577ad 100644 --- a/apps/red-ui/src/app/upload-download/file-download.service.ts +++ b/apps/red-ui/src/app/upload-download/file-download.service.ts @@ -9,7 +9,7 @@ import { FileStatusWrapper } from '../screens/file/model/file-status.wrapper'; import { mergeMap, tap } from 'rxjs/operators'; import { DownloadStatusWrapper } from './model/download-status.wrapper'; import { AppStateService } from '../state/app-state.service'; -import { PermissionsService } from '../common/service/permissions.service'; +import { PermissionsService } from '../utils/permissions.service'; import { KeycloakService } from 'keycloak-angular'; @Injectable({ diff --git a/apps/red-ui/src/app/common/service/annotation-actions.service.ts b/apps/red-ui/src/app/utils/annotation-actions.service.ts similarity index 96% rename from apps/red-ui/src/app/common/service/annotation-actions.service.ts rename to apps/red-ui/src/app/utils/annotation-actions.service.ts index 20a0f00a3..1abfc4eda 100644 --- a/apps/red-ui/src/app/common/service/annotation-actions.service.ts +++ b/apps/red-ui/src/app/utils/annotation-actions.service.ts @@ -1,13 +1,13 @@ import { EventEmitter, Injectable, NgZone } from '@angular/core'; import { PermissionsService } from './permissions.service'; -import { ManualAnnotationService } from '../../screens/file/service/manual-annotation.service'; -import { DialogService } from '../../dialogs/dialog.service'; -import { AnnotationWrapper } from '../../screens/file/model/annotation.wrapper'; +import { ManualAnnotationService } from '../screens/file/service/manual-annotation.service'; +import { DialogService } from '../dialogs/dialog.service'; +import { AnnotationWrapper } from '../screens/file/model/annotation.wrapper'; import { Observable } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { AddRedactionRequest } from '@redaction/red-ui-http'; -import { getFirstRelevantTextPart } from '../../utils/functions'; -import { AnnotationPermissions } from '../../screens/file/model/annotation.permissions'; +import { getFirstRelevantTextPart } from './functions'; +import { AnnotationPermissions } from '../screens/file/model/annotation.permissions'; @Injectable({ providedIn: 'root' diff --git a/apps/red-ui/src/app/common/service/permissions.service.ts b/apps/red-ui/src/app/utils/permissions.service.ts similarity index 96% rename from apps/red-ui/src/app/common/service/permissions.service.ts rename to apps/red-ui/src/app/utils/permissions.service.ts index e75007ba8..4bcc25cb9 100644 --- a/apps/red-ui/src/app/common/service/permissions.service.ts +++ b/apps/red-ui/src/app/utils/permissions.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; -import { AppStateService } from '../../state/app-state.service'; -import { UserService, UserWrapper } from '../../user/user.service'; -import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper'; +import { AppStateService } from '../state/app-state.service'; +import { UserService, UserWrapper } from '../user/user.service'; +import { FileStatusWrapper } from '../screens/file/model/file-status.wrapper'; import { User } from '@redaction/red-ui-http'; -import { ProjectWrapper } from '../../state/model/project.wrapper'; +import { ProjectWrapper } from '../state/model/project.wrapper'; @Injectable({ providedIn: 'root' diff --git a/apps/red-ui/src/app/common/sorters/redaction-filter-sorter.ts b/apps/red-ui/src/app/utils/sorters/redaction-filter-sorter.ts similarity index 100% rename from apps/red-ui/src/app/common/sorters/redaction-filter-sorter.ts rename to apps/red-ui/src/app/utils/sorters/redaction-filter-sorter.ts diff --git a/apps/red-ui/src/app/common/sorters/status-sorter.ts b/apps/red-ui/src/app/utils/sorters/status-sorter.ts similarity index 100% rename from apps/red-ui/src/app/common/sorters/status-sorter.ts rename to apps/red-ui/src/app/utils/sorters/status-sorter.ts diff --git a/apps/red-ui/src/app/common/sorters/super-type-sorter.ts b/apps/red-ui/src/app/utils/sorters/super-type-sorter.ts similarity index 100% rename from apps/red-ui/src/app/common/sorters/super-type-sorter.ts rename to apps/red-ui/src/app/utils/sorters/super-type-sorter.ts diff --git a/apps/red-ui/src/app/utils/sorting.service.ts b/apps/red-ui/src/app/utils/sorting.service.ts index 45ffa97a8..b73629bdb 100644 --- a/apps/red-ui/src/app/utils/sorting.service.ts +++ b/apps/red-ui/src/app/utils/sorting.service.ts @@ -5,7 +5,7 @@ export class SortingOption { column: string; } -type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing' | 'default-colors'; +type Screen = 'project-listing' | 'project-overview' | 'dictionary-listing' | 'rule-sets-listing' | 'default-colors' | 'file-attributes-listing'; @Injectable({ providedIn: 'root' @@ -16,7 +16,8 @@ export class SortingService { 'project-overview': { column: 'filename', order: 'asc' }, 'dictionary-listing': { column: 'label', order: 'asc' }, 'rule-sets-listing': { column: 'name', order: 'asc' }, - 'default-colors': { column: 'key', order: 'asc' } + 'default-colors': { column: 'key', order: 'asc' }, + 'file-attributes-listing': { column: 'name', order: 'asc' } }; constructor() {} diff --git a/apps/red-ui/src/app/common/service/user-preference.service.ts b/apps/red-ui/src/app/utils/user-preference.service.ts similarity index 100% rename from apps/red-ui/src/app/common/service/user-preference.service.ts rename to apps/red-ui/src/app/utils/user-preference.service.ts diff --git a/apps/red-ui/src/assets/i18n/en.json b/apps/red-ui/src/assets/i18n/en.json index 2f2404fe9..f2fd499d4 100644 --- a/apps/red-ui/src/assets/i18n/en.json +++ b/apps/red-ui/src/assets/i18n/en.json @@ -291,6 +291,18 @@ "view-toggle": "Redacted View", "tabs": { "quick-navigation": "Quick Navigation", + "document-info": { + "label": "Document Info", + "close": "Close Document Info", + "edit": "Edit Document Info", + "details": { + "project": "in {{projectName}}", + "pages": "{{pages}} pages", + "revised-pages": "{{pages}} revised pages", + "created-on": "Created on: {{date}}", + "due": "Due: {{date}}" + } + }, "annotations": { "label": "Workload" } @@ -303,6 +315,7 @@ "assign-me": "Assign to me", "last-reviewer": "Last Reviewed by:", "fullscreen": "Full Screen (F)", + "document-info": "Your Document Info lives here. This includes metadata required on each document.", "new-tab-ssr": "Open Document in Server Side Rendering Mode", "html-debug": "Open Document HTML Debug", "download-original-file": "Download Original File", @@ -583,6 +596,21 @@ "question": "Do you wish to proceed?" } }, + "add-edit-file-attribute": { + "title": { + "edit": "Edit {{name}} File Attribute", + "new": "Add New File Attribute" + }, + "form": { + "name": "Attribute Name", + "name-placeholder": "Enter Name", + "column-header": "CSV Column Header", + "column-header-placeholder": "Enter CSV Column Header", + "read-only": "Make Read-Only", + "visible": "Visible in Document Info" + }, + "save": "Save Attribute" + }, "add-edit-dictionary": { "title": { "edit": "Edit {{name}} Dictionary", @@ -694,6 +722,37 @@ "modified-on": "Modified on" } }, + "file-attributes-listing": { + "search": "Search by attribute name...", + "add-new": "New Attribute", + "table-header": { + "title": "{{length}} file attributes" + }, + "table-col-names": { + "name": "Name", + "created-by": "Created by", + "read-only": "Read-Only" + }, + "no-data": "No file attributes.", + "read-only": "Read-only", + "action": { + "edit": "Edit attribute", + "delete": "Delete attribute" + } + }, + "confirm-delete-file-attribute": { + "title": "Delete {{name}}", + "warning": "Warning: this cannot be undone!", + "delete": "Delete Attribute", + "cancel": "Keep Attribute", + "checkbox-1": "All documents it is used on will be impacted", + "checkbox-2": "All inputted details on the documents will be lost" + }, + "document-info": { + "title": "Introduce File Attributes", + "save": "Save Document Info", + "save-approval": "Save and Send for Approval" + }, "user-listing": { "table-header": { "title": "{{length}} users" @@ -748,6 +807,7 @@ }, "rule-editor": "Rule Editor", "watermark": "Watermark", + "file-attributes": "File Attributes", "pending-changes-guard": "WARNING: You have unsaved changes. Press Cancel to go back and save these changes, or OK to lose these changes.", "reset-filters": "Reset Filters", "overwrite-files-dialog": { diff --git a/apps/red-ui/src/assets/icons/general/read-only.svg b/apps/red-ui/src/assets/icons/general/read-only.svg new file mode 100644 index 000000000..ff827078c --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/read-only.svg @@ -0,0 +1,19 @@ + + + 9C81EE87-2992-4087-907E-26A83F796466 + + + + + + + + + + + + + + + + diff --git a/apps/red-ui/src/assets/icons/general/status-info.svg b/apps/red-ui/src/assets/icons/general/status-info.svg new file mode 100644 index 000000000..7f75f904d --- /dev/null +++ b/apps/red-ui/src/assets/icons/general/status-info.svg @@ -0,0 +1,27 @@ + + + F8E3057D-BE44-469F-9E28-9A04A0B36DF0 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/red-ui/src/assets/styles/red-components.scss b/apps/red-ui/src/assets/styles/red-components.scss index c61dbc680..b5f038b3c 100644 --- a/apps/red-ui/src/assets/styles/red-components.scss +++ b/apps/red-ui/src/assets/styles/red-components.scss @@ -74,7 +74,7 @@ mat-icon { width: 10px; - margin-right: 4px; + margin-right: 6px; } &:not(:last-child) { diff --git a/apps/red-ui/src/assets/styles/red-input.scss b/apps/red-ui/src/assets/styles/red-input.scss index f01563375..8c82292de 100644 --- a/apps/red-ui/src/assets/styles/red-input.scss +++ b/apps/red-ui/src/assets/styles/red-input.scss @@ -146,7 +146,7 @@ form { } } - label { + &:not(.ignore-label-styles) label { opacity: 0.7; font-size: 11px; letter-spacing: 0; diff --git a/apps/red-ui/src/assets/styles/red-loading.scss b/apps/red-ui/src/assets/styles/red-loading.scss index fb32d9e04..ac53e50bc 100644 --- a/apps/red-ui/src/assets/styles/red-loading.scss +++ b/apps/red-ui/src/assets/styles/red-loading.scss @@ -1,5 +1,5 @@ .loading:after { - content: ' .'; + content: '.'; animation: dots 1s steps(5, end) infinite; } diff --git a/apps/red-ui/src/assets/styles/red-tables.scss b/apps/red-ui/src/assets/styles/red-tables.scss index c5f77f376..34da2ed36 100644 --- a/apps/red-ui/src/assets/styles/red-tables.scss +++ b/apps/red-ui/src/assets/styles/red-tables.scss @@ -80,6 +80,10 @@ cdk-virtual-scroll-viewport { width: 14px; } + redaction-circle-button:not(:last-child) { + margin-right: 2px; + } + &.active { display: flex; // compensate for scroll diff --git a/apps/red-ui/src/assets/styles/red-toggle.scss b/apps/red-ui/src/assets/styles/red-toggle.scss index 812e1d7df..65875cbc2 100644 --- a/apps/red-ui/src/assets/styles/red-toggle.scss +++ b/apps/red-ui/src/assets/styles/red-toggle.scss @@ -1,8 +1,11 @@ -mat-slide-toggle { +@import 'red-variables'; + +.mat-slide-toggle { .mat-slide-toggle-bar { height: 16px !important; width: 30px !important; border-radius: 16px !important; + background-color: $grey-4; } .mat-slide-toggle-thumb-container { @@ -15,5 +18,29 @@ mat-slide-toggle { .mat-slide-toggle-thumb { height: 12px !important; width: 12px !important; + box-shadow: none; + background-color: $grey-2; + } + + .mat-ripple { + display: none; + } + + .mat-slide-toggle-content { + font-family: Inter, sans-serif; + } + + &.mat-primary.mat-checked { + .mat-slide-toggle-bar { + background-color: $primary; + } + + .mat-slide-toggle-thumb { + background-color: $white; + } + + .mat-slide-toggle-thumb-container { + transform: translate3d(14px, 0, 0); + } } } diff --git a/libs/red-ui-http/src/lib/model/fileStatus.ts b/libs/red-ui-http/src/lib/model/fileStatus.ts index 3ca542465..4429e4a45 100644 --- a/libs/red-ui-http/src/lib/model/fileStatus.ts +++ b/libs/red-ui-http/src/lib/model/fileStatus.ts @@ -9,15 +9,12 @@ * https://github.com/swagger-api/swagger-codegen.git * Do not edit the class manually. */ +import { FileAttributes } from './fileAttributes'; /** * Object containing information on a specific file. */ export interface FileStatus { - /** - * Time of last analysis - */ - analysisDuration?: number; /** * Date and time when the file was added to the system. */ @@ -26,6 +23,10 @@ export interface FileStatus { * Shows if all manual changes have been applied by a reanalysis. */ allManualRedactionsApplied?: boolean; + /** + * Shows how long the last analysis took + */ + analysisDuration?: number; /** * Shows the date of approval, if approved. */ @@ -38,6 +39,7 @@ export interface FileStatus { * Shows which dictionary versions was used during the analysis. */ dictionaryVersion?: number; + fileAttributes?: FileAttributes; /** * The ID of the file. */ @@ -50,6 +52,10 @@ export interface FileStatus { * Shows if any hints were found during the analysis. */ hasHints?: boolean; + /** + * Shows if any images were found during the analysis. + */ + hasImages?: boolean; /** * Shows if any redactions were found during the analysis. */ @@ -58,6 +64,10 @@ export interface FileStatus { * Shows if any requests were found during the analysis. */ hasRequests?: boolean; + /** + * Shows if there is any change between the previous and current analysis. + */ + hasUpdates?: boolean; /** * Shows the last date of a successful analysis. */ @@ -78,14 +88,6 @@ export interface FileStatus { * The number of times the file has been analyzed. */ numberOfAnalyses?: number; - /** - * Shows if any images were found during the analysis. - */ - hasImages?: boolean; - /** - * Shows if there is any change between the previous and current analysis. - */ - hasUpdates?: boolean; /** * The number of pages of the file. */