import { DOCUMENT } from '@angular/common'; import { Component, EventEmitter, Inject, OnInit, Output, Input, OnChanges } 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 { ToCEntry, ToCEntryType } from '../dataset-description.component'; import { FormArray, FormGroup } from '@angular/forms'; import { VisibilityRulesService } from '../visibility-rules/visibility-rules.service'; import { Rule } from '@app/core/model/dataset-profile-definition/rule'; 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: 'table-of-contents', styleUrls: ['./table-of-contents.scss'], templateUrl: './table-of-contents.html' }) export class TableOfContents extends BaseComponent implements OnInit, OnChanges { @Input() links: Link[]; container: string; headerSelectors = '.toc-page-header, .toc-section-header, .toc-compositeField-header'; @Output() stepFound = new EventEmitter(); @Output() currentLinks = new EventEmitter(); subscription: Subscription; linksSubject: Subject = new Subject(); @Input() isActive: boolean; tocentries: ToCEntry[] = null; @Input() TOCENTRY_ID_PREFIX = ''; // visibilityRules:Rule[] = []; @Input() visibilityRules:Rule[] = []; @Input() showErrors: boolean = false; @Input() selectedFieldsetId:string; private _tocentrySelected:ToCEntry = null; get tocentrySelected(){ return this.hasFocus?this._tocentrySelected: null; } set tocentrySelected(value){ this._tocentrySelected = value; } @Input() formGroup: FormGroup; @Input() hasFocus: boolean = false; show: boolean = false; constructor( @Inject(DOCUMENT) private _document: Document, public visibilityRulesService: VisibilityRulesService ) { super(); } ngOnInit(): void { if(this.formGroup){ this.tocentries = this.getTocEntries(this.formGroup.get('datasetProfileDefinition')); const fg = this.formGroup.get('datasetProfileDefinition'); this.visibilityRulesService.buildVisibilityRules(this.visibilityRules, fg); }else{ //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 = []; 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.selectedFieldsetId){ this.tocentrySelected = this._findTocEntryById(this.selectedFieldsetId,this.tocentries); } // 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]; // } private _buildRecursively(form: FormGroup,whatAmI:ToCEntryType):ToCEntry{ if(!form) return null; switch(whatAmI){ case ToCEntryType.Section: const sections = form.get('sections') as FormArray; const fieldsets = form.get('compositeFields') as FormArray; const tempResult:ToCEntry[] = []; if(sections &§ions.length){ sections.controls.forEach(section=>{ tempResult.push(this._buildRecursively(section as FormGroup, ToCEntryType.Section)); }); }else if(fieldsets && fieldsets.length){ fieldsets.controls.forEach(fieldset=>{ tempResult.push(this._buildRecursively(fieldset as FormGroup, ToCEntryType.FieldSet)); }); } return { form: form, id: form.get('id').value, label: form.get('title').value, numbering: '', subEntries:tempResult, subEntriesType: sections &§ions.length? ToCEntryType.Section: ToCEntryType.FieldSet, type: ToCEntryType.Section, ordinal: form.get('ordinal').value } case ToCEntryType.FieldSet: return { form: form, label:form.get('title').value, id: form.get('id').value, numbering:'s', subEntries:[], subEntriesType: ToCEntryType.Field, type: ToCEntryType.FieldSet, ordinal: form.get('ordinal').value } } } private _sortByOrdinal(tocentries: ToCEntry[]){ if(!tocentries || !tocentries.length) return; tocentries.sort(this._customCompare); tocentries.forEach(entry=>{ this._sortByOrdinal(entry.subEntries); }); } private _customCompare(a,b){ return a.ordinal - b.ordinal; } private _calculateNumbering(tocentries: ToCEntry[], depth:number[] = []){ if(!tocentries || !tocentries.length){ return; } let prefixNumbering = depth.length? depth.join('.'): ''; if(depth.length) prefixNumbering = prefixNumbering+"."; tocentries.forEach((entry,i)=>{ entry.numbering = prefixNumbering + (i+1); this._calculateNumbering(entry.subEntries, [...depth, i+1]) }); } getTocEntries(form): ToCEntry[] { if (form == null) { return []; } const result: ToCEntry[] = []; //build parent pages (form.get('pages') as FormArray).controls.forEach((pageElement, i) => { result.push({ id: i+'id', label: pageElement.get('title').value, type: ToCEntryType.Page, form: pageElement, numbering: (i + 1).toString(), subEntriesType: ToCEntryType.Section, subEntries:[], ordinal: pageElement.get('ordinal').value } as ToCEntry) }); result.forEach((entry,i)=>{ const sections = entry.form.get('sections') as FormArray; sections.controls.forEach(section=>{ const tempResults = this._buildRecursively(section as FormGroup,ToCEntryType.Section); entry.subEntries.push(tempResults); }); }); this._sortByOrdinal(result); //calculate numbering this._calculateNumbering(result); return result; } onToCentrySelected(entry: ToCEntry){ this.tocentrySelected = entry; // console.log('entry selected', entry); } public seekToFirstElement(){//only on tocentry mode if(this.tocentries && this.tocentries.length){ this.tocentrySelected = this.tocentries[0]; } } private _findTocEntryById(id: string, tocentries: ToCEntry[]): ToCEntry{ if(!tocentries || !tocentries.length){ 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; } } export interface LinkToScroll { page: number; section: number; }