Pull request #408: RED-6412 misc fixes.

Merge in RED/ui from RED-6412-remove-annotation-subscriptions to master

* commit '2d2ba49242bd3533536d0d75d4a2ef46054a6f3a':
  RED-6412, fix document annotation select being delayed.
  RED-6412, remove unused imports, add trackby to stop flickering.
  RED-6412, add detech changes, simplify methods, remove additional pipes.
  chore(release)
  RED-6524: fix file preview close button
  chore(release)
  RED-6176: Fixed save button disabled.
  RED-6412, some more refactoring.
  RED-6412, remove subscriptions from annotation details component.
This commit is contained in:
George Balanesc 2023-03-30 18:44:38 +02:00 committed by Dan Percic
commit 4168f1b687
8 changed files with 82 additions and 83 deletions

View File

@ -0,0 +1,5 @@
export interface ListItem<T> {
item: T;
isSelected: boolean;
multiSelectActive: boolean;
}

View File

@ -1,5 +1,5 @@
<div
*ngIf="(noSelection$ | async) && changesTooltip$ | async as changesTooltip"
*ngIf="noSelection && changesTooltip"
[matTooltip]="changesTooltip"
class="chip"
matTooltipClass="multiline"
@ -8,7 +8,7 @@
<mat-icon [svgIcon]="'red:redaction-changes'"></mat-icon>
</div>
<ng-container *ngIf="(noSelection$ | async) && engines$ | async as engines">
<ng-container *ngIf="noSelection && engines">
<div #trigger="cdkOverlayOrigin" (mouseout)="isPopoverOpen = false" (mouseover)="isPopoverOpen = true" cdkOverlayOrigin class="chip">
<mat-icon *ngFor="let engine of engines" [svgIcon]="engine.icon"></mat-icon>
</div>

View File

@ -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<AnnotationWrapper>[] = [
styleUrls: ['./annotation-details.component.scss'],
})
export class AnnotationDetailsComponent implements OnChanges {
@Input() annotation: AnnotationWrapper;
readonly noSelection$: Observable<boolean>;
@Input() annotation: ListItem<AnnotationWrapper>;
isPopoverOpen = false;
readonly engines$: Observable<Engine[]>;
readonly changesTooltip$: Observable<string>;
readonly #annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(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<Engine[]> {
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<string | undefined> {
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[] {

View File

@ -1,37 +1,37 @@
<div class="active-bar-marker"></div>
<div [class.removed]="annotation.isChangeLogRemoved" class="annotation">
<div [class.removed]="annotation.item.isChangeLogRemoved" class="annotation">
<redaction-annotation-card
[annotation]="annotation"
[isSelected]="isSelected$ | async"
[matTooltip]="annotation.content"
[annotation]="annotation.item"
[isSelected]="annotation.isSelected"
[matTooltip]="annotation.item.content"
matTooltipPosition="above"
></redaction-annotation-card>
<div *ngIf="!annotation.isEarmark" class="actions-wrapper">
<div *ngIf="!annotation.item.isEarmark" class="actions-wrapper">
<div
(click)="comments.toggleExpandComments()"
[matTooltip]="'comments.comments' | translate : { count: annotation.comments?.length }"
[matTooltip]="'comments.comments' | translate : { count: annotation.item.comments?.length }"
class="comments-counter"
matTooltipPosition="above"
stopPropagation
>
<mat-icon svgIcon="red:comment"></mat-icon>
{{ annotation.comments.length }}
{{ annotation.item.comments.length }}
</div>
<div *ngIf="multiSelectService.inactive$ | async" class="actions">
<div *ngIf="!annotation.multiSelectActive" class="actions">
<redaction-annotation-actions
[annotations]="[annotation]"
[annotations]="[annotation.item]"
[canPerformAnnotationActions]="pdfProxyService.canPerformAnnotationActions$ | async"
[iqserHelpMode]="getActionsHelpModeKey(annotation)"
[iqserHelpMode]="getActionsHelpModeKey(annotation.item)"
[overlappingElements]="['USER_MENU', 'WORKLOAD_FILTER', 'DOCUMENT_INFO']"
[scrollableParentView]="scrollableParentView"
></redaction-annotation-actions>
</div>
</div>
<redaction-comments #comments [annotation]="annotation"></redaction-comments>
<redaction-comments #comments [annotation]="annotation.item"></redaction-comments>
</div>
<redaction-annotation-details [annotation]="annotation"></redaction-annotation-details>

View File

@ -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<AnnotationWrapper>;
readonly isSelected$!: Observable<boolean>;
@HostBinding('attr.annotation-id') annotationId: string;
@HostBinding('class.active') active = false;
readonly scrollableParentView = ScrollableParentViews.ANNOTATIONS_LIST;
readonly #annotationChanged$ = new BehaviorSubject<AnnotationWrapper>(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 {

View File

@ -1,9 +1,12 @@
<ng-container *ngFor="let annotation of annotations; let idx = index">
<ng-container *ngFor="let annotation of annotations; let idx = index; trackBy: _trackBy">
<div *ngIf="showHighlightGroup(idx) as highlightGroup" class="workload-separator">
<redaction-highlights-separator [annotation]="annotation" [highlightGroup]="highlightGroup"></redaction-highlights-separator>
<redaction-highlights-separator [annotation]="annotation.item" [highlightGroup]="highlightGroup"></redaction-highlights-separator>
</div>
<redaction-annotation-wrapper (click)="annotationClicked(annotation, $event)" [annotation]="annotation"></redaction-annotation-wrapper>
<redaction-annotation-wrapper
(click)="annotationClicked(annotation.item, $event)"
[annotation]="annotation"
></redaction-annotation-wrapper>
</ng-container>
<redaction-annotation-references-list

View File

@ -9,6 +9,7 @@ import { BehaviorSubject } from 'rxjs';
import { EarmarkGroup } from '@red/domain';
import { REDAnnotationManager } from '../../../pdf-viewer/services/annotation-manager.service';
import { AnnotationsListingService } from '../../services/annotations-listing.service';
import { ListItem } from '@models/file/list-item';
@Component({
selector: 'redaction-annotations-list',
@ -16,11 +17,12 @@ import { AnnotationsListingService } from '../../services/annotations-listing.se
styleUrls: ['./annotations-list.component.scss'],
})
export class AnnotationsListComponent extends HasScrollbarDirective implements OnChanges {
@Input() annotations: AnnotationWrapper[];
@Input() annotations: ListItem<AnnotationWrapper>[];
@Output() readonly pagesPanelActive = new EventEmitter<boolean>();
readonly earmarkGroups$ = new BehaviorSubject<EarmarkGroup[]>([]);
protected readonly _trackBy = (index: number, listItem: ListItem<AnnotationWrapper>) => 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;
}

View File

@ -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<Map<number, AnnotationWrapper[]>>;
readonly displayedAnnotations$: Observable<Map<number, ListItem<AnnotationWrapper>[]>>;
readonly multiSelectInactive$: Observable<boolean>;
readonly showExcludedPages$: Observable<boolean>;
readonly title$: Observable<string>;
@ -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<Map<number, AnnotationWrapper[]>> {
private get _displayedAnnotations$(): Observable<Map<number, ListItem<AnnotationWrapper>[]>> {
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<number, AnnotationWrapper[]>) {
const listItemsMap = new Map<number, ListItem<AnnotationWrapper>[]>();
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;
}
}