WIP on master

RED-9788 - Adapt Annotation column to display only annotations per component if component is selected
This commit is contained in:
Valentin Mihai 2024-08-05 15:03:04 +03:00
parent 9b770b2be7
commit 9535f6f06a
4 changed files with 199 additions and 292 deletions

View File

@ -1,243 +1,219 @@
<ng-container *ngIf="!isDocumine && !documentInfoService.shown()"> <div
<div *ngIf="excludedPagesService.shown(); else selectAndFilter"
*ngIf="excludedPagesService.shown(); else selectAndFilter" class="right-title heading"
class="right-title heading" translate="file-preview.tabs.exclude-pages.label"
translate="file-preview.tabs.exclude-pages.label" >
> <div>
<iqser-circle-button
(action)="excludedPagesService.toggle()"
[tooltip]="'file-preview.tabs.exclude-pages.close' | translate"
icon="iqser:close"
tooltipPosition="before"
></iqser-circle-button>
</div>
</div>
<ng-template #selectAndFilter>
<div class="right-title heading">
{{ title() | translate }}
<div> <div>
<iqser-circle-button <div
(action)="excludedPagesService.toggle()" (click)="multiSelectService.activate()"
[tooltip]="'file-preview.tabs.exclude-pages.close' | translate" *ngIf="multiSelectService.enabled() && multiSelectService.inactive()"
icon="iqser:close" [attr.help-mode-key]="'workload_bulk_selection'"
tooltipPosition="before" class="all-caps-label primary pointer"
></iqser-circle-button> translate="file-preview.tabs.annotations.select"
></div>
<iqser-popup-filter
*ngIf="documentInfoService.hidden()"
[actionsTemplate]="annotationFilterActionTemplate"
[attr.help-mode-key]="'workload_filter'"
[fileId]="state.file()?.id"
[primaryFiltersSlug]="'primaryFilters'"
[secondaryFiltersSlug]="'secondaryFilters'"
></iqser-popup-filter>
</div> </div>
</div> </div>
</ng-template>
<ng-template #selectAndFilter> <div class="right-content">
<div class="right-title heading"> <redaction-readonly-banner
{{ title() | translate }} *ngIf="showAnalysisDisabledBanner; else readOnlyBanner"
<div> [customTranslation]="translations.analysisDisabled"
<div ></redaction-readonly-banner>
(click)="multiSelectService.activate()" <ng-template #readOnlyBanner>
*ngIf="multiSelectService.enabled() && multiSelectService.inactive()" <redaction-readonly-banner *ngIf="state.isReadonly()"></redaction-readonly-banner>
[attr.help-mode-key]="'workload_bulk_selection'"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select"
></div>
<ng-container *ngTemplateOutlet="annotationsFilter"></ng-container>
</div>
</div>
</ng-template> </ng-template>
</ng-container>
@if (displayedAnnotations$ | async; as annotations) { <div *ngIf="multiSelectService.active()" class="multi-select">
<div class="right-content"> <div class="selected-wrapper">
<ng-container *ngIf="!isDocumine"> <iqser-round-checkbox
<redaction-readonly-banner (click)="annotationManager.deselect()"
*ngIf="showAnalysisDisabledBanner; else readOnlyBanner" [indeterminate]="listingService.areSomeSelected$ | async"
[customTranslation]="translations.analysisDisabled" type="with-bg"
></redaction-readonly-banner> ></iqser-round-checkbox>
<ng-template #readOnlyBanner>
<redaction-readonly-banner *ngIf="state.isReadonly()"></redaction-readonly-banner>
</ng-template>
</ng-container>
<div *ngIf="multiSelectService.active()" class="multi-select"> <span class="all-caps-label">{{ listingService.selectedLength$ | async }} selected </span>
<div class="selected-wrapper">
<iqser-round-checkbox
(click)="annotationManager.deselect()"
[indeterminate]="listingService.areSomeSelected$ | async"
type="with-bg"
></iqser-round-checkbox>
<span class="all-caps-label">{{ listingService.selectedLength$ | async }} selected </span> <redaction-annotation-actions
*ngIf="listingService.areSomeSelected$ | async"
<redaction-annotation-actions [alwaysVisible]="true"
*ngIf="listingService.areSomeSelected$ | async" [annotations]="listingService.selectedEntities$ | async"
[alwaysVisible]="true" [canPerformAnnotationActions]="state.isWritable()"
[annotations]="listingService.selectedEntities$ | async" buttonType="primary"
[canPerformAnnotationActions]="state.isWritable()" tooltipPosition="above"
buttonType="primary" ></redaction-annotation-actions>
tooltipPosition="above"
></redaction-annotation-actions>
</div>
<iqser-circle-button
(action)="multiSelectService.deactivate()"
[tooltip]="'file-preview.tabs.multi-select.close' | translate"
[type]="circleButtonTypes.primary"
icon="iqser:close"
tooltipPosition="before"
></iqser-circle-button>
</div> </div>
<div class="annotations-wrapper" [class.documine-direction]="isDocumine"> <iqser-circle-button
<div [class.border-left]="isDocumine"> (action)="multiSelectService.deactivate()"
<div [tooltip]="'file-preview.tabs.multi-select.close' | translate"
#quickNavigation [type]="circleButtonTypes.primary"
*ngIf="!(documentInfoService.shown() && isDocumine); else documentInfo" icon="iqser:close"
(keydown)="preventKeyDefault($event)" tooltipPosition="before"
(keyup)="preventKeyDefault($event)" ></iqser-circle-button>
[class.active-panel]="pagesPanelActive" </div>
class="quick-navigation"
tabindex="0"
>
<div
(click)="scrollQuickNavFirst()"
[class.disabled]="pdf.currentPage() === 1"
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
[class.documine-height]="isDocumine"
class="jump"
matTooltipPosition="above"
>
<mat-icon svgIcon="iqser:nav-first"></mat-icon>
</div>
<redaction-pages (click)="pagesPanelActive = true" [pages]="displayedPages"></redaction-pages> <div class="annotations-wrapper">
<div
#quickNavigation
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="pagesPanelActive"
class="quick-navigation"
tabindex="0"
>
<div
(click)="scrollQuickNavFirst()"
[class.disabled]="pdf.currentPage() === 1"
[matTooltip]="'file-preview.quick-nav.jump-first' | translate"
class="jump"
matTooltipPosition="above"
>
<mat-icon svgIcon="iqser:nav-first"></mat-icon>
</div>
<redaction-pages (click)="pagesPanelActive = true" [pages]="displayedPages"></redaction-pages>
<div
(click)="scrollQuickNavLast()"
[class.disabled]="pdf.currentPage() === state.file()?.numberOfPages"
[matTooltip]="'file-preview.quick-nav.jump-last' | translate"
class="jump"
matTooltipPosition="above"
>
<mat-icon svgIcon="iqser:nav-last"></mat-icon>
</div>
</div>
<div class="content">
<div
*ngIf="!viewModeService.isEarmarks()"
[attr.anotation-page-header]="pdf.currentPage()"
[hidden]="excludedPagesService.shown()"
class="workload-separator"
>
<span *ngIf="!!pdf.currentPage()" class="flex-align-items-center">
<iqser-circle-button
(action)="excludedPagesService.toggle()"
*ngIf="currentPageIsExcluded()"
[size]="14"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize"
class="mr-10 primary"
icon="red:exclude-pages"
tooltipPosition="above"
></iqser-circle-button>
<span
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }"
[translate]="'page'"
class="all-caps-label"
></span>
</span>
<div *ngIf="multiSelectService.active()">
<div <div
(click)="scrollQuickNavLast()" (click)="selectAllOnActivePage()"
[class.disabled]="pdf.currentPage() === state.file()?.numberOfPages" class="all-caps-label primary pointer"
[matTooltip]="'file-preview.quick-nav.jump-last' | translate" translate="file-preview.tabs.annotations.select-all"
[class.documine-height]="isDocumine" ></div>
class="jump" <div
matTooltipPosition="above" (click)="deselectAllOnActivePage()"
> class="all-caps-label primary pointer"
<mat-icon svgIcon="iqser:nav-last"></mat-icon> translate="file-preview.tabs.annotations.select-none"
</div> ></div>
</div> </div>
</div> </div>
<div class="content" [class.documine-width]="isDocumine"> <div
@if (state.file().excluded && documentInfoService.hidden()) { #annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
[hidden]="excludedPagesService.shown()"
class="annotations"
id="annotations-list"
tabindex="1"
>
<ng-container *ngIf="pdf.currentPage() && !displayedAnnotations.get(pdf.currentPage())?.length">
<iqser-empty-state <iqser-empty-state
[horizontalPadding]="40" [horizontalPadding]="24"
[text]="'file-preview.tabs.is-excluded' | translate" [text]="'file-preview.no-data.title' | translate"
icon="red:needs-work" [verticalPadding]="40"
></iqser-empty-state> icon="iqser:document"
} @else {
<div
*ngIf="!viewModeService.isEarmarks()"
[attr.anotation-page-header]="pdf.currentPage()"
[hidden]="excludedPagesService.shown()"
class="workload-separator"
> >
<span *ngIf="!!pdf.currentPage()" class="flex-align-items-center"> <ng-container *ngIf="currentPageIsExcluded() && displayedPages.length">
<ng-container *ngIf="!isDocumine; else documineHeader"> {{ 'file-preview.tabs.annotations.page-is' | translate }}
<iqser-circle-button <a
(action)="excludedPagesService.toggle()" (click)="excludedPagesService.toggle()"
*ngIf="currentPageIsExcluded()" class="with-underline"
[size]="14" translate="file-preview.excluded-from-redaction"
[tooltip]="'file-preview.excluded-from-redaction' | translate | capitalize" ></a
class="mr-10 primary" >.
icon="red:exclude-pages"
tooltipPosition="above"
></iqser-circle-button>
<span
[translateParams]="{ page: pdf.currentPage(), count: activeAnnotations.length }"
[translate]="'page'"
class="all-caps-label"
></span>
</ng-container>
</span>
<div *ngIf="multiSelectService.active()">
<div
(click)="selectAllOnActivePage()"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select-all"
></div>
<div
(click)="deselectAllOnActivePage()"
class="all-caps-label primary pointer"
translate="file-preview.tabs.annotations.select-none"
></div>
</div>
</div>
<div
#annotationsElement
(keydown)="preventKeyDefault($event)"
(keyup)="preventKeyDefault($event)"
[class.active-panel]="!pagesPanelActive"
[hidden]="excludedPagesService.shown()"
class="annotations"
id="annotations-list"
tabindex="1"
>
<ng-container *ngIf="pdf.currentPage() && !displayedAnnotations.get(pdf.currentPage())?.length">
<iqser-empty-state
[horizontalPadding]="24"
[text]="'file-preview.no-data.title' | translate"
[verticalPadding]="40"
icon="iqser:document"
>
<ng-container *ngIf="currentPageIsExcluded() && displayedPages.length">
{{ 'file-preview.tabs.annotations.page-is' | translate }}
<a
(click)="excludedPagesService.toggle()"
class="with-underline"
translate="file-preview.excluded-from-redaction"
></a
>.
</ng-container>
<ng-container
*ngIf="(fileDataService.allLength$ | async) === 0 || filterService.noAnnotationsFilterChecked"
>
{{ 'file-preview.tabs.annotations.no-annotations' | translate }}
</ng-container>
<ng-container
*ngIf="
(fileDataService.allLength$ | async) > 0 &&
displayedPages.length === 0 &&
!filterService.noAnnotationsFilterChecked
"
>{{ 'file-preview.tabs.annotations.wrong-filters' | translate }}
<a
(click)="filterService.reset()"
class="with-underline"
translate="file-preview.tabs.annotations.reset"
></a>
{{ 'file-preview.tabs.annotations.the-filters' | translate }}
</ng-container>
</iqser-empty-state>
<div *ngIf="displayedPages.length" class="no-annotations-buttons-container mt-32">
<iqser-icon-button
(action)="jumpToPreviousWithAnnotations()"
[disabled]="pdf.currentPage() <= displayedPages[0]"
[label]="'file-preview.tabs.annotations.jump-to-previous' | translate"
[type]="iconButtonTypes.dark"
icon="iqser:nav-prev"
></iqser-icon-button>
<iqser-icon-button
(action)="jumpToNextWithAnnotations()"
[disabled]="pdf.currentPage() >= displayedPages[displayedPages.length - 1]"
[label]="'file-preview.tabs.annotations.jump-to-next' | translate"
[type]="iconButtonTypes.dark"
class="mt-8"
icon="iqser:nav-next"
></iqser-icon-button>
</div>
</ng-container> </ng-container>
<redaction-annotations-list <ng-container *ngIf="(fileDataService.allLength$ | async) === 0">
(pagesPanelActive)="pagesPanelActive = $event" {{ 'file-preview.tabs.annotations.no-annotations' | translate }}
[annotations]="annotations.get(pdf.currentPage())" </ng-container>
></redaction-annotations-list>
<ng-container *ngIf="(fileDataService.allLength$ | async) > 0 && displayedPages.length === 0"
>{{ 'file-preview.tabs.annotations.wrong-filters' | translate }}
<a (click)="filterService.reset()" class="with-underline" translate="file-preview.tabs.annotations.reset"></a>
{{ 'file-preview.tabs.annotations.the-filters' | translate }}
</ng-container>
</iqser-empty-state>
<div *ngIf="displayedPages.length" class="no-annotations-buttons-container mt-32">
<iqser-icon-button
(action)="jumpToPreviousWithAnnotations()"
[disabled]="pdf.currentPage() <= displayedPages[0]"
[label]="'file-preview.tabs.annotations.jump-to-previous' | translate"
[type]="iconButtonTypes.dark"
icon="iqser:nav-prev"
></iqser-icon-button>
<iqser-icon-button
(action)="jumpToNextWithAnnotations()"
[disabled]="pdf.currentPage() >= displayedPages[displayedPages.length - 1]"
[label]="'file-preview.tabs.annotations.jump-to-next' | translate"
[type]="iconButtonTypes.dark"
class="mt-8"
icon="iqser:nav-next"
></iqser-icon-button>
</div> </div>
} </ng-container>
<redaction-page-exclusion *ngIf="excludedPagesService.shown()"></redaction-page-exclusion>
<redaction-annotations-list
(pagesPanelActive)="pagesPanelActive = $event"
[annotations]="(displayedAnnotations$ | async)?.get(pdf.currentPage())"
></redaction-annotations-list>
</div> </div>
<redaction-page-exclusion *ngIf="excludedPagesService.shown()"></redaction-page-exclusion>
</div> </div>
</div> </div>
} </div>
<ng-template #annotationFilterActionTemplate let-filter="filter"> <ng-template #annotationFilterActionTemplate let-filter="filter">
<iqser-circle-button <iqser-circle-button
@ -251,23 +227,3 @@
iqserPreventDefault iqserPreventDefault
></iqser-circle-button> ></iqser-circle-button>
</ng-template> </ng-template>
<ng-template #documineHeader>
<span [translate]="'annotations'"></span>
<ng-container *ngTemplateOutlet="annotationsFilter"></ng-container>
</ng-template>
<ng-template #annotationsFilter>
<iqser-popup-filter
*ngIf="documentInfoService.hidden() || isDocumine"
[actionsTemplate]="annotationFilterActionTemplate"
[attr.help-mode-key]="'workload_filter'"
[fileId]="state.file()?.id"
[primaryFiltersSlug]="'primaryFilters'"
[secondaryFiltersSlug]="'secondaryFilters'"
></iqser-popup-filter>
</ng-template>
<ng-template #documentInfo>
<redaction-document-info id="document-info"></redaction-document-info>
</ng-template>

