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:
commit
4168f1b687
5
apps/red-ui/src/app/models/file/list-item.ts
Normal file
5
apps/red-ui/src/app/models/file/list-item.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface ListItem<T> {
|
||||
item: T;
|
||||
isSelected: boolean;
|
||||
multiSelectActive: boolean;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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[] {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user