added copy-link function on description editor fieldsets and annotations

This commit is contained in:
Sofia Papacharalampous 2024-06-28 15:38:46 +03:00
parent ca4f228dba
commit 2f03b563fe
21 changed files with 238 additions and 9 deletions

View File

@ -8,6 +8,11 @@
<mat-dialog-content>
<div class="row">
<!-- Create New Thread -->
<div class="ml-auto col-auto">
<button mat-icon-button class="col-auto" type="button" matTooltip="{{ 'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.COPY-LINK' | translate }}" (click)="copyLink()">
<mat-icon>link</mat-icon>
</button>
</div>
<div class="col-12">
<form [formGroup]="threadFormGroup">
<div class="row mt-2 mb-3">

View File

@ -217,4 +217,23 @@ export class AnnotationDialogComponent extends BaseComponent {
enableReply(threadId: string): void {
this.replyEnabledPerThread[threadId] = true;
}
copyLink() {
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;
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

@ -107,7 +107,22 @@
<div class="row toc-pane-container" #boundary>
<div #spacer></div>
<div class="col-12">
<app-table-of-contents [visibilityRulesService]="visibilityRulesService" #table0fContents [showErrors]="showTocEntriesErrors" [hasFocus]="reachedBase == false" [formGroup]="formGroup.get('properties')" [descriptionTemplate]="item.descriptionTemplate" *ngIf="formGroup" [links]="links" [boundary]="boundary" [spacer]="spacer" stickyThing (entrySelected)="changeStep($event.entry, $event.execute)" [pageToFieldSetMap]="pageToFieldSetMap"></app-table-of-contents>
<app-table-of-contents
*ngIf="formGroup"
stickyThing
[visibilityRulesService]="visibilityRulesService"
[showErrors]="showTocEntriesErrors"
[hasFocus]="reachedBase == false"
[formGroup]="formGroup.get('properties')"
[descriptionTemplate]="item.descriptionTemplate"
[links]="links"
[boundary]="boundary" [spacer]="spacer"
[pageToFieldSetMap]="pageToFieldSetMap"
[anchorFieldsetId]="anchorFieldsetId"
(entrySelected)="changeStep($event.entry, $event.execute)"
#table0fContents
></app-table-of-contents>
</div>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Title } from '@angular/platform-browser';
@ -44,6 +44,7 @@ import { TableOfContentsService } from './table-of-contents/services/table-of-co
import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { DescriptionFormService } from './description-form/components/services/description-form.service';
import { DescriptionFormAnnotationService } from './description-form/description-form-annotation.service';
@Component({
selector: 'app-description-editor-component',
@ -51,7 +52,7 @@ import { DescriptionFormService } from './description-form/components/services/d
styleUrls: ['./description-editor.component.scss'],
providers: [DescriptionEditorService]
})
export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorModel, Description> implements OnInit {
export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorModel, Description> implements OnInit, AfterViewInit {
isNew = true;
isDeleted = false;
@ -71,6 +72,10 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
reachedBase: boolean = true;
reachedLast: boolean = false;
reachedFirst: boolean = false;
anchorFieldsetId: string;
scrollToField: boolean = false;
openAnnotation: boolean = false;
pageToFieldSetMap: Map<string, DescriptionFieldIndicator[]> = new Map<string, DescriptionFieldIndicator[]>();
@ -105,6 +110,7 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
private changeDetectorRef: ChangeDetectorRef,
private tableOfContentsService: TableOfContentsService,
private descriptionFormService: DescriptionFormService,
private descriptionFormAnnotationService: DescriptionFormAnnotationService,
) {
const descriptionLabel: string = route.snapshot.data['entity']?.label;
if (descriptionLabel) {
@ -135,8 +141,12 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
const isPublicDescription = params['public'];
const newDmpId = params['newDmpId'];
if (copyDmpId && !dmpId && dmpSectionId) this.isCopy = true;
this.scrollToField = this.route.snapshot.data['scrollToField'] ?? false
this.anchorFieldsetId = params['fieldsetId'] ?? null;
this.openAnnotation = this.route.snapshot.data['openAnnotation'] ?? false;
if (copyDmpId && !dmpId && dmpSectionId) this.isCopy = true;
this.viewOnly = isPublicDescription;
@ -181,6 +191,12 @@ export class DescriptionEditorComponent extends BaseEditor<DescriptionEditorMode
});
}
ngAfterViewInit(): void {
if (this.scrollToField && this.anchorFieldsetId && this.anchorFieldsetId != '') {
if (this.openAnnotation) this.descriptionFormAnnotationService.οpenAnnotationDialog(this.anchorFieldsetId);
}
}
getItem(itemId: Guid, successFunction: (item: Description) => void) {
this.descriptionService.getSingle(itemId, DescriptionEditorEntityResolver.lookupFields())
.pipe(map(data => data as Description), takeUntil(this._destroyed))

View File

@ -41,6 +41,47 @@ const routes: Routes = [
title: 'DESCRIPTION-EDITOR.TITLE-EDIT-DESCRIPTION',
}
},
{
path: ':id/f/:fieldsetId',
canActivate: [AuthGuard],
component: DescriptionEditorComponent,
canDeactivate: [PendingChangesGuard],
resolve: {
'entity': DescriptionEditorEntityResolver,
'permissions': DescriptionEditorPermissionsResolver,
},
data: {
breadcrumbs: true,
getFromTitleService: true,
usePrefix: false,
...BreadcrumbService.generateRouteDataConfiguration({
skipNavigation: true,
}),
title: 'DESCRIPTION-EDITOR.TITLE-EDIT-DESCRIPTION',
scrollToField: true,
}
},
{
path: ':id/f/:fieldsetId/annotation',
canActivate: [AuthGuard],
component: DescriptionEditorComponent,
canDeactivate: [PendingChangesGuard],
resolve: {
'entity': DescriptionEditorEntityResolver,
'permissions': DescriptionEditorPermissionsResolver,
},
data: {
breadcrumbs: true,
getFromTitleService: true,
usePrefix: false,
...BreadcrumbService.generateRouteDataConfiguration({
skipNavigation: true,
}),
title: 'DESCRIPTION-EDITOR.TITLE-EDIT-DESCRIPTION',
scrollToField: true,
openAnnotation: true,
}
},
{
path: ':dmpId/:dmpSectionId',
canActivate: [AuthGuard],

View File

@ -6,8 +6,13 @@
<app-description-form-field-set-title [fieldSet]="fieldSet" [path]="path" [isChild]="isChild"></app-description-form-field-set-title>
</div>
<div *ngIf="!hideAnnotations" class="col-auto">
<button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(fieldSet.id)" [disabled]="!canReview">
<mat-icon matTooltip="{{'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.ANNOTATIONS' | translate}}" [matBadge]="annotationsCount" [matBadgeHidden]="annotationsCount <= 0" matBadgeColor="warn">comment</mat-icon>
<button mat-icon-button class="col-auto annotation-icon" (click)="showAnnotations(fieldSet.id)" matTooltip="{{ 'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.ANNOTATIONS' | translate }}" [disabled]="!canReview">
<mat-icon [matBadge]="annotationsCount" [matBadgeHidden]="annotationsCount <= 0" matBadgeColor="warn">comment</mat-icon>
</button>
</div>
<div *ngIf="!hideAnnotations" class="col-auto pl-0">
<button mat-icon-button class="col-auto" type="button" matTooltip="{{ 'DESCRIPTION-EDITOR.QUESTION.EXTENDED-DESCRIPTION.COPY-LINK' | translate }}" (click)="copyLink(fieldSet.id)">
<mat-icon>link</mat-icon>
</button>
</div>
</div>

View File

@ -17,6 +17,9 @@ import { DescriptionFormAnnotationService } from '../../description-form-annotat
import { DescriptionPropertyDefinitionFieldSet } from '@app/core/model/description/description';
import { DescriptionFormService } from '../services/description-form.service';
import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-template-field-type';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-description-form-field-set',
@ -53,10 +56,13 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
descriptionTemplateFieldType = DescriptionTemplateFieldType;
constructor(
private routerUtils: RouterUtilsService,
private dialog: MatDialog,
private changeDetector: ChangeDetectorRef,
private descriptionFormAnnotationService: DescriptionFormAnnotationService,
private descriptionFormService: DescriptionFormService,
private uiNotificationService: UiNotificationService,
private language: TranslateService,
) {
super();
}
@ -71,6 +77,10 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
this.changeDetector.markForCheck();
}
});
this.descriptionFormAnnotationService.getOpenAnnotationSubjectObservable().pipe(takeUntil(this._destroyed)).subscribe( (anchorFieldsetId: string) => {
if (anchorFieldsetId && anchorFieldsetId == this.fieldSet.id) this.showAnnotations(anchorFieldsetId);
});
}
canAddMultiplicityField(): boolean{
@ -135,6 +145,25 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
});
}
copyLink(fieldsetId: string) {
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.descriptionId.toString(), 'f', fieldsetId].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
);
}
//
//
// Annotations

