argos/dmp-frontend/src/app/ui/admin/dataset-profile/table-of-contents/table-of-contents.ts

619 lines
17 KiB
TypeScript

import { DOCUMENT } from '@angular/common';
import { Component, EventEmitter, Inject, OnInit, Output, Input } from '@angular/core';
import { BaseComponent } from '@common/base/base.component';
import { interval, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
import { type } from 'os';
import { SimpleChanges } from '@angular/core';
import { Foo, ToCEntry, ToCEntryType } from './table-of-contents-entry';
import { DragulaService } from 'ng2-dragula';
import { FormArray } from '@angular/forms';
export interface Link {
/* id of the section*/
id: string;
/* header type h3/h4 */
type: string;
/* If the anchor is in view of the page */
active: boolean;
/* name of the anchor */
name: string;
/* top offset px of the anchor */
top: number;
page: number;
section: number;
show: boolean;
selected: boolean;
}
@Component({
selector: 'dataset-profile-table-of-contents',
styleUrls: ['./table-of-contents.scss'],
templateUrl: './table-of-contents.html'
})
export class DatasetProfileTableOfContents extends BaseComponent implements OnInit {
@Input() links: ToCEntry[];
container: string;
headerSelectors = '.toc-page-header, .toc-section-header, .toc-compositeField-header';
@Output() stepFound = new EventEmitter<LinkToScroll>();
@Output() currentLinks = new EventEmitter<Link[]>();
@Output() itemClick = new EventEmitter<ToCEntry>();
@Output() newEntry = new EventEmitter<ToCEntry>();
@Output() removeEntry = new EventEmitter<ToCEntry>();
@Output() createEntry = new EventEmitter<Foo>();
@Output() dataNeedsRefresh = new EventEmitter<void>();
@Input() itemSelected: ToCEntry;
@Input() viewOnly: boolean;
subscription: Subscription;
linksSubject: Subject<HTMLElement[]> = new Subject<HTMLElement[]>();
@Input() isActive: boolean;
show: boolean = false;
isDragging: boolean = false;
draggingItemId: string = null;
tocEntryType = ToCEntryType;
DRAGULA_ITEM_ID_PREFIX="table_item_id_";
ROOT_ID: string = "ROOT_ID";//no special meaning
constructor(
@Inject(DOCUMENT) private _document: Document,
private dragulaService: DragulaService
) {
super();
if(this.dragulaService.find('TABLEDRAG')){
this.dragulaService.destroy('TABLEDRAG');
}
const dragula = this.dragulaService.createGroup('TABLEDRAG', {
invalid: (el,handle)=>{
// const elid = el.id;
// if(!el.id) return true;
// const elementId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX,'');
// const entry = this._findTocEntryById(elementId, this.links);
// if(!entry) return true;
// if(entry.type != this.tocEntryType.FieldSet) return true;
// console.log(el);
return false;
}
});
const drake = dragula.drake;
drake.on('drop', (el, target, source,sibling)=>{
// console.log('el:', el);
// console.log('target:', target);
// console.log('source:', source);
// console.log('sibling:', el);
const elementId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX,'');
const targetId = target.id as string;
const sourceId = source.id as string;
if(!(elementId && targetId && sourceId)){
console.error('Elements not have an id');
this.dataNeedsRefresh.emit();
return;
}
const element:ToCEntry = this._findTocEntryById(elementId, this.links);
const targetContainer:ToCEntry = this._findTocEntryById(targetId , this.links);
const sourceContainer:ToCEntry = this._findTocEntryById(sourceId, this.links);
if(!(element && (targetContainer ||((element.type===ToCEntryType.Page) && (targetId === this.ROOT_ID))) && (sourceContainer||((element.type===ToCEntryType.Page) && (sourceId === this.ROOT_ID))))){
console.info('Could not find elements');
this.dataNeedsRefresh.emit();
drake.cancel(true);
return;
}
switch(element.type){
case ToCEntryType.FieldSet:
if(targetContainer.type != this.tocEntryType.Section){
console.error('Fieldsets can only be childs of section');
this.dataNeedsRefresh.emit();
return;
}
//check if target container has no sections
if((targetContainer.form.get('sections') as FormArray).length){
console.error('Fieldsets can only be childs of section thas has no section children');
this.dataNeedsRefresh.emit();
return;
}
const fieldsetForm = element.form;
const targetFieldsets = targetContainer.form.get('fieldSets') as FormArray;
const sourceFieldsets = sourceContainer.form.get('fieldSets') as FormArray;
if(!targetFieldsets){
console.error('Not target fieldsets container found');
this.dataNeedsRefresh.emit();
return;
}
let sourceOrdinal=-1;
let idx = -1;
sourceFieldsets.controls.forEach((elem,index)=>{
if(elem.get('id').value === elementId){
sourceOrdinal = elem.get('ordinal').value;
idx = index
}
});
if(sourceOrdinal>=0 && idx>=0){
sourceFieldsets.removeAt(idx);
sourceFieldsets.controls.forEach(control=>{
const ordinal = control.get('ordinal');
if(ordinal.value>= sourceOrdinal){
const updatedOrdinalVal = ordinal.value -1;
ordinal.setValue(updatedOrdinalVal);
}
});
}
let position:number = targetFieldsets.length;
if(!sibling ||!sibling.id){
console.info('No sibling Id found');
}else{
const siblingId = (sibling.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX,'');
let siblingIndex = -1;
targetFieldsets.controls.forEach((e,idx)=>{
if(e.get('id').value === siblingId){
siblingIndex = idx;
position = e.get('ordinal').value;
}
});
if(siblingIndex>=0 && (position!=targetFieldsets.length)){
targetFieldsets.controls.filter(control=> control.get('ordinal').value >= position).forEach(control=>{
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value +1;
ordinal.setValue(updatedOrdinalVal);
})
}
}
fieldsetForm.get('ordinal').setValue(position);
targetFieldsets.insert(position,fieldsetForm);
this.dataNeedsRefresh.emit();
break;
case ToCEntryType.Section:
if(targetContainer.type == ToCEntryType.Section){
if((targetContainer.form.get('fieldSets')as FormArray).length){
console.info('Target container must only contain section children');
this.dataNeedsRefresh.emit();
return;
}
const targetSections = targetContainer.form.get('sections') as FormArray;
const elementSectionForm = element.form;
const sourceSections = elementSectionForm.parent as FormArray;
if(!(targetSections && sourceSections && elementSectionForm)){
console.info('Could not load sections');
this.dataNeedsRefresh.emit();
return;
}
let idx = -1;
sourceSections.controls.forEach((section,i)=>{
if(section.get('id').value === elementId){
idx = i;
}
});
if(!(idx>=0)){
console.info('Could not find element in Parent container');
this.dataNeedsRefresh.emit();
return;
}
sourceSections.controls.filter(control=>control.get('ordinal').value >= elementSectionForm.get('ordinal').value).forEach(control=>{
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value -1;
ordinal.setValue(updatedOrdinalVal);
});
sourceSections.removeAt(idx);
let targetOrdinal = targetSections.length;
if(sibling && sibling.id){
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX,'');
targetSections.controls.forEach((section,i)=>{
if(section.get('id').value === siblingId){
targetOrdinal = section.get('ordinal').value;
}
})
if(targetOrdinal!=targetSections.length){
// section.get('ordinal').setValue(i+1);
targetSections.controls.filter(control=> control.get('ordinal').value>=targetOrdinal).forEach(control=>{
const ordinal = control.get('ordinal');
const updatedOrdinalVal = ordinal.value+1;
ordinal.setValue(updatedOrdinalVal);
});
}
}else{
console.info('no siblings found');
}
elementSectionForm.get('ordinal').setValue(targetOrdinal);
targetSections.insert(targetOrdinal, elementSectionForm);
}else if(targetContainer.type === ToCEntryType.Page){
const pageId = targetContainer.form.get('id').value;
const rootform = targetContainer.form.root;
const sectionForm = element.form;
const parentSections = sectionForm.parent as FormArray;
let parentIndex = -1;
parentSections.controls.forEach((section,i )=>{
if(section.get('id').value === elementId){
parentIndex = i
}
})
if(parentIndex<0){
console.info('could not locate section in parents array');
this.dataNeedsRefresh.emit();
return;
}
//update parent sections ordinal
parentSections.controls.filter(section=>section.get('ordinal').value >= sectionForm.get('ordinal').value).forEach(section=>{
const ordinal = section.get('ordinal');
const updatedOrdinalVal = ordinal.value -1;
ordinal.setValue(updatedOrdinalVal);
})
parentSections.removeAt(parentIndex);
let position = 0;
if(targetContainer.subEntries){
position = targetContainer.subEntries.length;
}
//populate sections
const targetSectionsArray = rootform.get('sections') as FormArray;
if(sibling && sibling.id){
const siblingId= sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
let indx = -1;
targetContainer.subEntries.forEach((e,i)=>{//TOOD TO CHECK IF ORDINAL AND INDEX IS THE SAME
if(e.form.get('id').value === siblingId){
indx = i;
position = e.form.get('ordinal').value;
}
});
if(indx>=0 && position !=targetContainer.subEntries.length) {
// e.form.get('ordinal').setValue(i+1);
targetContainer.subEntries.filter(e=>e.form.get('ordinal').value >= position).forEach(e=>{
const ordinal = e.form.get('ordinal');
const updatedOrdinalVal = ordinal.value +1;
ordinal.setValue(updatedOrdinalVal);
});
}
}else{
console.info('No sibling found');
}
sectionForm.get('ordinal').setValue(position);
sectionForm.get('page').setValue(targetContainer.id);
targetSectionsArray.push(sectionForm);
}else{
console.info('Drag not support to specific container');
this.dataNeedsRefresh.emit();
return;
}
this.dataNeedsRefresh.emit();
break;
case ToCEntryType.Page:
if(targetId != this.ROOT_ID){
console.info('A page element can only be at top level');
this.dataNeedsRefresh.emit();
return;
}
const rootForm = element.form.root;
if(!rootForm){
console.info('Could not find root!')
this.dataNeedsRefresh.emit();
return;
}
const pages = rootForm.get('pages') as FormArray;
const pageForm = element.form;
let index = -1;
pages.controls.forEach((page,i)=>{
if(page.get('id').value === elementId){
index =i;
}
});
if(index<0){
console.info('Could not locate page on pages');
this.dataNeedsRefresh.emit();
return;
}
//ordinality
pages.controls.filter(page=> page.get('ordinal').value> pageForm.get('ordinal').value).forEach(page=>{
const ordinal = page.get('ordinal');
const ordinalVal = ordinal.value - 1;
ordinal.setValue(ordinalVal);
});
pages.removeAt(index);
let targetPosition = pages.length;
if(sibling){
const siblingId = sibling.id.replace(this.DRAGULA_ITEM_ID_PREFIX, '');
pages.controls.forEach((page,i)=>{
if(page.get('id').value === siblingId){
targetPosition = page.get('ordinal').value;
}
});
}
pageForm.get('ordinal').setValue(targetPosition);
//update ordinality
pages.controls.filter(page=> page.get('ordinal').value>= targetPosition).forEach(page=>{
const ordinal = page.get('ordinal');
const ordinalVal = ordinal.value +1;
ordinal.setValue(ordinalVal);
});
pages.insert(targetPosition, pageForm);
this.dataNeedsRefresh.emit();
break;
default:
console.error('Could not support moving objects for specific type of element');
this.dataNeedsRefresh.emit();
return;
}
});
drake.on('drag',(el,source)=>{
this.isDragging = true;
this.draggingItemId = (el.id as string).replace(this.DRAGULA_ITEM_ID_PREFIX, '');
});
drake.on('over',(el, container, source)=>{
try {
this.overcontainer = container.id;
} catch (error) {
this.overcontainer = null;
}
});
drake.on('dragend',(el)=>{
this.isDragging = false;
this.draggingItemId = null;
this.overcontainer = null;
});
}
overcontainer: string = null;
private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry{
if(!tocentries){
return null;
}
let tocEntryFound = tocentries.find(entry=>entry.id === id);
if(tocEntryFound){
return tocEntryFound;
}
for(let entry of tocentries){
const result = this._findTocEntryById(id, entry.subEntries);
if(result){
tocEntryFound = result;
break;
}
}
return tocEntryFound? tocEntryFound: null;
}
ngOnInit(): void {
//emit value every 500ms
// const source = interval(500);
// this.subscription = source.subscribe(val => {
// const headers = Array.from(this._document.querySelectorAll(this.headerSelectors)) as HTMLElement[];
// this.linksSubject.next(headers);
// });
// if (!this.links || this.links.length === 0) {
// this.linksSubject.asObservable()
// .pipe(distinctUntilChanged((p: HTMLElement[], q: HTMLElement[]) => JSON.stringify(p) == JSON.stringify(q)))
// .subscribe(headers => {
// const links: Array<Link> = [];
// if (headers.length) {
// let page;
// let section;
// let show
// for (const header of headers) {
// let name;
// let id;
// if (header.classList.contains('toc-page-header')) { // deprecated after removing stepper
// name = header.innerText.trim().replace(/^link/, '');
// id = header.id;
// page = header.id.split('_')[1];
// section = undefined;
// show = true;
// } else if (header.classList.contains('toc-section-header')) {
// name = header.childNodes[0].childNodes[0].childNodes[0].childNodes[0].childNodes[0].nodeValue.trim().replace(/^link/, '');
// id = header.id;
// page = header.id.split('.')[1];
// section = header.id;
// if (header.id.split('.')[4]) { show = false; }
// else { show = true; }
// } else if (header.classList.contains('toc-compositeField-header')) {
// name = (header.childNodes[0]).nodeValue.trim().replace(/^link/, '');
// id = header.id;
// // id = header.parentElement.parentElement.parentElement.id;
// show = false;
// }
// const { top } = header.getBoundingClientRect();
// links.push({
// name,
// id,
// type: header.tagName.toLowerCase(),
// top: top,
// active: false,
// page: page,
// section: section,
// show: show,
// selected: false
// });
// }
// }
// this.links = links;
// // Initialize selected for button next on dataset wizard component editor
// this.links.length > 0 ? this.links[0].selected = true : null;
// })
// }
}
ngOnChanges(changes: SimpleChanges) {
// if (!this.isActive && this.links && this.links.length > 0) {
// this.links.forEach(link => {
// link.selected = false;
// })
// this.links[0].selected = true;
// }
}
goToStep(link: Link) {
// this.stepFound.emit({
// page: link.page,
// section: link.section
// });
// this.currentLinks.emit(this.links);
// setTimeout(() => {
// const target = document.getElementById(link.id);
// target.scrollIntoView(true);
// var scrolledY = window.scrollY;
// if (scrolledY) {
// window.scroll(0, scrolledY - 70);
// }
// }, 500);
}
toggle(headerLink: Link) {
// const headerPage = +headerLink.name.split(" ", 1);
// let innerPage;
// for (const link of this.links) {
// link.selected = false;
// if (link.type === 'mat-expansion-panel') {
// innerPage = +link.name.split(".", 1)[0];
// if (isNaN(innerPage)) { innerPage = +link.name.split(" ", 1) }
// } else if (link.type === 'h5') {
// innerPage = +link.name.split(".", 1)[0];
// }
// if (headerPage === innerPage && (link.type !== 'mat-expansion-panel' || (link.type === 'mat-expansion-panel' && link.id.split(".")[4]))) {
// link.show = !link.show;
// }
// }
// headerLink.selected = true;
}
// getIndex(link: Link): number {
// return +link.id.split("_", 2)[1];
// }
itemClicked(item: ToCEntry){
//leaf node
this.itemClick.emit(item);
}
// propagateClickToParent(childIds:ToCEntry[], currentItem: ToCEntry){
// childIds.push(currentItem);
// this.itemClick.emit(childIds);
// }
addNewEntry(tce: ToCEntry){
this.newEntry.emit(tce);
}
deleteEntry(currentLink: ToCEntry){
this.removeEntry.emit(currentLink);
}
createNewEntry(foo: Foo){
this.createEntry.emit(foo);
}
onDataNeedsRefresh(){
this.dataNeedsRefresh.emit();
}
}
export interface LinkToScroll {
page: number;
section: number;
}