annotation counts added

This commit is contained in:
Diamantis Tziotzios 2024-03-27 20:07:49 +02:00
parent 4d50aefacd
commit 04efb9c0b1
10 changed files with 108 additions and 342 deletions

View File

@ -24,13 +24,13 @@ export class CoreAnnotationServiceModule {
return { return {
ngModule: CoreAnnotationServiceModule, ngModule: CoreAnnotationServiceModule,
providers: [ providers: [
BaseHttpV2Service, BaseHttpV2Service,
HttpErrorHandlingService, HttpErrorHandlingService,
FilterService, FilterService,
FormService, FormService,
LoggingService, LoggingService,
PrincipalService, PrincipalService,
AnnotationService AnnotationService
], ],
}; };
} }

View File

@ -80,16 +80,6 @@ const appRoutes: Routes = [
title: 'GENERAL.TITLES.ABOUT' title: 'GENERAL.TITLES.ABOUT'
} }
}, },
// ----------- UNCOMMENT TO ADD AGAIN GRANTS --------
// {
// path: 'grants',
// loadChildren: () => import('./ui/grant/grant.module').then(m => m.GrantModule),
// data: {
// breadcrumb: true,
// title: 'GENERAL.TITLES.GRANTS'
// }
// },
{ {
path: 'description-templates', path: 'description-templates',

View File

@ -1,4 +1,3 @@
import { HttpClient } from '@angular/common/http';
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { FormBuilder, FormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
@ -31,6 +30,8 @@ export class AnnotationDialogComponent extends BaseComponent {
private anchor: string; private anchor: string;
private entityType: string; private entityType: string;
private changesMade: boolean = false;
public comments = new Array<Annotation>(); public comments = new Array<Annotation>();
public threads = new Set<Guid>(); public threads = new Set<Guid>();
public annotationsPerThread = {}; public annotationsPerThread = {};
@ -49,12 +50,13 @@ export class AnnotationDialogComponent extends BaseComponent {
private language: TranslateService, private language: TranslateService,
private annotationService: AnnotationService, private annotationService: AnnotationService,
private formService: FormService, private formService: FormService,
private enumUtils: EnumUtils private enumUtils: EnumUtils,
) { ) {
super(); super();
this.entityId = data.entityId; this.entityId = data.entityId;
this.anchor = data.anchor; this.anchor = data.anchor;
this.entityType = data.entityType; this.entityType = data.entityType;
dialogRef.beforeClosed().pipe(takeUntil(this._destroyed)).subscribe(() => dialogRef.close(this.changesMade));
} }
ngOnInit(): void { ngOnInit(): void {
@ -199,8 +201,7 @@ export class AnnotationDialogComponent extends BaseComponent {
private onCallbackSuccess() { private onCallbackSuccess() {
this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.SUCCESS'), SnackBarNotificationLevel.Success); this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.SUCCESS'), SnackBarNotificationLevel.Success);
this.refreshAnnotations(); this.refreshAnnotations();
// this.router.navigate(['/reload']).then(() => this.router.navigate(['/plans'])); this.changesMade = true;
// this.router.navigate(['/reload']).then(() => this.isPublic ? this.router.navigate(['/explore-plans']) : this.router.navigate(['/plans']));
} }
private onCallbackError(error: any) { private onCallbackError(error: any) {
@ -214,15 +215,11 @@ export class AnnotationDialogComponent extends BaseComponent {
cancel() { cancel() {
this.dialogRef.close(); this.dialogRef.close(this.changesMade);
}
send() {
this.dialogRef.close(this.data);
} }
close() { close() {
this.dialogRef.close(false); this.dialogRef.close(this.changesMade);
} }
startWizard() { startWizard() {

View File

@ -6,7 +6,7 @@
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(fieldSet.id)"> <button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(fieldSet.id)">
<mat-icon matTooltip="{{'DATASET-EDITOR.QUESTION.EXTENDED-DESCRIPTION.ANNOTATIONS' | translate}}">comment</mat-icon> <mat-icon matTooltip="{{'DATASET-EDITOR.QUESTION.EXTENDED-DESCRIPTION.ANNOTATIONS' | translate}}" [matBadge]="descriptionFormAnnotationService.getCount(fieldSet.id)" [matBadgeHidden]="descriptionFormAnnotationService.getCount(fieldSet.id) <= 0" matBadgeColor="warn">comment</mat-icon>
</button> </button>
</div> </div>
</div> </div>

View File

@ -13,6 +13,7 @@ import { ValidationErrorModel } from '@common/forms/validation/error-model/valid
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { AnnotationDialogComponent } from '@app/ui/annotations/annotation-dialog-component/annotation-dialog.component'; import { AnnotationDialogComponent } from '@app/ui/annotations/annotation-dialog-component/annotation-dialog.component';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type'; import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
import { DescriptionFormAnnotationService } from '../../description-form-annotation.service';
@Component({ @Component({
selector: 'app-description-form-field-set', selector: 'app-description-form-field-set',
@ -52,7 +53,8 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
constructor( constructor(
private dialog: MatDialog, private dialog: MatDialog,
private changeDetector: ChangeDetectorRef private changeDetector: ChangeDetectorRef,
private descriptionFormAnnotationService: DescriptionFormAnnotationService
) { ) {
super(); super();
} }
@ -79,7 +81,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
} }
const item: DescriptionPropertyDefinitionFieldSetEditorModel = new DescriptionPropertyDefinitionEditorModel(this.validationErrorModel).calculateFieldSetProperties(this.fieldSet, null, null); const item: DescriptionPropertyDefinitionFieldSetEditorModel = new DescriptionPropertyDefinitionEditorModel(this.validationErrorModel).calculateFieldSetProperties(this.fieldSet, null, null);
formArray.push((item.buildForm({rootPath: `properties.fieldSets[${this.fieldSet.id}].`}).get('items') as UntypedFormArray).at(0)); formArray.push((item.buildForm({ rootPath: `properties.fieldSets[${this.fieldSet.id}].` }).get('items') as UntypedFormArray).at(0));
} }
@ -134,50 +136,12 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
entityType: AnnotationEntityType.Description entityType: AnnotationEntityType.Description
} }
}); });
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => {
if (result && result.success) { if (changesMade) {
//TODO refactor this.descriptionFormAnnotationService.refreshAnnotations(() => {
this.changeDetector.markForCheck();
});
} }
}); });
} }
// deleteCompositeFieldFormGroup() {
// const compositeFieldId = ((this.form.get('multiplicityItems') as UntypedFormArray).get('' + 0) as UntypedFormGroup).getRawValue().id;
// const fieldIds = (this.form.get('fields') as UntypedFormArray).controls.map(control => control.get('id').value) as string[];
// const numberOfItems = this.form.get('multiplicityItems').get('' + 0).get('fields').value.length;
// for (let i = 0; i < numberOfItems; i++) {
// const multiplicityItem = this.form.get('multiplicityItems').get('' + 0).get('fields').get('' + i).value;
// this.form.get('fields').get('' + i).patchValue(multiplicityItem);
// }
// (<UntypedFormArray>(this.form.get('multiplicityItems'))).removeAt(0);
// this.visibilityRulesService.removeAllIdReferences(compositeFieldId);
// fieldIds.forEach(x => this.visibilityRulesService.removeAllIdReferences(x));
// }
// deleteMultipeFieldFromCompositeFormGroup() {
// const parent = this.form.parent;
// const index = (parent as UntypedFormArray).controls.indexOf(this.form);
// const currentId = this.form.get('id').value;
// const fieldIds = (this.form.get('fields') as UntypedFormArray).controls.map(control => control.get('id').value) as string[];
// this.visibilityRulesService.removeAllIdReferences(currentId);
// fieldIds.forEach(x => this.visibilityRulesService.removeAllIdReferences(x));
// (parent as UntypedFormArray).removeAt(index);
// (parent as UntypedFormArray).controls.forEach((control, i) => {
// try {
// control.get('ordinal').setValue(i);
// } catch {
// throw 'Could not find ordinal';
// }
// });
// }
} }

