added plan links

This commit is contained in:
Sofia Papacharalampous 2024-07-15 17:12:35 +03:00
parent 287c016c89
commit f0c2e9dacc
9 changed files with 156 additions and 38 deletions

View File

@ -26,7 +26,6 @@ import { ConfigurationService } from '@app/core/services/configuration/configura
import { MatSelectionList } from '@angular/material/list'; import { MatSelectionList } from '@angular/material/list';
import { PlanUser } from '@app/core/model/plan/plan'; import { PlanUser } from '@app/core/model/plan/plan';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { Subject } from 'rxjs';
interface AnnotationPayloadItem { interface AnnotationPayloadItem {
isMention: boolean; isMention: boolean;
@ -63,12 +62,14 @@ export class AnnotationDialogComponent extends BaseComponent {
public listingStatuses: Status[] = []; public listingStatuses: Status[] = [];
public planUsers: PlanUser[] = []; public planUsers: PlanUser[] = [];
queryIncludesField: boolean = false;
queryIncludesAnnotation: boolean = false;
@ViewChild('annotationStatus') annotationStatus: MatSelectionList; @ViewChild('annotationStatus') annotationStatus: MatSelectionList;
constructor( constructor(
public dialogRef: MatDialogRef<AnnotationDialogComponent>, public dialogRef: MatDialogRef<AnnotationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
private router: Router,
public dialog: MatDialog, public dialog: MatDialog,
private uiNotificationService: UiNotificationService, private uiNotificationService: UiNotificationService,
private language: TranslateService, private language: TranslateService,
@ -78,13 +79,14 @@ export class AnnotationDialogComponent extends BaseComponent {
private statusService: StatusService, private statusService: StatusService,
protected routerUtils: RouterUtilsService, protected routerUtils: RouterUtilsService,
private configurationService: ConfigurationService, private configurationService: ConfigurationService,
private sanitizer: DomSanitizer,
) { ) {
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;
this.planUsers = data.planUsers; this.planUsers = data.planUsers;
this.queryIncludesField = data.queryIncludesField;
this.queryIncludesAnnotation = data.queryIncludesAnnotation;
dialogRef.backdropClick().pipe(takeUntil(this._destroyed)).subscribe(() => dialogRef.close(this.changesMade)); dialogRef.backdropClick().pipe(takeUntil(this._destroyed)).subscribe(() => dialogRef.close(this.changesMade));
} }
@ -279,8 +281,18 @@ export class AnnotationDialogComponent extends BaseComponent {
const el = document.createElement('textarea'); const el = document.createElement('textarea');
let domain = `${window.location.protocol}//${window.location.hostname}`; let domain = `${window.location.protocol}//${window.location.hostname}`;
if (window.location.port && window.location.port != '') domain += `:${window.location.port}` if (window.location.port && window.location.port != '') domain += `:${window.location.port}`
const descriptionSectionPath = this.routerUtils.generateUrl(['descriptions/edit', this.entityId, 'f', this.anchor, 'annotation'].join('/'));
el.value = domain + descriptionSectionPath; let currentPath = window.location.pathname;
if (this.queryIncludesAnnotation) {
currentPath = currentPath.split('/').slice(0, currentPath.split('/').length-4).join('/')
} else if (this.queryIncludesField) {
currentPath = currentPath.split('/').slice(0, currentPath.split('/').length-3).join('/')
} else {
currentPath = currentPath.split('/').slice(0, currentPath.split('/').length-1).join('/')
}
const sectionPath = this.routerUtils.generateUrl([currentPath, this.entityId, 'f', this.anchor, 'annotation'].join('/'));
el.value = domain + sectionPath;
el.setAttribute('readonly', ''); el.setAttribute('readonly', '');
el.style.position = 'absolute'; el.style.position = 'absolute';
el.style.left = '-9999px'; el.style.left = '-9999px';
@ -374,27 +386,7 @@ export class AnnotationDialogComponent extends BaseComponent {
}); });
return payloadItems; return payloadItems;
// for (let planUser of this.planUsers) {
// payloadItems = payload.split(`@{{userid:${planUser.id}}}`).map({isMention }).join('<span class="highlight-user-mention">'+planUser?.user?.name+'</span>');
// }
// return this.sanitizer.bypassSecurityTrustHtml(payload);
} }
// parsePayload(payload: string): AnnotationPayloadItem[] {
// if (!this.planUsers) return [{ isMention: false, payload: payload}];
// if (this.planUsers.length == 0) [{ isMention: false, payload: payload}];
// let payloadItems = [];
// // let payloadItems = [];
// for (let planUser of this.planUsers) {
// payloadItems = payload.split(`@{{userid:${planUser.id}}}`).map({isMention }).join('<span class="highlight-user-mention">'+planUser?.user?.name+'</span>');
// }
// return this.sanitizer.bypassSecurityTrustHtml(payload);
// }
private onCallbackAnnotationStatusSuccess() { private onCallbackAnnotationStatusSuccess() {
this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.ANNOTATION-STATUS.SUCCESS'), SnackBarNotificationLevel.Success); this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.ANNOTATION-STATUS.SUCCESS'), SnackBarNotificationLevel.Success);

