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>
</form>
</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 *ngFor="let thread of threads" class="row">
<!-- Parent Thread -->
@ -43,12 +43,12 @@
<div class="row reply-content">
<div class="col-12">
<div class="row h-100">
<div class="col annotation-time">{{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="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-10 annotation-time">{{getParentAnnotation(thread).timeStamp | date:'EEEE, MMMM d, y, h:mm a'}}</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="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">{{getParentAnnotation(thread).payload}}</div>
<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>
@ -59,10 +59,10 @@
<!-- Previous Replies -->
<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 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 class="col pl-0 pt-1">
<div class="parent row m-0">
@ -72,7 +72,7 @@
<div class="col reply-content">
<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-full-text">{{annotation.text}}</div>
<div class="col-md-12 annotation-full-text">{{annotation.payload}}</div>
<div class="col-md-12">
<em class="gr-color">{{'ANNOTATION-DIALOG.THREADS.FROM-USER' | translate}}</em>
{{annotation.author.name}}
@ -89,11 +89,11 @@
<div class="col-12">
<div class="row new-reply mr-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>
<mat-error *ngIf="this.threadReplyTextsFG[thread.id.toString()]?.get('replyText')?.hasError('required')">{{'COMMONS.VALIDATION.REQUIRED' | translate}}</mat-error>
<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.toString()]?.get('replyText')?.hasError('required')">{{'COMMONS.VALIDATION.REQUIRED' | translate}}</mat-error>
</mat-form-field>
<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>
</button>
</div>

View File

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