diff --git a/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts b/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts index a46189ed1..8d424fb8b 100644 --- a/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts +++ b/frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts @@ -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, @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(''+planUser?.user?.name+''); - // } - - // 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(''+planUser?.user?.name+''); - // } - - // return this.sanitizer.bypassSecurityTrustHtml(payload); - // } private onCallbackAnnotationStatusSuccess() { this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.ANNOTATION-STATUS.SUCCESS'), SnackBarNotificationLevel.Success); diff --git a/frontend/src/app/ui/description/editor/description-editor.component.ts b/frontend/src/app/ui/description/editor/description-editor.component.ts index 1c273c39d..fd2ab39ce 100644 --- a/frontend/src/app/ui/description/editor/description-editor.component.ts +++ b/frontend/src/app/ui/description/editor/description-editor.component.ts @@ -141,9 +141,9 @@ export class DescriptionEditorComponent extends BaseEditor { diff --git a/frontend/src/app/ui/description/editor/description-form/components/services/description-form.service.ts b/frontend/src/app/ui/description/editor/description-form/components/services/description-form.service.ts index 8f56ac6e4..e0863d2a2 100644 --- a/frontend/src/app/ui/description/editor/description-form/components/services/description-form.service.ts +++ b/frontend/src/app/ui/description/editor/description-form/components/services/description-form.service.ts @@ -4,6 +4,9 @@ import { Observable, Subject } from "rxjs"; @Injectable() export class DescriptionFormService { + public queryIncludesField: boolean; + public queryIncludesAnnotation: boolean; + private detectChangesSubject: Subject = new Subject(); public getDetectChangesObservable(): Observable { diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html index c9ed77777..0a42d74aa 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.html @@ -198,13 +198,20 @@
-
-
+ {{field.id}} +
+
{{i + 1}}.{{j + 1}} {{enumUtils.toPlanBlueprintSystemFieldTypeString(field.systemFieldType)}}*
{{i + 1}}.{{j + 1}} {{field.referenceType.name}}*
{{i + 1}}.{{j + 1}} {{field.label}}*
-
+ + +
diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.scss b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.scss index 8372f572c..b2768f462 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.scss +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.scss @@ -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; diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts index b0e5dc48a..5da907549 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.component.ts @@ -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 implements OnInit { +export class PlanEditorComponent extends BaseEditor implements OnInit, AfterViewInit { isNew = true; isDeleted = false; @@ -89,6 +89,9 @@ export class PlanEditorComponent extends BaseEditor imple planUserRoleEnumValues = this.enumUtils.getEnumValues(PlanUserRole); fileTransformerEntityTypeEnum = FileTransformerEntityType; + scrollToField: boolean = false; + openAnnotation: boolean = false; + permissionPerSection: Map; hoveredContact: number = -1; @@ -214,7 +217,28 @@ export class PlanEditorComponent extends BaseEditor 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 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 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 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 + ); + } } diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.routing.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.routing.ts index c03313e7a..09b0d4b8c 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.routing.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/plan-editor.routing.ts @@ -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({ diff --git a/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts b/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts index 68f1aaee9..a2a14f959 100644 --- a/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts +++ b/frontend/src/app/ui/plan/plan-editor-blueprint/resolvers/plan-editor-enitity.resolver.ts @@ -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)); } } }