266 lines
9.7 KiB
TypeScript
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;
|
|
});
|
|
}
|
|
}
|