View File

@ -34,6 +34,7 @@ export class DescriptionFormSectionComponent extends BaseComponent implements On
@Input() path: string; @Input() path: string;
@Input() descriptionId: Guid; @Input() descriptionId: Guid;
// @Input() datasetProfileId: String; // @Input() datasetProfileId: String;
// @Input() form: UntypedFormGroup; // @Input() form: UntypedFormGroup;
@Input() tocentry: ToCEntry; @Input() tocentry: ToCEntry;

View File

@ -0,0 +1,75 @@
import { Injectable } from '@angular/core';
import { Annotation } from '@annotation-service/core/model/annotation.model';
import { AnnotationLookup } from '@annotation-service/core/query/annotation.lookup';
import { AnnotationService } from '@annotation-service/services/http/annotation.service';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { BaseService } from '@common/base/base.service';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import { takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@Injectable({
providedIn: 'any',
})
export class DescriptionFormAnnotationService extends BaseService {
private entityId: Guid;
private annotationsPerAnchor: Map<string, number>;
constructor(
private annotationService: AnnotationService,
private uiNotificationService: UiNotificationService,
private language: TranslateService
) {
super();
}
init(entityId: Guid) {
this.entityId = entityId;
this.refreshAnnotations();
}
public getCount(anchor: string) {
if (this.annotationsPerAnchor.has(anchor)) {
return this.annotationsPerAnchor.get(anchor);
} else {
return 0;
}
}
public refreshAnnotations(onSuccess?: () => void) {
const lookup: AnnotationLookup = new AnnotationLookup();
lookup.entityIds = [this.entityId];
lookup.entityTypes = [AnnotationEntityType.Description];
lookup.project = {
fields: [
nameof<Annotation>(x => x.id),
nameof<Annotation>(x => x.anchor),
]
};
this.annotationService.query(lookup)
.pipe(takeUntil(this._destroyed))
.subscribe(
data => {
this.annotationsPerAnchor = new Map();
for (const item of data.items) {
if (!this.annotationsPerAnchor.has(item.anchor)) {
this.annotationsPerAnchor.set(item.anchor, 0);
}
this.annotationsPerAnchor.set(item.anchor, this.annotationsPerAnchor.get(item.anchor) + 1);
}
onSuccess ? onSuccess() : null;
},
error => this.onCallbackError(error),
);
}
private onCallbackError(error: any) {
this.uiNotificationService.snackBarNotification(this.language.instant(error.message), SnackBarNotificationLevel.Error);
}
}

View File

@ -1,13 +1,13 @@
import { AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { UntypedFormGroup } from '@angular/forms'; import { UntypedFormGroup } from '@angular/forms';
import { MatExpansionPanel } from '@angular/material/expansion'; import { MatExpansionPanel } from '@angular/material/expansion';
import { DescriptionTemplate, DescriptionTemplateSection } from '@app/core/model/description-template/description-template'; import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { BaseComponent } from '@common/base/base.component'; import { BaseComponent } from '@common/base/base.component';
import { LinkToScroll } from '../table-of-contents/table-of-contents.component';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model'; import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { Guid } from '@common/types/guid'; import { Guid } from '@common/types/guid';
import { RuleWithTarget } from './visibility-rules/models/rule'; import { LinkToScroll } from '../table-of-contents/table-of-contents.component';
import { DescriptionFormAnnotationService } from './description-form-annotation.service';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
@Component({ @Component({
selector: 'app-description-form', selector: 'app-description-form',
@ -22,6 +22,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, A
@Input() descriptionId: Guid; @Input() descriptionId: Guid;
// @ViewChild('stepper', { static: false }) stepper: MatStepper; // @ViewChild('stepper', { static: false }) stepper: MatStepper;
@Input() path: string; @Input() path: string;
@Input() datasetDescription: String; @Input() datasetDescription: String;
@ -38,6 +39,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, A
// public hiddenEntriesIds: string[] = []; // public hiddenEntriesIds: string[] = [];
constructor( constructor(
public descriptionFormAnnotationService: DescriptionFormAnnotationService,
) { ) {
super(); super();
@ -57,6 +59,11 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, A
// this.stepper.selectedIndex = changes['linkToScroll'].currentValue.page; // this.stepper.selectedIndex = changes['linkToScroll'].currentValue.page;
// } // }
// } // }
if (this.descriptionId != null) {
this.descriptionFormAnnotationService.init(this.descriptionId);
}
} }
ngAfterViewInit() { ngAfterViewInit() {
@ -99,273 +106,4 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, A
// this.fieldsetFocusChange.emit(id); // this.fieldsetFocusChange.emit(id);
} }
// private _enrichWithMultiplicityRules(tocentries: ToCEntry[]): Rule[] {
// if (tocentries) {
// return tocentries.map(entry => {
// if (entry.type === ToCEntryType.Field) return []; // * TODO Me to tora implementation den tha ftasei pote edo
// if (entry.type === ToCEntryType.FieldSet) {
// // if(multiplicity: )
// try {
// // TODO OTAN KANW HIDE MULTIPLE PEDIO TOTE STO ON SHOW HANO TA VALUES (AUTO MPOREI NA EINAI KAI LEGIT) ('NA DOUME AN ONTOS DIAGRAFONTAI I APLA DEN TA DEIXNOUME')
// // * UPDATE KANEI DESTROY TO COMPONENT H NGIF . PITHANOTATA NA XREIASTEI NA TO KANOUME HIDDEN AN THELOUME KATI ALLO
// const multiplicity = entry.form.get('multiplicity').value;
// if ((multiplicity.max > 1) && (multiplicity.min > 0) && (multiplicity.max >= multiplicity.min)) { // has valid multiplicity
// return this._createAndAppendVisibilityRule(entry);
// }
// } catch {
// }
// return [];
// }
// if (entry.subEntries) {
// return this._enrichWithMultiplicityRules(entry.subEntries);
// }
// })
// .reduce((r, c) => { return [...c, ...r] }, []);
// }
// return [];
// }
// private _createAndAppendVisibilityRule(entry: ToCEntry): Rule[] {
// const rules_to_append = [];
// if (entry && (entry.type === ToCEntryType.FieldSet)) {
// //childs that are either target or source
// const childIdsWithVisRules = (entry.form.get('fields') as UntypedFormArray).controls.reduce((all, s) => {
// const sval = s.value as Field;
// return this.visibilityRules.find(x => (x.targetField === sval.id) || (x.sourceField === sval.id)) ? [...all, sval] : all;
// }, []) as Field[];
// const innerCompositeFieldOriginalIds = (entry.form.get('fields') as UntypedFormArray).controls.map(x => x.get('id').value) as string[];
// //multiplicity items
// const multiplicityItemsValue = entry.form.get('multiplicityItems').value as CompositeField[];
// // ********* FIELDS OF FIELDSET ARE EITHER TARGETS OR SOURCES *****
// if (childIdsWithVisRules.length && multiplicityItemsValue && multiplicityItemsValue.length) {
// //check each multiplicity item composite field
// multiplicityItemsValue.forEach(mi => {
// const multiplicityCompositeFieldIds = mi.fields.map(x => x.id);
// const idMappings = multiplicityCompositeFieldIds.map(x => {
// return {
// original: innerCompositeFieldOriginalIds.find(l => x.includes(l)),
// multiplicityIdValue: x
// }
// }) as { original: string, multiplicityIdValue: string }[];
// //each field of mutliplicity item
// mi.fields.forEach(field => {
// //get original visibility rules (original field)
// //original id
// const original_id = childIdsWithVisRules.find(x => field.id.includes(x.id)).id;
// //get vis rules
// //as source
// const original_as_source = this.visibilityRules.filter(x => x.sourceField === original_id);
// const original_as_target = this.visibilityRules.filter(x => x.targetField === original_id);
// if (original_as_source.length) {
// //inner dependencies
// const innerDep = original_as_source.filter(x => innerCompositeFieldOriginalIds.includes(x.targetField));
// innerDep.forEach(x => {
// const newRule = { ...x, sourceField: field.id, targetField: idMappings.find(l => l.original === x.targetField).multiplicityIdValue } as Rule;
// rules_to_append.push(newRule);
// })
// //outer dependencies
// const outerDep = original_as_source.filter(x => !innerCompositeFieldOriginalIds.includes(x.targetField));
// outerDep.forEach(x => {
// const newRule = { ...x, sourceField: field.id };
// rules_to_append.push(newRule);
// })
// }
// if (original_as_target.length) {
// //inner dependencies
// const innerDep = original_as_target.filter(x => innerCompositeFieldOriginalIds.includes(x.sourceField));
// innerDep.forEach(x => {
// const newRule = { ...x, targetField: field.id, sourceField: idMappings.find(l => l.original === x.sourceField).multiplicityIdValue } as Rule;
// rules_to_append.push(newRule);
// })
// //outer dependencies
// const outerDep = original_as_target.filter(x => !innerCompositeFieldOriginalIds.includes(x.sourceField));
// outerDep.forEach(x => {
// const newRule = { ...x, targetField: field.id } as Rule;
// rules_to_append.push(newRule);
// })
// }
// })
// });
// }
// // ** FIELDSET ITSELF IS TARGET
// // ** source it can never be
// const compositeFieldAsTargetRules = this.visibilityRules.filter(x => x.targetField === entry.id);
// const idCompositeFieldMappings = multiplicityItemsValue.map(x => {
// return {
// originalValue: entry.id,
// newValue: x.id
// }
// }) as { originalValue: string, newValue: string }[];
// if (compositeFieldAsTargetRules.length) {
// compositeFieldAsTargetRules.forEach(x => {
// idCompositeFieldMappings.forEach(l => {
// const newRule = { ...x, targetField: l.newValue };
// rules_to_append.push(newRule);
// });
// });
// }
// }
// return rules_to_append;
// }
// private _buildRecursively(form: UntypedFormGroup, whatAmI: ToCEntryType): ToCEntry {
// if (!form) return null;
// switch (whatAmI) {
// case ToCEntryType.Section:
// const sections = form.get('sections') as UntypedFormArray;
// const fieldsets = form.get('compositeFields') as UntypedFormArray;
// const tempResult: ToCEntry[] = [];
// if (sections && sections.length) {
// sections.controls.forEach(section => {
// tempResult.push(this._buildRecursively(section as UntypedFormGroup, ToCEntryType.Section));
// });
// } else if (fieldsets && fieldsets.length) {
// fieldsets.controls.forEach(fieldset => {
// tempResult.push(this._buildRecursively(fieldset as UntypedFormGroup, ToCEntryType.FieldSet));
// });
// }
// return {
// // form: form,
// id: form.get('id').value,
// label: form.get('title').value,
// numbering: '',
// subEntries: tempResult,
// subEntriesType: sections && sections.length ? ToCEntryType.Section : ToCEntryType.FieldSet,
// type: ToCEntryType.Section,
// ordinal: form.get('ordinal').value
// }
// case ToCEntryType.FieldSet:
// return {
// // form: form,
// label: form.get('title').value,
// id: form.get('id').value,
// numbering: 's',
// subEntries: [],
// subEntriesType: ToCEntryType.Field,
// type: ToCEntryType.FieldSet,
// ordinal: form.get('ordinal').value
// }
// }
// }
// private _sortByOrdinal(tocentries: ToCEntry[]) {
// if (!tocentries || !tocentries.length) return;
// tocentries.sort(this._customCompare);
// tocentries.forEach(entry => {
// this._sortByOrdinal(entry.subEntries);
// });
// }
// private _customCompare(a, b) {
// return a.ordinal - b.ordinal;
// }
// private _calculateNumbering(tocentries: ToCEntry[], depth: number[] = []) {
// if (!tocentries || !tocentries.length) {
// return;
// }
// let prefixNumbering = depth.length ? depth.join('.') : '';
// if (depth.length) prefixNumbering = prefixNumbering + ".";
// tocentries.forEach((entry, i) => {
// entry.numbering = prefixNumbering + (i + 1);
// this._calculateNumbering(entry.subEntries, [...depth, i + 1])
// });
// }
// getTocEntries(): ToCEntry[] {
// if (!this.form) { return []; }
// const result: ToCEntry[] = [];
// //build parent pages
// (this.form.get('pages') as UntypedFormArray).controls.forEach((pageElement, i) => {
// result.push({
// id: i + 'id',
// label: pageElement.get('title').value,
// type: ToCEntryType.Page,
// // form: pageElement,
// numbering: (i + 1).toString(),
// subEntriesType: ToCEntryType.Section,
// subEntries: [],
// ordinal: pageElement.get('ordinal').value
// } as ToCEntry)
// });
// result.forEach((entry, i) => {
// const sections = entry.form.get('sections') as UntypedFormArray;
// sections.controls.forEach(section => {
// const tempResults = this._buildRecursively(section as UntypedFormGroup, ToCEntryType.Section);
// entry.subEntries.push(tempResults);
// });
// });
// this._sortByOrdinal(result);
// //calculate numbering
// this._calculateNumbering(result);
// return result;
// }
} }

View File

@ -13,6 +13,7 @@ import { DescriptionFormFieldSetComponent } from './components/form-field-set/fo
import { DescriptionFormFieldComponent } from './components/form-field/form-field.component'; import { DescriptionFormFieldComponent } from './components/form-field/form-field.component';
import { DescriptionFormSectionComponent } from './components/form-section/form-section.component'; import { DescriptionFormSectionComponent } from './components/form-section/form-section.component';
import { DescriptionFormComponent } from './description-form.component'; import { DescriptionFormComponent } from './description-form.component';
import { DescriptionFormAnnotationService } from './description-form-annotation.service';
@NgModule({ @NgModule({
@ -39,6 +40,7 @@ import { DescriptionFormComponent } from './description-form.component';
DescriptionFormFieldSetComponent DescriptionFormFieldSetComponent
], ],
providers: [ providers: [
DescriptionFormAnnotationService
] ]
}) })
export class DescriptionFormModule { } export class DescriptionFormModule { }

View File

@ -1,5 +1,4 @@
export interface QueryResult<T> { export interface QueryResult<T> {
count: number; count: number;
countOverride?: number;
items: T[]; items: T[];
} }