View File

@ -141,9 +141,9 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
const isPublicDescription = params['public']; const isPublicDescription = params['public'];
const newPlanId = params['newPlanId']; const newPlanId = params['newPlanId'];
this.scrollToField = this.route.snapshot.data['scrollToField'] ?? false this.scrollToField = this.route.snapshot.data['scrollToField'] ?? false; this.descriptionFormService.queryIncludesField = this.scrollToField;
this.anchorFieldsetId = params['fieldsetId'] ?? null; this.anchorFieldsetId = params['fieldsetId'] ?? null;
this.openAnnotation = this.route.snapshot.data['openAnnotation'] ?? false; this.openAnnotation = this.route.snapshot.data['openAnnotation'] ?? false; this.descriptionFormService.queryIncludesAnnotation = this.openAnnotation;
if (copyPlanId && !planId && planSectionId) this.isCopy = true; if (copyPlanId && !planId && planSectionId) this.isCopy = true;

View File

@ -186,7 +186,9 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
entityId: this.descriptionId, entityId: this.descriptionId,
anchor: fieldSetId, anchor: fieldSetId,
entityType: AnnotationEntityType.Description, entityType: AnnotationEntityType.Description,
planUsers: this.planUsers planUsers: this.planUsers,
queryIncludesField: this.descriptionFormService.queryIncludesField,
queryIncludesAnnotation: this.descriptionFormService.queryIncludesAnnotation,
} }
}); });
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => {

View File

@ -4,6 +4,9 @@ import { Observable, Subject } from "rxjs";
@Injectable() @Injectable()
export class DescriptionFormService { export class DescriptionFormService {
public queryIncludesField: boolean;
public queryIncludesAnnotation: boolean;
private detectChangesSubject: Subject<any> = new Subject<any>(); private detectChangesSubject: Subject<any> = new Subject<any>();
public getDetectChangesObservable(): Observable<any> { public getDetectChangesObservable(): Observable<any> {

View File

@ -198,13 +198,20 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div *ngFor="let field of section.fields; let j=index"> <div *ngFor="let field of section.fields; let j=index">
<div class="row align-items-start"> {{field.id}}
<div class="col"> <div class="heading-wrapper row align-items-start" [id]="field.id">
<div class="col-auto">
<div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.System">{{i + 1}}.{{j + 1}} {{enumUtils.toPlanBlueprintSystemFieldTypeString(field.systemFieldType)}}<span *ngIf="field.required">*</span></div> <div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.System">{{i + 1}}.{{j + 1}} {{enumUtils.toPlanBlueprintSystemFieldTypeString(field.systemFieldType)}}<span *ngIf="field.required">*</span></div>
<div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.ReferenceType">{{i + 1}}.{{j + 1}} {{field.referenceType.name}}<span *ngIf="field.required">*</span></div> <div class="heading" *ngIf="!field.label && field.category === planBlueprintSectionFieldCategoryEnum.ReferenceType">{{i + 1}}.{{j + 1}} {{field.referenceType.name}}<span *ngIf="field.required">*</span></div>
<div class="heading" *ngIf="field.label">{{i + 1}}.{{j + 1}} {{field.label}}<span *ngIf="field.required">*</span></div> <div class="heading" *ngIf="field.label">{{i + 1}}.{{j + 1}} {{field.label}}<span *ngIf="field.required">*</span></div>
</div> </div>
<div *ngIf="!isNew" class="col-auto" style="margin-top: 1rem;"> <div class="col-auto link-icon" style="margin-top: 1rem;">
<button *ngIf="!hildeLink" mat-icon-button type="button" matTooltip="{{ 'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.COPY-LINK' | translate }}" (click)="copyLink(field?.id)">
<mat-icon>link</mat-icon>
</button>
</div>
<div *ngIf="!isNew" class="ml-auto col-auto" style="margin-top: 1rem;">
<button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(field.id)" matTooltip="{{ 'PLAN-EDITOR.ACTIONS.ANNOTATIONS' | translate }}" [disabled]="!canAnnotate(section.id)"> <button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(field.id)" matTooltip="{{ 'PLAN-EDITOR.ACTIONS.ANNOTATIONS' | translate }}" [disabled]="!canAnnotate(section.id)">
<mat-icon [matBadge]="annotationsPerAnchor?.get(field.id)" [matBadgeHidden]="annotationsPerAnchor?.get(field.id) <= 0" matBadgeColor="warn">comment</mat-icon> <mat-icon [matBadge]="annotationsPerAnchor?.get(field.id)" [matBadgeHidden]="annotationsPerAnchor?.get(field.id) <= 0" matBadgeColor="warn">comment</mat-icon>
</button> </button>

View File

@ -325,6 +325,17 @@ a:hover {
margin: 3rem 0rem 3rem 0rem; margin: 3rem 0rem 3rem 0rem;
} }
.link-icon {
margin-top: 1rem;
visibility: hidden;
}
.heading-wrapper:hover {
& > .link-icon {
visibility: visible;
}
}
.heading { .heading {
text-align: left; text-align: left;
font-weight: 700; font-weight: 700;

View File

@ -1,9 +1,9 @@
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Component, OnInit } from '@angular/core'; import { AfterViewInit, Component, OnInit } from '@angular/core';
import { FormArray, UntypedFormGroup } from '@angular/forms'; import { FormArray, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type'; import { AnnotationEntityType } from '@app/core/common/enum/annotation-entity-type';
import { DescriptionStatus } from '@app/core/common/enum/description-status'; import { DescriptionStatus } from '@app/core/common/enum/description-status';
import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type'; import { FileTransformerEntityType } from '@app/core/common/enum/file-transformer-entity-type';
@ -66,7 +66,7 @@ import { FormAnnotationService } from '@app/ui/annotations/annotation-dialog-com
styleUrls: ['./plan-editor.component.scss'], styleUrls: ['./plan-editor.component.scss'],
providers: [PlanEditorService] providers: [PlanEditorService]
}) })
export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> implements OnInit { export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> implements OnInit, AfterViewInit {
isNew = true; isNew = true;
isDeleted = false; isDeleted = false;
@ -89,6 +89,9 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
planUserRoleEnumValues = this.enumUtils.getEnumValues<PlanUserRole>(PlanUserRole); planUserRoleEnumValues = this.enumUtils.getEnumValues<PlanUserRole>(PlanUserRole);
fileTransformerEntityTypeEnum = FileTransformerEntityType; fileTransformerEntityTypeEnum = FileTransformerEntityType;
scrollToField: boolean = false;
openAnnotation: boolean = false;
permissionPerSection: Map<Guid, string[]>; permissionPerSection: Map<Guid, string[]>;
hoveredContact: number = -1; hoveredContact: number = -1;
@ -214,7 +217,28 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
this.formAnnotationService.getOpenAnnotationSubjectObservable().pipe(takeUntil(this._destroyed)).subscribe((anchorId: string) => { this.formAnnotationService.getOpenAnnotationSubjectObservable().pipe(takeUntil(this._destroyed)).subscribe((anchorId: string) => {
if (anchorId && anchorId == this.item.id?.toString()) this.showAnnotations(anchorId); if (anchorId && anchorId == this.item.id?.toString()) this.showAnnotations(anchorId);
}); });
}
ngAfterViewInit(): void {
this.route.params
.pipe(takeUntil(this._destroyed))
.subscribe((params: Params) => {
const fieldId = params['fieldId'];
this.scrollToField = this.route.snapshot.data['scrollToField'] ?? false
this.openAnnotation = this.route.snapshot.data['openAnnotation'] ?? false;
if (fieldId) {
let fieldStep = this.item?.blueprint.definition.sections.find(s => s.fields.filter(f => f.id.toString() == fieldId)?.length > 0)?.ordinal;
this.changeStep(fieldStep);
setTimeout(() => this.changeStep1(fieldId), 600);
const openAnnotation = this.route.snapshot.data['openAnnotation'] ?? false;
if (openAnnotation) this.showAnnotations(fieldId);
}
});
} }
getItem(itemId: Guid, successFunction: (item: Plan) => void) { getItem(itemId: Guid, successFunction: (item: Plan) => void) {
@ -491,6 +515,15 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
this.resetScroll(); this.resetScroll();
} }
public changeStep1(fieldId: string = null) {
if (!fieldId) return;
const element = document.getElementById(fieldId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}
nextStep() { nextStep() {
this.step = this.step < this.selectedBlueprint.definition.sections.length ? this.step + 1 : this.step; this.step = this.step < this.selectedBlueprint.definition.sections.length ? this.step + 1 : this.step;
this.resetScroll(); this.resetScroll();
@ -807,7 +840,9 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
entityId: this.item.id, entityId: this.item.id,
anchor: anchorId, anchor: anchorId,
entityType: AnnotationEntityType.Plan, entityType: AnnotationEntityType.Plan,
planUsers: this.item?.planUsers ?? [] planUsers: this.item?.planUsers ?? [],
queryIncludesField: this.scrollToField,
queryIncludesAnnotation: this.openAnnotation,
} }
}); });
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => { dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => {
@ -829,4 +864,23 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
getLanguageInfos(): LanguageInfo[] { getLanguageInfos(): LanguageInfo[] {
return this.languageInfoService.getLanguageInfoValues(); return this.languageInfoService.getLanguageInfoValues();
} }
copyLink(fieldId: string): void {
const el = document.createElement('textarea');
let domain = `${window.location.protocol}//${window.location.hostname}`;
if (window.location.port && window.location.port != '') domain += `:${window.location.port}`
const descriptionSectionPath = this.routerUtils.generateUrl(['plans/edit', this.item.id.toString(), 'f', fieldId].join('/'));
el.value = domain + descriptionSectionPath;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
this.uiNotificationService.snackBarNotification(
this.language.instant('DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.COPY-LINK-SUCCESSFUL'),
SnackBarNotificationLevel.Success
);
}
} }

