Sending replies and new messages using the annotation dialog is now possible, displayed hierarchically as message threads, layout tweaks

This commit is contained in:
Thomas Georgios Giannos 2024-03-14 12:10:23 +02:00
parent 1bdafbc9e6
commit 00815b4b80
3 changed files with 50 additions and 34 deletions

View File

@ -30,7 +30,7 @@
</div> </div>
</form> </form>
</div> </div>
<div class="col-12 mb-3 pt-2 gr-color" *ngIf="threads?.length > 0">{{'ANNOTATION-DIALOG.TITLE' | translate}}</div> <div class="col-12 mb-3 pt-2 gr-color" *ngIf="threads?.size > 0">{{'ANNOTATION-DIALOG.TITLE' | translate}}</div>
<div class="col-12"> <div class="col-12">
<div *ngFor="let thread of threads" class="row"> <div *ngFor="let thread of threads" class="row">
<!-- Parent Thread --> <!-- Parent Thread -->
@ -43,12 +43,12 @@
<div class="row reply-content"> <div class="row reply-content">
<div class="col-12"> <div class="col-12">
<div class="row h-100"> <div class="row h-100">
<div class="col annotation-time">{{thread.timeStamp | date:'EEEE, MMMM d, y, h:mm a'}}</div> <div class="col-10 annotation-time">{{getParentAnnotation(thread).timeStamp | date:'EEEE, MMMM d, y, h:mm a'}}</div>
<div class="col-auto" *ngIf="thread.protectionType === annotationProtectionTypeEnum.Private" matTooltip="{{'ANNOTATION-DIALOG.PROTECTION.PRIVATE' | translate}}"><i class="material-icons protection-icon icon-{{getAnnotationProtectionType(thread)}}"></i>{{getAnnotationProtectionType(thread)}}</div> <div class="col-auto" *ngIf="getParentAnnotation(thread).protectionType === annotationProtectionTypeEnum.Private" matTooltip="{{'ANNOTATION-DIALOG.PROTECTION.PRIVATE' | translate}}"><i class="material-icons protection-icon icon-{{getAnnotationProtectionType(thread)}}"></i>{{getAnnotationProtectionType(thread)}}</div>
<div class="col-auto" *ngIf="thread.protectionType === annotationProtectionTypeEnum.EntityAccessors" matTooltip="{{'ANNOTATION-DIALOG.PROTECTION.ENTITY-ACCESSORS' | translate}}"><i class="material-icons protection-icon icon-{{getAnnotationProtectionType(thread)}}"></i>{{getAnnotationProtectionType(thread)}}</div> <div class="col-auto" *ngIf="getParentAnnotation(thread).protectionType === annotationProtectionTypeEnum.EntityAccessors" matTooltip="{{'ANNOTATION-DIALOG.PROTECTION.ENTITY-ACCESSORS' | translate}}"><i class="material-icons protection-icon icon-{{getAnnotationProtectionType(thread)}}"></i>{{getAnnotationProtectionType(thread)}}</div>
<div class="col-md-12 annotation-full-text">{{thread.payload}}</div> <div class="col-md-12 annotation-full-text">{{getParentAnnotation(thread).payload}}</div>
<div class="col-md-12"> <div class="col-md-12">
<em class="user">{{'ANNOTATION-DIALOG.THREADS.FROM-USER' | translate}} {{thread.author.name}}</em> <em class="user">{{'ANNOTATION-DIALOG.THREADS.FROM-USER' | translate}} {{getParentAnnotation(thread).author.name}}</em>
</div> </div>
</div> </div>
</div> </div>
@ -59,10 +59,10 @@
<!-- Previous Replies --> <!-- Previous Replies -->
<div class="col-12"> <div class="col-12">
<div *ngFor="let annotation of annotationsPerThread[thread.threadId]; let i = index;" class="row replies"> <div *ngFor="let annotation of annotationsPerThread[thread]; let i = index;" class="row replies">
<div class="col-auto pr-0"> <div class="col-auto pr-0">
<div class="col reply child"></div> <div class="col reply child"></div>
<div class="col reply child dummy-for-next-child" *ngIf="i != annotationsPerThread[thread.threadId].length - 1"></div> <div class="col reply child dummy-for-next-child" *ngIf="i != annotationsPerThread[thread].length - 1"></div>
</div> </div>
<div class="col pl-0 pt-1"> <div class="col pl-0 pt-1">
<div class="parent row m-0"> <div class="parent row m-0">
@ -72,7 +72,7 @@
<div class="col reply-content"> <div class="col reply-content">
<div class="row h-100"> <div class="row h-100">
<div class="col-md-12 annotation-time">{{annotation.timeStamp | date:'EEEE, MMMM d, y, h:mm a'}}</div> <div class="col-md-12 annotation-time">{{annotation.timeStamp | date:'EEEE, MMMM d, y, h:mm a'}}</div>
<div class="col-md-12 annotation-full-text">{{annotation.text}}</div> <div class="col-md-12 annotation-full-text">{{annotation.payload}}</div>
<div class="col-md-12"> <div class="col-md-12">
<em class="gr-color">{{'ANNOTATION-DIALOG.THREADS.FROM-USER' | translate}}</em> <em class="gr-color">{{'ANNOTATION-DIALOG.THREADS.FROM-USER' | translate}}</em>
{{annotation.author.name}} {{annotation.author.name}}
@ -89,11 +89,11 @@
<div class="col-12"> <div class="col-12">
<div class="row new-reply mr-0"> <div class="row new-reply mr-0">
<mat-form-field class="col pt-2 pb-2 pr-0"> <mat-form-field class="col pt-2 pb-2 pr-0">
<textarea matInput matTextareaAutosize matAutosizeMinRows="1" [formControl]="this.threadReplyTextsFG[thread.id.toString()].get('replyText')" placeholder="{{'ANNOTATION-DIALOG.THREADS.REPLY' | translate}}"></textarea> <textarea matInput matTextareaAutosize matAutosizeMinRows="1" [formControl]="this.threadReplyTextsFG[thread.toString()].get('replyText')" placeholder="{{'ANNOTATION-DIALOG.THREADS.REPLY' | translate}}"></textarea>
<mat-error *ngIf="this.threadReplyTextsFG[thread.id.toString()]?.get('replyText')?.hasError('required')">{{'COMMONS.VALIDATION.REQUIRED' | translate}}</mat-error> <mat-error *ngIf="this.threadReplyTextsFG[thread.toString()]?.get('replyText')?.hasError('required')">{{'COMMONS.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field> </mat-form-field>
<div class="col-auto send-msg"> <div class="col-auto send-msg">
<button class="form-field-margin" mat-icon-button type="button" color="accent" (click)="replyThread(thread.threadId, thread.protectionType)" matTooltip="{{'ANNOTATION-DIALOG.THREADS.REPLY' | translate}}"> <button class="form-field-margin" mat-icon-button type="button" color="accent" (click)="replyThread(thread)" matTooltip="{{'ANNOTATION-DIALOG.THREADS.REPLY' | translate}}">
<i class="fa fa-paper-plane"></i> <i class="fa fa-paper-plane"></i>
</button> </button>
</div> </div>

View File

@ -30,10 +30,11 @@ export class AnnotationDialogComponent extends BaseComponent {
private entityId: Guid; private entityId: Guid;
private anchor: string; private anchor: string;
private entityType: string; private entityType: string;
// public annotations: Array<Annotation> = [];
public threads = new Array<Annotation>(); public comments = new Array<Annotation>();
public threads = new Set<Guid>();
public annotationsPerThread = {}; public annotationsPerThread = {};
public parentAnnotationsPerThread = {};
threadReplyTextsFG: Array<UntypedFormGroup>; threadReplyTextsFG: Array<UntypedFormGroup>;
threadFormGroup: UntypedFormGroup; threadFormGroup: UntypedFormGroup;
private formBuilder: FormBuilder = new FormBuilder(); private formBuilder: FormBuilder = new FormBuilder();
@ -73,6 +74,7 @@ export class AnnotationDialogComponent extends BaseComponent {
return; return;
} }
const threadToCreate: AnnotationPersist = { const threadToCreate: AnnotationPersist = {
threadId: Guid.create(),
payload: this.threadFormGroup.get('text').value, payload: this.threadFormGroup.get('text').value,
protectionType: this.threadFormGroup.get('protectionType').value, protectionType: this.threadFormGroup.get('protectionType').value,
entityId: this.entityId, entityId: this.entityId,
@ -86,7 +88,7 @@ export class AnnotationDialogComponent extends BaseComponent {
); );
} }
replyThread(threadId: Guid, threadProtection: AnnotationProtectionType) { replyThread(threadId: Guid) {
// if (!this.threadReplyTexts[threadId.toString()] || this.threadReplyTexts[threadId.toString()].length === 0) { return; } // if (!this.threadReplyTexts[threadId.toString()] || this.threadReplyTexts[threadId.toString()].length === 0) { return; }
this.formService.removeAllBackEndErrors(this.threadReplyTextsFG[threadId.toString()]); this.formService.removeAllBackEndErrors(this.threadReplyTextsFG[threadId.toString()]);
this.formService.touchAllFormFields(this.threadReplyTextsFG[threadId.toString()]); this.formService.touchAllFormFields(this.threadReplyTextsFG[threadId.toString()]);
@ -96,10 +98,11 @@ export class AnnotationDialogComponent extends BaseComponent {
const replyToCreate: AnnotationPersist = { const replyToCreate: AnnotationPersist = {
threadId: threadId, threadId: threadId,
payload: this.threadReplyTextsFG[threadId.toString()].get('replyText').value, payload: this.threadReplyTextsFG[threadId.toString()].get('replyText').value,
protectionType: threadProtection, protectionType: this.parentAnnotationsPerThread[threadId.toString()].protectionType,
entityId: this.entityId, entityId: this.entityId,
entityType: this.entityType, entityType: this.entityType,
anchor: this.anchor anchor: this.anchor,
parentId: this.parentAnnotationsPerThread[threadId.toString()].id
}; };
this.annotationService.persist(replyToCreate).pipe(takeUntil(this._destroyed)) this.annotationService.persist(replyToCreate).pipe(takeUntil(this._destroyed))
.subscribe( .subscribe(
@ -118,12 +121,13 @@ export class AnnotationDialogComponent extends BaseComponent {
private loadThreads() { private loadThreads() {
const lookup: AnnotationLookup = new AnnotationLookup(); const lookup: AnnotationLookup = new AnnotationLookup();
lookup.entityIds = [this.entityId]; lookup.entityIds = [this.entityId];
// lookup.anchors = [this.anchor]; lookup.anchors = [this.anchor];
lookup.entityTypes = [this.entityType]; lookup.entityTypes = [this.entityType];
lookup.project = { lookup.project = {
fields: [ fields: [
nameof<Annotation>(x => x.id), nameof<Annotation>(x => x.id),
nameof<Annotation>(x => x.threadId), nameof<Annotation>(x => x.threadId),
nameof<Annotation>(x => x.parent.id),
nameof<Annotation>(x => x.timeStamp), nameof<Annotation>(x => x.timeStamp),
nameof<Annotation>(x => x.author.name), nameof<Annotation>(x => x.author.name),
nameof<Annotation>(x => x.payload), nameof<Annotation>(x => x.payload),
@ -135,23 +139,33 @@ export class AnnotationDialogComponent extends BaseComponent {
.pipe(takeUntil(this._destroyed)) .pipe(takeUntil(this._destroyed))
.subscribe( .subscribe(
data => { data => {
// this.annotationsPerThread = {}; this.annotationsPerThread = {};
// this.threadReplyTextsFG = new Array<UntypedFormGroup>(); this.parentAnnotationsPerThread = {};
// this.resetFormGroup(); this.threads = new Set();
// this.threads = data.items.filter(item => item.id === item.threadId).sort((a1, a2) => new Date(a2.timeStamp).getTime() - new Date(a1.timeStamp).getTime()); this.threadReplyTextsFG = new Array<UntypedFormGroup>();
// this.threads.forEach(element => { this.resetFormGroup();
// // this.threadReplyTextsFG.addControl(element.id.toString(), new FormControl(null)); this.comments = data.items.sort((a1, a2) => new Date(a2.timeStamp).getTime() - new Date(a1.timeStamp).getTime());
// this.threadReplyTextsFG[element.id.toString()] = this.fb.group({ replyText: new FormControl(null, [Validators.required]) }); this.comments.forEach(element => {
// this.annotationsPerThread[element.id.toString()] = data.items.filter(x => x.threadId === element.id && x.id !== element.id).sort((a1, a2) => new Date(a1.timeStamp).getTime() - new Date(a2.timeStamp).getTime()); // this.threadReplyTextsFG.addControl(element.id.toString(), new FormControl(null));
// }); this.threadReplyTextsFG[element.threadId.toString()] = this.formBuilder.group({ replyText: new FormControl(null, [Validators.required]) });
// // this.annotationsChanged.emit(this.threads); this.annotationsPerThread[element.threadId.toString()] = data.items.filter(x => x.threadId === element.threadId && x.id !== element.id).sort((a1, a2) => new Date(a1.timeStamp).getTime() - new Date(a2.timeStamp).getTime());
this.parentAnnotationsPerThread[element.threadId.toString()] = data.items.filter(x => x.threadId === element.threadId && x.id === element.id)[0];
this.threads = data.items; this.threads.add(element.threadId);
});
// console.log(this.comments);
// console.log(this.threads);
// console.log(this.parentAnnotationsPerThread);
// console.log(this.annotationsPerThread);
// this.annotationsChanged.emit(this.threads);
}, },
error => this.onCallbackError(error), error => this.onCallbackError(error),
); );
} }
getParentAnnotation(thread: Guid): Annotation {
return this.parentAnnotationsPerThread[thread.toString()];
}
resetFormGroup() { resetFormGroup() {
this.threadFormGroup.reset(); this.threadFormGroup.reset();
this.threadFormGroup.get('protectionType').setValue(AnnotationProtectionType.EntityAccessors); this.threadFormGroup.get('protectionType').setValue(AnnotationProtectionType.EntityAccessors);
@ -181,8 +195,9 @@ export class AnnotationDialogComponent extends BaseComponent {
} }
private onCallbackSuccess() { private onCallbackSuccess() {
this.uiNotificationService.snackBarNotification(this.language.instant('DMP-UPLOAD.UPLOAD-SUCCESS'), SnackBarNotificationLevel.Success); this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.SUCCESS'), SnackBarNotificationLevel.Success);
this.router.navigate(['/reload']).then(() => this.router.navigate(['/plans'])); this.refreshAnnotations();
// this.router.navigate(['/reload']).then(() => this.router.navigate(['/plans']));
// this.router.navigate(['/reload']).then(() => this.isPublic ? this.router.navigate(['/explore-plans']) : this.router.navigate(['/plans'])); // this.router.navigate(['/reload']).then(() => this.isPublic ? this.router.navigate(['/explore-plans']) : this.router.navigate(['/plans']));
} }
@ -191,8 +206,8 @@ export class AnnotationDialogComponent extends BaseComponent {
} }
getAnnotationProtectionType(thread: Annotation): string { getAnnotationProtectionType(thread: Guid): string {
return this.enumUtils.toAnnotationProtectionTypeString(thread.protectionType); return this.enumUtils.toAnnotationProtectionTypeString(this.parentAnnotationsPerThread[thread.toString()].protectionType);
} }

View File

@ -127,6 +127,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
const dialogRef = this.dialog.open(AnnotationDialogComponent, { const dialogRef = this.dialog.open(AnnotationDialogComponent, {
width: '40rem', width: '40rem',
maxWidth: '90vw', maxWidth: '90vw',
maxHeight: '90vh',
data: { data: {
entityId: this.descriptionId, entityId: this.descriptionId,
anchor: fieldSetId, anchor: fieldSetId,