red-ui/apps/red-ui/src/app/modules/file-preview/services/annotation-processing.service.ts
2023-10-31 10:25:53 +02:00

266 lines
9.7 KiB
TypeScript

import { Injectable } from '@angular/core';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { getConfig } from '@iqser/common-ui';
import { Filter, handleCheckedValue, IFilter, INestedFilter, NestedFilter } from '@iqser/common-ui/lib/filtering';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { annotationDefaultColorConfig } from '@red/domain';
import { DefaultColorsService } from '@services/entity-services/default-colors.service';
import { ViewedPagesMapService } from '@services/files/viewed-pages-map.service';
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
import { UserPreferenceService } from '@users/user-preference.service';
import { of } from 'rxjs';
import { SuperTypeSorter } from '../../../utils';
import { PageRotationService } from '../../pdf-viewer/services/page-rotation.service';
import {
sortBottomLeftToTopRight,
sortBottomRightToTopLeft,
sortTopLeftToBottomRight,
sortTopRightToBottomLeft,
} from '../utils/sort-by-page-rotation.utils';
import { FileDataService } from './file-data.service';
import { FilePreviewStateService } from './file-preview-state.service';
@Injectable()
export class AnnotationProcessingService {
readonly #isDocumine = getConfig().IS_DOCUMINE;
readonly #devMode = this._userPreferenceService.isIqserDevMode;
constructor(
private readonly _viewedPagesMapService: ViewedPagesMapService,
private readonly _fileDataService: FileDataService,
private readonly _state: FilePreviewStateService,
private readonly _defaultColorsService: DefaultColorsService,
private readonly _pageRotationService: PageRotationService,
private readonly _userPreferenceService: UserPreferenceService,
) {}
get secondaryAnnotationFilters(): INestedFilter[] {
return [
{
id: 'with-comments',
icon: 'red:comment',
label: _('filter-menu.with-comments'),
checked: false,
topLevelFilter: true,
checker: (annotation: AnnotationWrapper) => annotation?.numberOfComments > 0,
},
{
id: 'redaction-changes',
icon: 'red:redaction-changes',
label: _('filter-menu.redaction-changes'),
checked: false,
topLevelFilter: true,
checker: (annotation: AnnotationWrapper) => annotation?.hasRedactionChanges,
},
{
id: 'unseen-pages',
icon: 'iqser:pages',
label: _('filter-menu.unseen-pages'),
checked: false,
topLevelFilter: true,
checker: (annotation: AnnotationWrapper) =>
!this._viewedPagesMapService.get(this._state.fileId).some(page => page.page === annotation.pageNumber),
},
{
id: 'pages-without-annotations',
icon: 'iqser:pages',
label: _('filter-menu.pages-without-annotations'),
checked: false,
topLevelFilter: true,
checker: () => true,
},
].map(item => new NestedFilter(item));
}
getAnnotationFilter(): INestedFilter[] {
const filterMap = new Map<string, INestedFilter>();
const filters: INestedFilter[] = [];
this._fileDataService.all?.forEach(a => {
if (this.#isDocumine && !this.#devMode && a.isOCR) {
return;
}
const topLevelFilter = a.topLevelFilter;
const filter = filterMap.get(a.filterKey);
if (filter) {
filter.matches += 1;
} else {
// top level filter
if (topLevelFilter) {
this._createParentFilter(a.isEarmark ? a.filterKey : a.superType, filterMap, filters, a.isEarmark, {
color$: of(a.color),
shortLabel: a.isEarmark ? '' : null,
shape: a.iconShape,
});
} else {
let parentFilter = filterMap.get(a.superType);
if (!parentFilter) {
parentFilter = this._createParentFilter(a.superType, filterMap, filters, false, {
shape: a.iconShape,
color$: this._defaultColorsService.getColor$(
this._state.dossierTemplateId,
annotationDefaultColorConfig[a.superType],
),
});
}
const childFilter: IFilter = {
id: a.filterKey,
label: a.typeLabel,
checked: false,
matches: 1,
metadata: {
color$: of(a.color),
shape: a.iconShape,
},
skipTranslation: true,
};
const newChildFilter = new Filter(childFilter);
filterMap.set(a.filterKey, newChildFilter);
parentFilter.children.push(newChildFilter);
}
}
});
for (const filter of filters) {
filter.children.sort((a, b) => a.id.localeCompare(b.id));
handleCheckedValue(filter);
if (filter.checked || filter.indeterminate) {
filter.expanded = true;
}
if (filter.children.length > 0) {
filter.matches = filter.children.reduce((a, b) => a + b.matches, 0);
}
}
return filters.sort((a, b) => SuperTypeSorter[a.id] - SuperTypeSorter[b.id]);
}
filterAndGroupAnnotations(
annotations: AnnotationWrapper[],
primaryFilters: INestedFilter[],
secondaryFilters?: INestedFilter[],
): Map<number, AnnotationWrapper[]> {
const obj = new Map<number, AnnotationWrapper[]>();
const primaryFlatFilters = this._getFlatFilters(primaryFilters, f => f.checked);
const secondaryFlatFilters = this._getFlatFilters(secondaryFilters, f => f.checked);
for (const annotation of annotations) {
if (!this._matchesOne(primaryFlatFilters, f => this._checkByFilterKey(f, annotation))) {
continue;
}
if (
!this._matchesAll(
secondaryFlatFilters,
f => (!!f.checker && f.checker(annotation)) || this._checkByFilterKey(f, annotation),
)
) {
continue;
}
const pageNumber = annotation.pageNumber;
if (!obj.has(pageNumber)) {
obj.set(pageNumber, []);
}
obj.get(pageNumber).push(annotation);
}
obj.forEach((values, page) => {
if (!values[0].isEarmark) {
obj.set(page, this.#sortAnnotations(values));
}
});
return obj;
}
private _createParentFilter(
key: string,
filterMap: Map<string, INestedFilter>,
filters: INestedFilter[],
skipTranslation = false,
metadata?: Record<string, any>,
) {
const filter: INestedFilter = new NestedFilter({
id: key,
topLevelFilter: true,
matches: 1,
label: skipTranslation ? key : annotationTypesTranslations[key],
skipTranslation,
metadata,
});
filterMap.set(key, filter);
filters.push(filter);
return filter;
}
private _getFlatFilters(filters: INestedFilter[], filterBy?: (f: INestedFilter) => boolean) {
const flatFilters: INestedFilter[] = [];
filters.forEach(filter => {
flatFilters.push(filter);
flatFilters.push(...filter.children);
});
return filterBy ? flatFilters.filter(f => filterBy(f)) : flatFilters;
}
private _matchesOne = (filters: INestedFilter[], condition: (filter: INestedFilter) => boolean): boolean => {
if (filters.length === 0) {
return true;
}
for (const filter of filters) {
if (condition(filter)) {
return true;
}
}
return false;
};
private _matchesAll = (filters: INestedFilter[], condition: (filter: INestedFilter) => boolean): boolean => {
if (filters.length === 0) {
return true;
}
for (const filter of filters) {
if (!condition(filter)) {
return false;
}
}
return true;
};
private _checkByFilterKey = (filter: NestedFilter | IFilter, annotation: AnnotationWrapper) => filter.id === annotation.filterKey;
#sortAnnotations(annotations: AnnotationWrapper[]): AnnotationWrapper[] {
const pageRotation = this._pageRotationService.currentPageRotation;
return annotations.sort((first, second) => {
if (first.pageNumber === second.pageNumber) {
switch (pageRotation) {
case 0: {
return sortTopLeftToBottomRight(first, second);
}
case 90: {
return sortBottomLeftToTopRight(first, second);
}
case 180: {
return sortBottomRightToTopLeft(first, second);
}
case 270: {
return sortTopRightToBottomLeft(first, second);
}
}
}
return first.pageNumber < second.pageNumber ? -1 : 1;
});
}
}