View File

@ -27,43 +27,11 @@
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
&.documine-direction {
flex-direction: row-reverse;
justify-content: space-between;
}
.border-left {
border-left: 1px solid var(--iqser-separator);
}
.content { .content {
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&.documine-width {
width: calc(var(--documine-workload-content-width));
border-right: 1px solid var(--iqser-separator);
z-index: 1;
.workload-separator {
min-height: 37px;
background: var(--iqser-grey-8);
.flex-align-items-center {
width: 100%;
justify-content: space-between;
::ng-deep span {
color: var(--iqser-text);
font-size: var(--iqser-font-size);
line-height: 20px;
font-weight: 600;
}
}
}
}
} }
} }
@ -77,9 +45,8 @@
} }
.quick-navigation { .quick-navigation {
height: 100%;
border-right: 1px solid var(--iqser-separator); border-right: 1px solid var(--iqser-separator);
min-width: var(--qiuck-navigation-width); min-width: 61px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -92,10 +59,6 @@
cursor: pointer; cursor: pointer;
transition: background-color 0.25s; transition: background-color 0.25s;
&.documine-height {
min-height: 37px;
}
&:not(.disabled):hover { &:not(.disabled):hover {
background-color: var(--iqser-tab-hover); background-color: var(--iqser-tab-hover);
} }
@ -129,7 +92,3 @@
[hidden] { [hidden] {
display: none !important; display: none !important;
} }
redaction-document-info {
width: var(--workload-width);
}

View File

@ -25,7 +25,7 @@ import { workloadTranslations } from '@translations/workload-translations';
import { UserPreferenceService } from '@users/user-preference.service'; import { UserPreferenceService } from '@users/user-preference.service';
import { getLocalStorageDataByFileId } from '@utils/local-storage'; import { getLocalStorageDataByFileId } from '@utils/local-storage';
import { combineLatest, delay, Observable } from 'rxjs'; import { combineLatest, delay, Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import scrollIntoView from 'scroll-into-view-if-needed'; import scrollIntoView from 'scroll-into-view-if-needed';
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service'; import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service'; import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service';
@ -182,7 +182,6 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
delay(0), delay(0),
map(([annotations, primary, secondary]) => this.#filterAnnotations(annotations, primary, secondary)), map(([annotations, primary, secondary]) => this.#filterAnnotations(annotations, primary, secondary)),
map(annotations => this.#mapListItemsFromAnnotationWrapperArray(annotations)), map(annotations => this.#mapListItemsFromAnnotationWrapperArray(annotations)),
tap(annotations => this.#scrollToFirstAnnotationPage(annotations)),
); );
} }
@ -521,11 +520,4 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnInit, On
}); });
return listItemsMap; return listItemsMap;
} }
#scrollToFirstAnnotationPage(annotations: Map<number, ListItem<AnnotationWrapper>[]>) {
if (this.isDocumine && annotations.size && !this.displayedPages.includes(this.pdf.currentPage())) {
const page = annotations.keys().next().value;
this.pdf.navigateTo(page);
}
}
} }