View File

@ -7,7 +7,7 @@ import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/serv
import { BaseService } from '@common/base/base.service';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
@ -20,6 +20,7 @@ export class DescriptionFormAnnotationService extends BaseService {
private entityId: Guid;
private annotationsPerAnchor: Map<string, number>;
private annotationCountSubject: BehaviorSubject<Map<string, number>> = new BehaviorSubject<Map<string, number>>(null);
private openAnnotationSubject: Subject<any> = new Subject<any>();
constructor(
private annotationService: AnnotationService,
@ -37,6 +38,14 @@ export class DescriptionFormAnnotationService extends BaseService {
public getAnnotationCountObservable(): Observable<Map<string, number>> {
return this.annotationCountSubject.asObservable();
}
public getOpenAnnotationSubjectObservable(): Observable<string> {
return this.openAnnotationSubject.asObservable();
}
public οpenAnnotationDialog(next: any): void {
this.openAnnotationSubject.next(next);
}
public refreshAnnotations() {
const lookup: AnnotationLookup = new AnnotationLookup();

View File

@ -161,8 +161,13 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver {
const dmpId = route.paramMap.get('dmpId');
const dmpSectionId = route.paramMap.get('dmpSectionId');
const copyDmpId = route.paramMap.get('copyDmpId');
// const cloneid = route.paramMap.get('cloneid');
const fieldsetId = route.paramMap.get('fieldsetId');
if (id != null && copyDmpId == null && dmpSectionId == null) {
if (fieldsetId != null) {
this.breadcrumbService.addExcludedParam(fieldsetId, true);
this.breadcrumbService.addExcludedParam('f', true);
this.breadcrumbService.addExcludedParam('annotation', true);
}
return this.descriptionService.getSingle(Guid.parse(id), fields).pipe(tap(d => this.breadcrumbService.addIdResolvedValue(d.id.toString(), d.label)));
} else if (dmpId != null && dmpSectionId != null && copyDmpId == null) {
return this.dmpService.getSingle(Guid.parse(dmpId), DescriptionEditorEntityResolver.dmpLookupFields())

View File

@ -21,6 +21,7 @@ export class TableOfContentsComponent extends BaseComponent implements OnInit, O
@Input() descriptionTemplate: DescriptionTemplate;
@Input() hasFocus: boolean = false;
@Input() visibilityRulesService: VisibilityRulesService;
@Input() anchorFieldsetId: string;
tocentries: ToCEntry[] = null;
@ -29,7 +30,6 @@ export class TableOfContentsComponent extends BaseComponent implements OnInit, O
private _tocentrySelected: ToCEntry = null;
get tocentrySelected() {
return this._tocentrySelected;
// return this.hasFocus ? this._tocentrySelected : null;
}
set tocentrySelected(value) {
this._tocentrySelected = value;
@ -47,6 +47,10 @@ export class TableOfContentsComponent extends BaseComponent implements OnInit, O
if (this.descriptionTemplate) {
this.tocentries = this.getTocEntries(this.descriptionTemplate);
if (this.anchorFieldsetId) {
const anchorTocentry = TableOfContentsComponent._findTocEntryById(this.anchorFieldsetId, this.tocentries);
if (anchorTocentry) setTimeout(() => { this.onToCentrySelected(anchorTocentry) }, 300);
}
}
this.tableOfContentsService.getNextClickedEventObservable().pipe(takeUntil(this._destroyed)).subscribe(x => {
@ -105,6 +109,10 @@ export class TableOfContentsComponent extends BaseComponent implements OnInit, O
if (changes['descriptionTemplate'] && changes.descriptionTemplate != null) {
this.tocentries = this.getTocEntries(this.descriptionTemplate);
}
if (changes['anchorFieldsetId'] && changes.anchorFieldsetId != null) {
const anchorTocentry = TableOfContentsComponent._findTocEntryById(this.anchorFieldsetId, this.tocentries);
if (anchorTocentry) setTimeout(() => { this.onToCentrySelected(anchorTocentry) }, 300);
}
}
private _resetObserver() {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {

View File

@ -982,6 +982,13 @@
"CANCEL": "Cancel",
"UPDATE": "Update"
}
},
"QUESTION": {
"EXTENDED-DESCRIPTION": {
"COPY-LINK": "Copy Link",
"COPY-LINK-SUCCESSFUL": "Link copied",
"ANNOTATIONS": "Comment"
}
}
},
"DESCRIPTION-COPY-DIALOG": {