View File

@ -6,6 +6,7 @@ import { AuthGuard } from '@app/core/auth-guard.service';
import { PlanEditorComponent } from './plan-editor.component'; import { PlanEditorComponent } from './plan-editor.component';
import { PlanEditorEntityResolver } from './resolvers/plan-editor-enitity.resolver'; import { PlanEditorEntityResolver } from './resolvers/plan-editor-enitity.resolver';
import { PlanEditorPermissionsResolver } from './resolvers/plan-editor-permissions.resolver'; import { PlanEditorPermissionsResolver } from './resolvers/plan-editor-permissions.resolver';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
const routes: Routes = [ const routes: Routes = [
{ {
@ -34,8 +35,48 @@ const routes: Routes = [
getFromTitleService: true, getFromTitleService: true,
usePrefix: false usePrefix: false
} }
} },
{
path: ':id/f/:fieldId',
canActivate: [AuthGuard],
component: PlanEditorComponent,
canDeactivate: [PendingChangesGuard],
resolve: {
'entity': PlanEditorEntityResolver,
'permissions': PlanEditorPermissionsResolver,
},
data: {
breadcrumbs: true,
getFromTitleService: true,
usePrefix: false,
...BreadcrumbService.generateRouteDataConfiguration({
skipNavigation: true,
}),
title: 'PLAN-EDITOR.TITLE-EDIT',
scrollToField: true,
}
},
,
{
path: ':id/f/:fieldId/annotation',
canActivate: [AuthGuard],
component: PlanEditorComponent,
canDeactivate: [PendingChangesGuard],
resolve: {
'entity': PlanEditorEntityResolver,
'permissions': PlanEditorPermissionsResolver,
},
data: {
breadcrumbs: true,
getFromTitleService: true,
usePrefix: false,
...BreadcrumbService.generateRouteDataConfiguration({
skipNavigation: true,
}),
title: 'DESCRIPTION-EDITOR.TITLE-EDIT-DESCRIPTION',
openAnnotation: true,
}
},
]; ];
@NgModule({ @NgModule({

View File

@ -130,8 +130,16 @@ export class PlanEditorEntityResolver extends BaseEditorResolver {
...PlanEditorEntityResolver.lookupFields() ...PlanEditorEntityResolver.lookupFields()
]; ];
const id = route.paramMap.get('id'); const id = route.paramMap.get('id');
const fieldsetId = route.paramMap.get('fieldsetId');
if (id != null) { if (id != null) {
return this.descriptionService.getSingle(Guid.parse(id), fields).pipe(tap(x => this.breadcrumbService.addIdResolvedValue(id, x.label)), takeUntil(this._destroyed)); return this.descriptionService.getSingle(Guid.parse(id), fields).pipe(tap(x => {
if (fieldsetId) {
this.breadcrumbService.addExcludedParam(fieldsetId, true);
this.breadcrumbService.addExcludedParam('f', true);
this.breadcrumbService.addExcludedParam('annotation', true);
}
this.breadcrumbService.addIdResolvedValue(id, x.label)
}), takeUntil(this._destroyed));
} }
} }
} }