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 { PlanUser } from '@app/core/model/plan/plan';
import { DomSanitizer } from '@angular/platform-browser';
import { Subject } from 'rxjs';
interface AnnotationPayloadItem {
isMention: boolean;
@ -62,13 +61,15 @@ export class AnnotationDialogComponent extends BaseComponent {
public annotationStatusFormGroup: UntypedFormGroup;
public listingStatuses: Status[] = [];
public planUsers: PlanUser[] = [];
queryIncludesField: boolean = false;
queryIncludesAnnotation: boolean = false;
@ViewChild('annotationStatus') annotationStatus: MatSelectionList;
constructor(
public dialogRef: MatDialogRef<AnnotationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private router: Router,
public dialog: MatDialog,
private uiNotificationService: UiNotificationService,
private language: TranslateService,
@ -78,13 +79,14 @@ export class AnnotationDialogComponent extends BaseComponent {
private statusService: StatusService,
protected routerUtils: RouterUtilsService,
private configurationService: ConfigurationService,
private sanitizer: DomSanitizer,
) {
super();
this.entityId = data.entityId;
this.anchor = data.anchor;
this.entityType = data.entityType;
this.planUsers = data.planUsers;
this.queryIncludesField = data.queryIncludesField;
this.queryIncludesAnnotation = data.queryIncludesAnnotation;
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');
let domain = `${window.location.protocol}//${window.location.hostname}`;
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.style.position = 'absolute';
el.style.left = '-9999px';
@ -374,27 +386,7 @@ export class AnnotationDialogComponent extends BaseComponent {
});
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() {
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 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.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;

View File

@ -186,7 +186,9 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
entityId: this.descriptionId,
anchor: fieldSetId,
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 => {

View File

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

View File

@ -198,13 +198,20 @@
<div class="row">
<div class="col-12">
<div *ngFor="let field of section.fields; let j=index">
<div class="row align-items-start">
<div class="col">
{{field.id}}
<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.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>
<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)">
<mat-icon [matBadge]="annotationsPerAnchor?.get(field.id)" [matBadgeHidden]="annotationsPerAnchor?.get(field.id) <= 0" matBadgeColor="warn">comment</mat-icon>
</button>

View File

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

View File

@ -1,9 +1,9 @@
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 { MatDialog } from '@angular/material/dialog';
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 { DescriptionStatus } from '@app/core/common/enum/description-status';
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'],
providers: [PlanEditorService]
})
export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> implements OnInit {
export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> implements OnInit, AfterViewInit {
isNew = true;
isDeleted = false;
@ -89,6 +89,9 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
planUserRoleEnumValues = this.enumUtils.getEnumValues<PlanUserRole>(PlanUserRole);
fileTransformerEntityTypeEnum = FileTransformerEntityType;
scrollToField: boolean = false;
openAnnotation: boolean = false;
permissionPerSection: Map<Guid, string[]>;
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) => {
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) {
@ -491,6 +515,15 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
this.resetScroll();
}
public changeStep1(fieldId: string = null) {
if (!fieldId) return;
const element = document.getElementById(fieldId);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
}
nextStep() {
this.step = this.step < this.selectedBlueprint.definition.sections.length ? this.step + 1 : this.step;
this.resetScroll();
@ -807,7 +840,9 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
entityId: this.item.id,
anchor: anchorId,
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 => {
@ -829,4 +864,23 @@ export class PlanEditorComponent extends BaseEditor<PlanEditorModel, Plan> imple
getLanguageInfos(): LanguageInfo[] {
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 { PlanEditorEntityResolver } from './resolvers/plan-editor-enitity.resolver';
import { PlanEditorPermissionsResolver } from './resolvers/plan-editor-permissions.resolver';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
const routes: Routes = [
{
@ -34,8 +35,48 @@ const routes: Routes = [
getFromTitleService: true,
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({

View File

@ -130,8 +130,16 @@ export class PlanEditorEntityResolver extends BaseEditorResolver {
...PlanEditorEntityResolver.lookupFields()
];
const id = route.paramMap.get('id');
const fieldsetId = route.paramMap.get('fieldsetId');
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));
}
}
}