View File

@ -1,9 +1,9 @@
{ {
"ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_NAME": null,
"ADMIN_CONTACT_URL": null, "ADMIN_CONTACT_URL": null,
"API_URL": "https://dan2.iqser.cloud", "API_URL": "https://frontend2.iqser.cloud",
"APP_NAME": "RedactManager", "APP_NAME": "RedactManager",
"IS_DOCUMINE": false, "IS_DOCUMINE": true,
"RULE_EDITOR_DEV_ONLY": false, "RULE_EDITOR_DEV_ONLY": false,
"AUTO_READ_TIME": 3, "AUTO_READ_TIME": 3,
"BACKEND_APP_VERSION": "4.4.40", "BACKEND_APP_VERSION": "4.4.40",
@ -13,13 +13,13 @@
"MAX_RETRIES_ON_SERVER_ERROR": 3, "MAX_RETRIES_ON_SERVER_ERROR": 3,
"OAUTH_CLIENT_ID": "redaction", "OAUTH_CLIENT_ID": "redaction",
"OAUTH_IDP_HINT": null, "OAUTH_IDP_HINT": null,
"OAUTH_URL": "https://dan2.iqser.cloud/auth", "OAUTH_URL": "https://frontend2.iqser.cloud/auth",
"RECENT_PERIOD_IN_HOURS": 24, "RECENT_PERIOD_IN_HOURS": 24,
"SELECTION_MODE": "structural", "SELECTION_MODE": "structural",
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
"ANNOTATIONS_THRESHOLD": 1000, "ANNOTATIONS_THRESHOLD": 1000,
"THEME": "redact", "THEME": "scm",
"BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/", "BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/",
"AVAILABLE_NOTIFICATIONS_DAYS": 30, "AVAILABLE_NOTIFICATIONS_DAYS": 30,
"AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60, "AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60,
"NOTIFICATIONS_THRESHOLD": 1000, "NOTIFICATIONS_THRESHOLD": 1000,