diff --git a/apps/red-ui/src/app/models/file/list-item.ts b/apps/red-ui/src/app/models/file/list-item.ts new file mode 100644 index 000000000..71ba19148 --- /dev/null +++ b/apps/red-ui/src/app/models/file/list-item.ts @@ -0,0 +1,5 @@ +export interface ListItem { + item: T; + isSelected: boolean; + multiSelectActive: boolean; +} diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html index 317fe83b7..c5dbee02b 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.html @@ -1,5 +1,5 @@
- +
diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts index 57777f99c..183a6dd71 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-details/annotation-details.component.ts @@ -3,10 +3,9 @@ import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { TranslateService } from '@ngx-translate/core'; import { annotationChangesTranslations } from '@translations/annotation-changes-translations'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { MultiSelectService } from '../../services/multi-select.service'; -import { KeysOf, shareDistinctLast } from '@iqser/common-ui'; -import { BehaviorSubject, combineLatest, filter, map, Observable, switchMap } from 'rxjs'; +import { KeysOf } from '@iqser/common-ui'; import { AnnotationsListingService } from '../../services/annotations-listing.service'; +import { ListItem } from '@models/file/list-item'; interface Engine { readonly icon: string; @@ -42,52 +41,31 @@ const changesProperties: KeysOf[] = [ styleUrls: ['./annotation-details.component.scss'], }) export class AnnotationDetailsComponent implements OnChanges { - @Input() annotation: AnnotationWrapper; - readonly noSelection$: Observable; + @Input() annotation: ListItem; isPopoverOpen = false; - readonly engines$: Observable; - readonly changesTooltip$: Observable; - readonly #annotationChanged$ = new BehaviorSubject(undefined); + engines: Engine[]; + changesTooltip: string; + noSelection: boolean; - constructor( - private readonly _translateService: TranslateService, - private readonly _listingService: AnnotationsListingService, - readonly multiSelectService: MultiSelectService, - ) { - const isSelected$ = this.#annotationChanged$.pipe(switchMap(annotation => this._listingService.isSelected$(annotation))); - this.noSelection$ = combineLatest([isSelected$, multiSelectService.inactive$]).pipe( - map(([isSelected, inactive]) => !isSelected || inactive), - shareDistinctLast(), - ); - this.engines$ = this.#engines$; - this.changesTooltip$ = this.#changesTooltip; - } + constructor(private readonly _translateService: TranslateService, private readonly _listingService: AnnotationsListingService) {} - get #engines$(): Observable { - return this.#annotationChanged$.pipe( - filter(annotation => !!annotation), - map(annotation => this.#extractEngines(annotation).filter(engine => engine.show)), - ); - } + getChangesTooltip(): string | undefined { + const changes = changesProperties.filter(key => this.annotation.item[key]); - get #changesTooltip(): Observable { - return this.#annotationChanged$.pipe( - filter(annotation => !!annotation), - map(annotation => changesProperties.filter(key => annotation[key])), - map(changes => { - if (!changes.length) { - return; - } - const header = this._translateService.instant(_('annotation-changes.header')); - const details = changes.map(change => this._translateService.instant(annotationChangesTranslations[change])); - return [header, ...details.map(change => `• ${change}`)].join('\n'); - }), - ); + if (!changes.length) { + return; + } + + const header = this._translateService.instant(_('annotation-changes.header')); + const details = changes.map(change => this._translateService.instant(annotationChangesTranslations[change])); + return [header, ...details.map(change => `• ${change}`)].join('\n'); } ngOnChanges() { - this.#annotationChanged$.next(this.annotation); + this.engines = this.#extractEngines(this.annotation.item).filter(engine => engine.show); + this.changesTooltip = this.getChangesTooltip(); + this.noSelection = !this.annotation.isSelected || !this.annotation.multiSelectActive; } #extractEngines(annotation: AnnotationWrapper): Engine[] { diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.html index b4c5a02da..994434dd0 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.html @@ -1,37 +1,37 @@
-
+
-
+
- {{ annotation.comments.length }} + {{ annotation.item.comments.length }}
-
+
- +
diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.ts b/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.ts index f6f52c154..1fd6e3c88 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/annotation-wrapper/annotation-wrapper.component.ts @@ -1,12 +1,10 @@ import { Component, HostBinding, Input, OnChanges } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { distinctUntilChanged, switchMap, tap } from 'rxjs/operators'; import { AnnotationWrapper } from '@models/file/annotation.wrapper'; -import { MultiSelectService } from '../../services/multi-select.service'; import { AnnotationsListingService } from '../../services/annotations-listing.service'; import { PdfProxyService } from '../../services/pdf-proxy.service'; import { ScrollableParentViews } from '@iqser/common-ui'; import { ActionsHelpModeKeys } from '../../utils/constants'; +import { ListItem } from '@models/file/list-item'; @Component({ selector: 'redaction-annotation-wrapper [annotation]', @@ -14,29 +12,17 @@ import { ActionsHelpModeKeys } from '../../utils/constants'; styleUrls: ['./annotation-wrapper.component.scss'], }) export class AnnotationWrapperComponent implements OnChanges { - @Input() annotation!: AnnotationWrapper; + @Input() annotation!: ListItem; - readonly isSelected$!: Observable; @HostBinding('attr.annotation-id') annotationId: string; @HostBinding('class.active') active = false; readonly scrollableParentView = ScrollableParentViews.ANNOTATIONS_LIST; - readonly #annotationChanged$ = new BehaviorSubject(undefined); - constructor( - readonly listingService: AnnotationsListingService, - readonly multiSelectService: MultiSelectService, - readonly pdfProxyService: PdfProxyService, - ) { - this.isSelected$ = this.#annotationChanged$.pipe( - switchMap(entity => this.listingService.isSelected$(entity)), - distinctUntilChanged(), - tap(isSelected => (this.active = isSelected)), - ); - } + constructor(readonly listingService: AnnotationsListingService, readonly pdfProxyService: PdfProxyService) {} ngOnChanges() { - this.#annotationChanged$.next(this.annotation); - this.annotationId = this.annotation.id; + this.annotationId = this.annotation.item.id; + this.active = this.annotation.isSelected; } getActionsHelpModeKey(annotation: AnnotationWrapper): string { diff --git a/apps/red-ui/src/app/modules/file-preview/components/annotations-list/annotations-list.component.html b/apps/red-ui/src/app/modules/file-preview/components/annotations-list/annotations-list.component.html index 7637759e9..ac61e425b 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/annotations-list/annotations-list.component.html +++ b/apps/red-ui/src/app/modules/file-preview/components/annotations-list/annotations-list.component.html @@ -1,9 +1,12 @@ - +
- +
- +
[]; @Output() readonly pagesPanelActive = new EventEmitter(); readonly earmarkGroups$ = new BehaviorSubject([]); + protected readonly _trackBy = (index: number, listItem: ListItem) => listItem.item.id; constructor( protected readonly _elementRef: ElementRef, @@ -87,11 +89,11 @@ export class AnnotationsListComponent extends HasScrollbarDirective implements O const earmarksGroups: EarmarkGroup[] = []; let lastGroup: EarmarkGroup; for (let idx = 0; idx < this.annotations.length; ++idx) { - if (idx === 0 || this.annotations[idx].color !== this.annotations[idx - 1].color) { + if (idx === 0 || this.annotations[idx].item.color !== this.annotations[idx - 1].item.color) { if (lastGroup) { earmarksGroups.push(lastGroup); } - lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].color }; + lastGroup = { startIdx: idx, length: 1, color: this.annotations[idx].item.color }; } else { lastGroup.length += 1; } diff --git a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts index 595a6cbc2..682952d93 100644 --- a/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts +++ b/apps/red-ui/src/app/modules/file-preview/components/file-workload/file-workload.component.ts @@ -31,6 +31,7 @@ import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-ma import { AnnotationsListingService } from '../../services/annotations-listing.service'; import { REDDocumentViewer } from '../../../pdf-viewer/services/document-viewer.service'; import { SuggestionsService } from '../../services/suggestions.service'; +import { ListItem } from '@models/file/list-item'; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; @@ -49,7 +50,7 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy @Input() file!: File; displayedPages: number[] = []; pagesPanelActive = true; - readonly displayedAnnotations$: Observable>; + readonly displayedAnnotations$: Observable[]>>; readonly multiSelectInactive$: Observable; readonly showExcludedPages$: Observable; readonly title$: Observable; @@ -95,8 +96,8 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy this.handleKeyEvent($event); }); - this.displayedAnnotations$ = this._displayedAnnotations$; this.multiSelectInactive$ = this._multiSelectInactive$; + this.displayedAnnotations$ = this._displayedAnnotations$; this.showExcludedPages$ = this._showExcludedPages$; this.isEarmarks$ = this._isEarmarks$; this.title$ = this._title$; @@ -149,13 +150,21 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy return this.listingService.selected.length ? this.listingService.selected[0] : null; } - private get _displayedAnnotations$(): Observable> { + private get _displayedAnnotations$(): Observable[]>> { const primary$ = this.filterService.getFilterModels$('primaryFilters'); const secondary$ = this.filterService.getFilterModels$('secondaryFilters'); - return combineLatest([this.fileDataService.all$, primary$, secondary$]).pipe( + return combineLatest([ + this.fileDataService.all$, + primary$, + secondary$, + this.listingService.selected$, + this.multiSelectService.active$, + ]).pipe( delay(0), map(([annotations, primary, secondary]) => this._filterAnnotations(annotations, primary, secondary)), + map(annotations => this._mapListItemsFromAnnotationWrapperArray(annotations)), + tap(() => setTimeout(() => this._changeDetectorRef.detectChanges())), ); } @@ -457,4 +466,20 @@ export class FileWorkloadComponent extends AutoUnsubscribe implements OnDestroy FileWorkloadComponent._scrollToFirstElement(elements); } } + + private _mapListItemsFromAnnotationWrapperArray(annotations: Map) { + const listItemsMap = new Map[]>(); + if (!annotations) { + return listItemsMap; + } + [...annotations.keys()].forEach(key => { + const newValue = annotations.get(key).map(annotation => ({ + item: annotation, + isSelected: this.listingService.isSelected(annotation), + multiSelectActive: this.multiSelectService.isActive, + })); + listItemsMap.set(key, newValue); + }); + return listItemsMap; + } }