Pull request #134: File attributes
Merge in RED/ui from file-attributes to master * commit '77fc8c1b4825fb5c07f01fd7a1efdb00e1c08491': Finished file attributes File workload separate component Display document info over workload Document info Delete file attribute Add file attribute Reorganize more files Quick fixes Move items outside of common directory Extract routing to module File attributes listing
This commit is contained in:
commit
0621aca7fc
212
apps/red-ui/src/app/app-routing.module.ts
Normal file
212
apps/red-ui/src/app/app-routing.module.ts
Normal file
@ -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 {}
|
||||
@ -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({
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
@ -7,7 +7,9 @@
|
||||
[disabled]="disabled"
|
||||
[class.small]="small"
|
||||
[class.overlay]="showDot"
|
||||
[class.dummy]="dummy"
|
||||
mat-icon-button
|
||||
[disableRipple]="dummy"
|
||||
>
|
||||
<mat-icon [svgIcon]="icon"></mat-icon>
|
||||
</button>
|
||||
|
||||
@ -36,4 +36,8 @@ button {
|
||||
background-color: $yellow-2;
|
||||
}
|
||||
}
|
||||
|
||||
&.dummy {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<any>();
|
||||
|
||||
constructor() {}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
<div class="right-title heading" translate="file-preview.tabs.document-info.label">
|
||||
<div>
|
||||
<redaction-circle-button
|
||||
icon="red:edit"
|
||||
(action)="edit()"
|
||||
tooltipPosition="before"
|
||||
tooltip="file-preview.tabs.document-info.edit"
|
||||
></redaction-circle-button>
|
||||
<redaction-circle-button
|
||||
icon="red:close"
|
||||
(action)="closeDocumentInfoView.emit()"
|
||||
tooltipPosition="before"
|
||||
tooltip="file-preview.tabs.document-info.close"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-content" redactionHasScrollbar>
|
||||
<div class="section">
|
||||
<div class="attribute" *ngFor="let attr of fileAttributesConfig">
|
||||
<div class="small-label">{{ attr.label }}:</div>
|
||||
<div>{{ file.fileAttributes.attributeIdToValue[attr.id] || '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section small-label stats-subtitle">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:folder"></mat-icon>
|
||||
<span>{{ 'file-preview.tabs.document-info.details.project' | translate: { projectName: project.name } }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:document"></mat-icon>
|
||||
<span>{{ 'file-preview.tabs.document-info.details.pages' | translate: { pages: file.numberOfPages } }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<mat-icon svgIcon="red:calendar"></mat-icon>
|
||||
<span>{{ 'file-preview.tabs.document-info.details.created-on' | translate: { date: file.added | date: 'mediumDate' } }}</span>
|
||||
</div>
|
||||
<div *ngIf="project.project.dueDate">
|
||||
<mat-icon svgIcon="red:lightning"></mat-icon>
|
||||
<span>{{ 'file-preview.tabs.document-info.details.due' | translate: { date: project.project.dueDate | date: 'mediumDate' } }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,6 @@
|
||||
<div class="empty-state-container">
|
||||
<div class="heading-xl" translate="project-listing.no-projects"></div>
|
||||
<button
|
||||
(click)="addProjectRequest.emit()"
|
||||
*ngIf="userService.isManager()"
|
||||
class="add-project-btn"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
>
|
||||
<button (click)="addProjectRequest.emit()" *ngIf="userService.isManager()" class="add-project-btn" color="primary" mat-flat-button>
|
||||
<mat-icon svgIcon="red:plus"></mat-icon>
|
||||
<span translate="project-listing.add-new"></span>
|
||||
</button>
|
||||
@ -49,6 +49,15 @@
|
||||
>
|
||||
</redaction-file-download-btn>
|
||||
|
||||
<redaction-circle-button
|
||||
*ngIf="screen === 'file-preview'"
|
||||
(action)="toggleViewDocumentInfo()"
|
||||
tooltip="file-preview.document-info"
|
||||
tooltipPosition="before"
|
||||
icon="red:status-info"
|
||||
[attr.aria-expanded]="activeDocumentInfo"
|
||||
></redaction-circle-button>
|
||||
|
||||
<!-- Ready for approval-->
|
||||
<redaction-circle-button
|
||||
(action)="setFileUnderApproval($event, fileStatus)"
|
||||
@ -1,5 +1,5 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { PermissionsService } from '../service/permissions.service';
|
||||
import { PermissionsService } from '../../utils/permissions.service';
|
||||
import { FileStatusWrapper } from '../../screens/file/model/file-status.wrapper';
|
||||
import { DialogService } from '../../dialogs/dialog.service';
|
||||
import { AppStateService } from '../../state/app-state.service';
|
||||
@ -14,6 +14,7 @@ import { DEFAULT_RUL_SET_UUID } from '../../utils/rule-set-default';
|
||||
})
|
||||
export class FileActionsComponent implements OnInit {
|
||||
@Input() fileStatus: FileStatusWrapper;
|
||||
@Input() activeDocumentInfo: boolean;
|
||||
@Output() actionPerformed = new EventEmitter<string>();
|
||||
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';
|
||||
}
|
||||
@ -0,0 +1,133 @@
|
||||
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
|
||||
<div>
|
||||
<redaction-filter
|
||||
(filtersChanged)="filtersChanged($event)"
|
||||
[chevron]="true"
|
||||
[filterTemplate]="annotationFilterTemplate"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[filters]="annotationFilters"
|
||||
></redaction-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div
|
||||
#quickNavigation
|
||||
(keydown)="preventKeyDefault($event)"
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="pagesPanelActive"
|
||||
class="quick-navigation"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="jump"
|
||||
[class.disabled]="!quickScrollFirstEnabled"
|
||||
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
|
||||
matTooltipPosition="above"
|
||||
(click)="quickScrollFirstEnabled && scrollQuickNavFirst()"
|
||||
>
|
||||
<mat-icon svgIcon="red:nav-first"></mat-icon>
|
||||
</div>
|
||||
<div class="pages" (scroll)="computeQuickNavButtonsState()" id="pages">
|
||||
<redaction-page-indicator
|
||||
(pageSelected)="pageSelectedByClick($event)"
|
||||
*ngFor="let pageNumber of displayedPages"
|
||||
[active]="pageNumber === activeViewerPage"
|
||||
[number]="pageNumber"
|
||||
[viewedPages]="fileData.viewedPages"
|
||||
>
|
||||
</redaction-page-indicator>
|
||||
</div>
|
||||
<div
|
||||
class="jump"
|
||||
[class.disabled]="!quickScrollLastEnabled"
|
||||
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
|
||||
matTooltipPosition="above"
|
||||
(click)="scrollQuickNavLast()"
|
||||
>
|
||||
<mat-icon svgIcon="red:nav-last"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="overflow: hidden; width: 100%;">
|
||||
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
|
||||
<span *ngIf="!!activeViewerPage" class="all-caps-label"
|
||||
><span translate="page"></span> {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }}
|
||||
<span [translate]="displayedAnnotations[activeViewerPage]?.annotations?.length === 1 ? 'annotation' : 'annotations'"></span
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
#annotationsElement
|
||||
(keydown)="preventKeyDefault($event)"
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="!pagesPanelActive"
|
||||
redactionHasScrollbar
|
||||
class="annotations"
|
||||
tabindex="1"
|
||||
>
|
||||
<div *ngIf="!displayedAnnotations[activeViewerPage]" class="heading-l no-annotations">
|
||||
{{ 'file-preview.no-annotations-for-page' | translate }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="annotationClicked(annotation)"
|
||||
class="annotation-wrapper"
|
||||
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"
|
||||
attr.annotation-id="{{ annotation.id }}"
|
||||
attr.annotation-page="{{ activeViewerPage }}"
|
||||
[class.active]="annotationIsSelected(annotation)"
|
||||
>
|
||||
<div class="active-marker"></div>
|
||||
<div class="annotation" [class.removed]="annotation.isChangeLogRemoved">
|
||||
<redaction-hidden-action (action)="logAnnotation(annotation)" [requiredClicks]="2">
|
||||
<div class="details">
|
||||
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<strong>{{ annotation.typeLabel | translate }}</strong>
|
||||
</div>
|
||||
<div *ngIf="annotation.dictionary && annotation.dictionary !== 'manual'">
|
||||
<strong
|
||||
><span>{{ annotation.descriptor | translate }}</span
|
||||
>: </strong
|
||||
>{{ annotation.dictionary | humanize: false }}
|
||||
</div>
|
||||
<div *ngIf="annotation.content && !annotation.isHint">
|
||||
<strong><span translate="content"></span>: </strong>{{ annotation.content }}
|
||||
</div>
|
||||
</div>
|
||||
<ng-container [ngTemplateOutlet]="annotationActionsTemplate" [ngTemplateOutletContext]="{ annotation: annotation }"> </ng-container>
|
||||
|
||||
<!-- <redaction-annotation-actions-->
|
||||
<!-- (annotationsChanged)="annotationsChangedByReviewAction($event)"-->
|
||||
<!-- [annotation]="annotation"-->
|
||||
<!-- [canPerformAnnotationActions]="canPerformAnnotationActions"-->
|
||||
<!-- [viewer]="activeViewer"-->
|
||||
<!-- ></redaction-annotation-actions>-->
|
||||
</div>
|
||||
</redaction-hidden-action>
|
||||
<redaction-comments [annotation]="annotation"></redaction-comments>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #annotationFilterTemplate let-filter="filter">
|
||||
<redaction-type-filter *ngIf="filter.topLevelFilter" [filter]="filter"></redaction-type-filter>
|
||||
<ng-container *ngIf="!filter.topLevelFilter">
|
||||
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
|
||||
{{ filter.key | humanize: false }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #annotationFilterActionTemplate let-filter="filter">
|
||||
<ng-container *ngIf="filter.key === 'skipped'">
|
||||
<redaction-circle-button
|
||||
[icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'"
|
||||
(action)="toggleSkipped.emit($event)"
|
||||
class="skipped-toggle-button"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<any>;
|
||||
@Input() annotationFilters: FilterModel[];
|
||||
@Input() fileData: FileDataModel;
|
||||
@Input() hideSkipped: boolean;
|
||||
@Input() annotationActionsTemplate: TemplateRef<any>;
|
||||
|
||||
@Output() selectAnnotation = new EventEmitter<AnnotationWrapper>();
|
||||
@Output() selectPage = new EventEmitter<number>();
|
||||
@Output() toggleSkipped = new EventEmitter<any>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-32 small-label stats-subtitle">
|
||||
<div class="pb-32 small-label stats-subtitle" [class.mt-24]="!hasFiles">
|
||||
<div>
|
||||
<mat-icon svgIcon="red:document"></mat-icon>
|
||||
<span>{{ 'project-overview.project-details.stats.documents' | translate: { count: appStateService.activeProject.files.length } }}</span>
|
||||
@ -1,4 +1,4 @@
|
||||
@import '../../../../assets/styles/red-variables';
|
||||
@import '../../../assets/styles/red-variables';
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
@ -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',
|
||||
@ -36,5 +36,5 @@
|
||||
>
|
||||
</redaction-circle-button>
|
||||
|
||||
<redaction-file-download-btn [file]="project.files" [project]="project"> </redaction-file-download-btn>
|
||||
<redaction-file-download-btn [file]="project.files" [project]="project" type="dark-bg"> </redaction-file-download-btn>
|
||||
</div>
|
||||
@ -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',
|
||||
@ -1,4 +1,4 @@
|
||||
@import '../../../../assets/styles/red-variables';
|
||||
@import '../../../assets/styles/red-variables';
|
||||
|
||||
:host {
|
||||
flex: 1;
|
||||
@ -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',
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<ng-container *ngFor="let tab of tabs">
|
||||
<div
|
||||
class="red-tab"
|
||||
*ngIf="!tab.onlyDevMode || userPreferenceService.areDevFeaturesEnabled"
|
||||
*ngIf="(!tab.onlyAdmin || permissionsService.isAdmin()) && (!tab.onlyDevMode || userPreferenceService.areDevFeaturesEnabled)"
|
||||
(click)="switchView(tab.screen)"
|
||||
[class.active]="tab.screen === screen"
|
||||
>
|
||||
|
||||
@ -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
|
||||
) {}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@import '../../../../../assets/styles/red-variables';
|
||||
@import '../../../assets/styles/red-variables';
|
||||
|
||||
.first-row {
|
||||
display: flex;
|
||||
@ -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({
|
||||
@ -0,0 +1,39 @@
|
||||
<section class="dialog">
|
||||
<div class="dialog-header heading-l">
|
||||
{{ (fileAttribute ? 'add-edit-file-attribute.title.edit' : 'add-edit-file-attribute.title.new') | translate: { name: fileAttribute?.label } }}
|
||||
</div>
|
||||
|
||||
<form (submit)="saveFileAttribute()" [formGroup]="fileAttributeForm">
|
||||
<div class="dialog-content">
|
||||
<div class="red-input-group required w-300">
|
||||
<label translate="add-edit-file-attribute.form.name"></label>
|
||||
<input formControlName="label" name="label" type="text" placeholder="{{ 'add-edit-file-attribute.form.name-placeholder' | translate }}" />
|
||||
</div>
|
||||
|
||||
<div class="red-input-group required w-300">
|
||||
<label translate="add-edit-file-attribute.form.column-header"></label>
|
||||
<input
|
||||
formControlName="csvColumnHeader"
|
||||
name="csvColumnHeader"
|
||||
type="text"
|
||||
placeholder="{{ 'add-edit-file-attribute.form.column-header-placeholder' | translate }}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="red-input-group ignore-label-styles">
|
||||
<mat-slide-toggle formControlName="readonly" color="primary">{{ 'add-edit-file-attribute.form.read-only' | translate }}</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="red-input-group ignore-label-styles">
|
||||
<mat-slide-toggle formControlName="visible" color="primary">{{ 'add-edit-file-attribute.form.visible' | translate }}</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="fileAttributeForm.invalid || !changed" color="primary" mat-flat-button type="submit">
|
||||
{{ 'add-edit-file-attribute.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
|
||||
</section>
|
||||
@ -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<AddEditFileAttributeDialogComponent>,
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
@ -0,0 +1,21 @@
|
||||
<section class="dialog">
|
||||
<div class="dialog-header heading-l">
|
||||
{{ 'confirm-delete-file-attribute.title' | translate: { name: fileAttribute.label } }}
|
||||
</div>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="heading" translate="confirm-delete-file-attribute.warning"></div>
|
||||
|
||||
<mat-checkbox *ngFor="let checkbox of checkboxes; let idx = index" [(ngModel)]="checkbox.value" color="primary">
|
||||
{{ 'confirm-delete-file-attribute.checkbox-' + (idx + 1) | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="!valid" color="primary" mat-flat-button (click)="deleteFileAttribute()">
|
||||
{{ 'confirm-delete-file-attribute.delete' | translate }}
|
||||
</button>
|
||||
<div class="all-caps-label cancel" (click)="cancel()" translate="confirm-delete-file-attribute.cancel"></div>
|
||||
</div>
|
||||
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
|
||||
</section>
|
||||
@ -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;
|
||||
}
|
||||
@ -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<ConfirmDeleteFileAttributeDialogComponent>,
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@ -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<AddEditFileAttributeDialogComponent> {
|
||||
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<ConfirmDeleteFileAttributeDialogComponent> {
|
||||
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<DocumentInfoDialogComponent> {
|
||||
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();
|
||||
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
<section *ngIf="!!documentInfoForm" class="dialog">
|
||||
<div class="dialog-header heading-l" translate="document-info.title"></div>
|
||||
|
||||
<form (submit)="saveDocumentInfo()" [formGroup]="documentInfoForm">
|
||||
<div class="dialog-content">
|
||||
<div class="red-input-group w-300" *ngFor="let attr of attributes">
|
||||
<label>{{ attr.label }}</label>
|
||||
<input [formControlName]="attr.id" [name]="attr.id" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<button [disabled]="documentInfoForm.invalid" color="primary" mat-flat-button type="submit">
|
||||
{{ 'document-info.save' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<redaction-circle-button icon="red:close" mat-dialog-close class="dialog-close"></redaction-circle-button>
|
||||
</section>
|
||||
@ -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<DocumentInfoDialogComponent>,
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
{{ 'dictionary-listing.table-header.title' | translate: { length: displayedDictionaries.length } }}
|
||||
</span>
|
||||
|
||||
<div class="dictionary-actions-container">
|
||||
<div class="attributes-actions-container">
|
||||
<redaction-search-input [form]="searchForm" [placeholder]="'dictionary-listing.search'"></redaction-search-input>
|
||||
<div class="actions">
|
||||
<redaction-icon-button
|
||||
@ -111,11 +111,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank small-label">
|
||||
<div class="center small-label">
|
||||
{{ dict.rank }}
|
||||
</div>
|
||||
|
||||
<div class="analyzed">
|
||||
<div class="center">
|
||||
<redaction-annotation-icon [dictType]="dict" [type]="dict.hint ? 'circle' : 'square'"></redaction-annotation-icon>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<redaction-admin-breadcrumbs class="flex-1"></redaction-admin-breadcrumbs>
|
||||
|
||||
<redaction-tabs [screen]="'file-attributes'"></redaction-tabs>
|
||||
|
||||
<div class="actions flex-1">
|
||||
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="red-content-inner">
|
||||
<div class="left-container">
|
||||
<div class="header-item">
|
||||
<div class="select-all-container">
|
||||
<div
|
||||
(click)="toggleSelectAll()"
|
||||
[class.active]="areAllAttributesSelected"
|
||||
class="select-oval always-visible"
|
||||
*ngIf="!areAllAttributesSelected && !areSomeAttributesSelected"
|
||||
></div>
|
||||
<mat-icon
|
||||
*ngIf="areAllAttributesSelected"
|
||||
(click)="toggleSelectAll()"
|
||||
class="selection-icon active"
|
||||
svgIcon="red:radio-selected"
|
||||
></mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="areSomeAttributesSelected && !areAllAttributesSelected"
|
||||
(click)="toggleSelectAll()"
|
||||
class="selection-icon"
|
||||
svgIcon="red:radio-indeterminate"
|
||||
></mat-icon>
|
||||
</div>
|
||||
|
||||
<span class="all-caps-label">
|
||||
{{ 'file-attributes-listing.table-header.title' | translate: { length: displayedAttributes.length } }}
|
||||
</span>
|
||||
|
||||
<div class="attributes-actions-container">
|
||||
<redaction-search-input [form]="searchForm" [placeholder]="'file-attributes-listing.search'"></redaction-search-input>
|
||||
<div class="actions">
|
||||
<redaction-icon-button
|
||||
icon="red:plus"
|
||||
(action)="openAddEditAttributeDialog($event)"
|
||||
text="file-attributes-listing.add-new"
|
||||
type="primary"
|
||||
></redaction-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [class.no-data]="noData" class="table-header" redactionSyncWidth="table-item">
|
||||
<div class="select-oval-placeholder"></div>
|
||||
|
||||
<redaction-table-col-name
|
||||
label="file-attributes-listing.table-col-names.name"
|
||||
(toggleSort)="toggleSort($event)"
|
||||
[activeSortingOption]="sortingOption"
|
||||
[withSort]="true"
|
||||
column="name"
|
||||
></redaction-table-col-name>
|
||||
|
||||
<!-- <redaction-table-col-name label="file-attributes-listing.table-col-names.created-by" class="flex-center"></redaction-table-col-name>-->
|
||||
|
||||
<redaction-table-col-name label="file-attributes-listing.table-col-names.read-only" class="flex-center"></redaction-table-col-name>
|
||||
|
||||
<div></div>
|
||||
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="noData" class="no-data heading-l" translate="file-attributes-listing.no-data"></div>
|
||||
|
||||
<cdk-virtual-scroll-viewport [itemSize]="80" redactionHasScrollbar>
|
||||
<!-- Table lines -->
|
||||
<div class="table-item" *cdkVirtualFor="let attribute of displayedAttributes | sortBy: sortingOption.order:sortingOption.column">
|
||||
<div class="pr-0" (click)="toggleAttributeSelected($event, attribute)">
|
||||
<div *ngIf="!isAttributeSelected(attribute)" class="select-oval"></div>
|
||||
<mat-icon class="selection-icon active" *ngIf="isAttributeSelected(attribute)" svgIcon="red:radio-selected"></mat-icon>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ attribute.label }}
|
||||
</div>
|
||||
<!-- TODO-->
|
||||
<!-- <div class="center">-->
|
||||
<!-- <redaction-initials-avatar [userId]="attribute.userId" [withName]="true" size="large"></redaction-initials-avatar>-->
|
||||
<!-- </div>-->
|
||||
<div class="center">
|
||||
<redaction-circle-button
|
||||
*ngIf="!attribute.editable"
|
||||
type="dark-bg"
|
||||
icon="red:read-only"
|
||||
tooltip="file-attributes-listing.read-only"
|
||||
[dummy]="true"
|
||||
></redaction-circle-button>
|
||||
</div>
|
||||
<div class="actions-container">
|
||||
<div class="action-buttons">
|
||||
<redaction-circle-button
|
||||
(action)="openAddEditAttributeDialog($event, attribute)"
|
||||
tooltip="file-attributes-listing.action.edit"
|
||||
tooltipPosition="before"
|
||||
type="dark-bg"
|
||||
icon="red:edit"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
<redaction-circle-button
|
||||
(action)="openConfirmDeleteAttributeDialog($event, attribute)"
|
||||
tooltip="file-attributes-listing.action.delete"
|
||||
tooltipPosition="before"
|
||||
type="dark-bg"
|
||||
icon="red:trash"
|
||||
>
|
||||
</redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrollbar-placeholder"></div>
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
||||
|
||||
<div class="right-container"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<redaction-full-page-loading-indicator [displayed]="!viewReady"></redaction-full-page-loading-indicator>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -5,14 +5,7 @@
|
||||
<redaction-tabs [screen]="'watermark'"></redaction-tabs>
|
||||
|
||||
<div class="actions flex-1">
|
||||
<redaction-circle-button
|
||||
class="ml-6"
|
||||
*ngIf="permissionsService.isUser()"
|
||||
[routerLink]="['/ui/projects/']"
|
||||
tooltip="common.close"
|
||||
tooltipPosition="before"
|
||||
icon="red:close"
|
||||
></redaction-circle-button>
|
||||
<redaction-circle-button [routerLink]="['../..']" tooltip="common.close" tooltipPosition="before" icon="red:close"></redaction-circle-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -129,7 +129,11 @@
|
||||
</ng-container>
|
||||
<div class="vertical-line"></div>
|
||||
|
||||
<redaction-file-actions (actionPerformed)="fileActionPerformed($event)" *ngIf="viewReady"></redaction-file-actions>
|
||||
<redaction-file-actions
|
||||
(actionPerformed)="fileActionPerformed($event)"
|
||||
[activeDocumentInfo]="viewDocumentInfo"
|
||||
*ngIf="viewReady"
|
||||
></redaction-file-actions>
|
||||
|
||||
<redaction-circle-button
|
||||
(action)="toggleFullScreen()"
|
||||
@ -200,118 +204,28 @@
|
||||
</div>
|
||||
|
||||
<div class="right-container">
|
||||
<div class="right-title heading" translate="file-preview.tabs.annotations.label">
|
||||
<div>
|
||||
<redaction-filter
|
||||
(filtersChanged)="filtersChanged($event)"
|
||||
[chevron]="true"
|
||||
[filterTemplate]="annotationFilterTemplate"
|
||||
[actionsTemplate]="annotationFilterActionTemplate"
|
||||
[filters]="annotationFilters"
|
||||
></redaction-filter>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-content">
|
||||
<div
|
||||
#quickNavigation
|
||||
(keydown)="preventKeyDefault($event)"
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="pagesPanelActive"
|
||||
class="quick-navigation"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="jump"
|
||||
[class.disabled]="!quickScrollFirstEnabled"
|
||||
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
|
||||
matTooltipPosition="above"
|
||||
(click)="quickScrollFirstEnabled && scrollQuickNavFirst()"
|
||||
>
|
||||
<mat-icon svgIcon="red:nav-first"></mat-icon>
|
||||
</div>
|
||||
<div class="pages" (scroll)="computeQuickNavButtonsState()" id="pages">
|
||||
<redaction-page-indicator
|
||||
(pageSelected)="pageSelectedByClick($event)"
|
||||
*ngFor="let pageNumber of displayedPages"
|
||||
[active]="pageNumber === activeViewerPage"
|
||||
[number]="pageNumber"
|
||||
[viewedPages]="fileData.viewedPages"
|
||||
>
|
||||
</redaction-page-indicator>
|
||||
</div>
|
||||
<div
|
||||
class="jump"
|
||||
[class.disabled]="!quickScrollLastEnabled"
|
||||
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
|
||||
matTooltipPosition="above"
|
||||
(click)="scrollQuickNavLast()"
|
||||
>
|
||||
<mat-icon svgIcon="red:nav-last"></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<redaction-document-info
|
||||
*ngIf="viewReady && viewDocumentInfo"
|
||||
[file]="fileData.fileStatus.fileStatus"
|
||||
(closeDocumentInfoView)="viewDocumentInfo = false"
|
||||
>
|
||||
</redaction-document-info>
|
||||
|
||||
<div style="overflow: hidden; width: 100%;">
|
||||
<div attr.anotation-page-header="{{ activeViewerPage }}" class="page-separator">
|
||||
<span *ngIf="!!activeViewerPage" class="all-caps-label"
|
||||
><span translate="page"></span> {{ activeViewerPage }} - {{ displayedAnnotations[activeViewerPage]?.annotations?.length || 0 }}
|
||||
<span [translate]="displayedAnnotations[activeViewerPage]?.annotations?.length === 1 ? 'annotation' : 'annotations'"></span
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
#annotationsElement
|
||||
(keydown)="preventKeyDefault($event)"
|
||||
(keyup)="preventKeyDefault($event)"
|
||||
[class.active-panel]="!pagesPanelActive"
|
||||
redactionHasScrollbar
|
||||
class="annotations"
|
||||
tabindex="1"
|
||||
>
|
||||
<div *ngIf="!displayedAnnotations[activeViewerPage]" class="heading-l no-annotations">
|
||||
{{ 'file-preview.no-annotations-for-page' | translate }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
(click)="annotationClicked(annotation)"
|
||||
class="annotation-wrapper"
|
||||
*ngFor="let annotation of displayedAnnotations[activeViewerPage]?.annotations"
|
||||
attr.annotation-id="{{ annotation.id }}"
|
||||
attr.annotation-page="{{ activeViewerPage }}"
|
||||
[class.active]="annotationIsSelected(annotation)"
|
||||
>
|
||||
<div class="active-marker"></div>
|
||||
<div class="annotation" [class.removed]="annotation.isChangeLogRemoved">
|
||||
<redaction-hidden-action (action)="logAnnotation(annotation)" [requiredClicks]="2">
|
||||
<div class="details">
|
||||
<redaction-type-annotation-icon [annotation]="annotation"></redaction-type-annotation-icon>
|
||||
<div class="flex-1">
|
||||
<div>
|
||||
<strong>{{ annotation.typeLabel | translate }}</strong>
|
||||
</div>
|
||||
<div *ngIf="annotation.dictionary && annotation.dictionary !== 'manual'">
|
||||
<strong
|
||||
><span>{{ annotation.descriptor | translate }}</span
|
||||
>: </strong
|
||||
>{{ annotation.dictionary | humanize: false }}
|
||||
</div>
|
||||
<div *ngIf="annotation.content && !annotation.isHint">
|
||||
<strong><span translate="content"></span>: </strong>{{ annotation.content }}
|
||||
</div>
|
||||
</div>
|
||||
<redaction-annotation-actions
|
||||
(annotationsChanged)="annotationsChangedByReviewAction($event)"
|
||||
[annotation]="annotation"
|
||||
[canPerformAnnotationActions]="canPerformAnnotationActions"
|
||||
[viewer]="activeViewer"
|
||||
></redaction-annotation-actions>
|
||||
</div>
|
||||
</redaction-hidden-action>
|
||||
<redaction-comments [annotation]="annotation"></redaction-comments>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<redaction-file-workload
|
||||
#fileWorkloadComponent
|
||||
[annotations]="annotations"
|
||||
[selectedAnnotations]="selectedAnnotations"
|
||||
[activeViewerPage]="activeViewerPage"
|
||||
[shouldDeselectAnnotationsOnPageChange]="shouldDeselectAnnotationsOnPageChange"
|
||||
[dialogRef]="dialogRef"
|
||||
[annotationFilters]="annotationFilters"
|
||||
[fileData]="fileData"
|
||||
[hideSkipped]="hideSkipped"
|
||||
[annotationActionsTemplate]="annotationActionsTemplate"
|
||||
(selectAnnotation)="selectAnnotation($event)"
|
||||
(selectPage)="selectPage($event)"
|
||||
(toggleSkipped)="toggleSkipped($event)"
|
||||
></redaction-file-workload>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -323,17 +237,11 @@
|
||||
</div>
|
||||
</redaction-full-page-loading-indicator>
|
||||
|
||||
<ng-template #annotationFilterTemplate let-filter="filter">
|
||||
<redaction-type-filter *ngIf="filter.topLevelFilter" [filter]="filter"></redaction-type-filter>
|
||||
<ng-container *ngIf="!filter.topLevelFilter">
|
||||
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
|
||||
{{ filter.key | humanize: false }}
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #annotationFilterActionTemplate let-filter="filter">
|
||||
<ng-container *ngIf="filter.key === 'skipped'">
|
||||
<redaction-circle-button [icon]="hideSkipped ? 'red:visibility-off' : 'red:visibility'" (action)="toggleSkipped($event)" class="skipped-toggle-button">
|
||||
</redaction-circle-button>
|
||||
</ng-container>
|
||||
<ng-template #annotationActionsTemplate let-annotation="annotation">
|
||||
<redaction-annotation-actions
|
||||
(annotationsChanged)="annotationsChangedByReviewAction($event)"
|
||||
[annotation]="annotation"
|
||||
[canPerformAnnotationActions]="canPerformAnnotationActions"
|
||||
[viewer]="activeViewer"
|
||||
></redaction-annotation-actions>
|
||||
</ng-template>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<any>;
|
||||
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<any>;
|
||||
|
||||
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 (<html>) 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 (<html>) 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() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user