argos/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.ts

222 lines
5.9 KiB
TypeScript
Raw Normal View History

import { DOCUMENT } from '@angular/common';
import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { fromEvent, Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { link } from 'fs';
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;
}
@Component({
selector: 'table-of-contents',
styleUrls: ['./table-of-contents.scss'],
templateUrl: './table-of-contents.html'
})
export class TableOfContents implements OnInit, AfterViewInit, OnDestroy {
@Input() links: Link[] = [];
@Input() container: string;
@Input() headerSelectors = '.toc-page-header, .toc-section-header, .toc-copositeField-header';
@Output() stepFound = new EventEmitter<LinkToScroll>();
_rootUrl = this._router.url.split('#')[0];
private _scrollContainer: any;
private _destroyed = new Subject();
private _urlFragment = '';
private selectedLinkId: string;
constructor(private _router: Router,
private _route: ActivatedRoute,
private _element: ElementRef,
@Inject(DOCUMENT) private _document: Document) {
this._router.events.pipe(takeUntil(this._destroyed)).subscribe((event) => {
if (event instanceof NavigationEnd) {
const rootUrl = _router.url.split('#')[0];
if (rootUrl !== this._rootUrl) {
this.links = this.createLinks();
this._rootUrl = rootUrl;
}
}
});
this._route.fragment.pipe(takeUntil(this._destroyed)).subscribe(fragment => {
this._urlFragment = fragment;
const target = document.getElementById(this._urlFragment);
if (target) {
target.scrollIntoView();
}
});
}
ngOnInit(): void {
// On init, the sidenav content element doesn't yet exist, so it's not possible
// to subscribe to its scroll event until next tick (when it does exist).
Promise.resolve().then(() => {
this._scrollContainer = this.container ?
this._document.querySelectorAll(this.container)[0] : window;
if (this._scrollContainer) {
fromEvent(this._scrollContainer, 'scroll').pipe(
takeUntil(this._destroyed),
debounceTime(10))
.subscribe(() => this.onScroll());
}
});
}
ngAfterViewInit() {
this.updateScrollPosition();
}
ngOnDestroy(): void {
this._destroyed.next();
}
updateScrollPosition(): void {
this.links = this.createLinks();
const target = document.getElementById(this._urlFragment);
if (target) {
target.scrollIntoView();
}
}
/** Gets the scroll offset of the scroll container */
private getScrollOffset(): number | void {
const { top } = this._element.nativeElement.getBoundingClientRect();
if (typeof this._scrollContainer.scrollTop !== 'undefined') {
return this._scrollContainer.scrollTop + top;
} else if (typeof this._scrollContainer.pageYOffset !== 'undefined') {
return this._scrollContainer.pageYOffset + top;
}
}
private createLinks(): Link[] {
const links: Array<Link> = [];
const headers =
Array.from(this._document.querySelectorAll(this.headerSelectors)) as HTMLElement[];
if (headers.length) {
for (const header of headers) {
// const step = 0;
// remove the 'link' icon name from the inner text
const name = header.innerText.trim().replace(/^link/, '');
const { top } = header.getBoundingClientRect();
// links.push({
// name,
// // step,
// type: header.tagName.toLowerCase(),
// top: top,
// id: header.id,
// active: false,
// section: section
// });
}
}
return links;
}
private onScroll(): void {
// for (let i = 0; i < this.links.length; i++) {
// this.links[i].active = this.isLinkActive(this.links[i], this.links[i + 1]);
// }
}
// private isLinkActive(currentLink: any, nextLink: any): boolean {
// A link is considered active if the page is scrolled passed the anchor without also
// being scrolled passed the next link
// const scrollOffset = this.getScrollOffset();
// return scrollOffset >= currentLink.top && !(nextLink && nextLink.top < scrollOffset);
// }
getLinks() {
const links: Array<Link> = [];
const headers =
Array.from(this._document.querySelectorAll(this.headerSelectors)) as HTMLElement[];
if (headers.length) {
let page;
let section;
for (const header of headers) {
let name;
let id;
if (header.classList.contains('toc-page-header')) {
name = header.innerText.trim().replace(/^link/, '');
id = header.id;
page = header.id.split('_')[1];
section = undefined;
} 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;
} else if (header.classList.contains('toc-copositeField-header')) {
name = (header.childNodes[0]).nodeValue.trim().replace(/^link/, '');
id = header.id;
// id = header.parentElement.parentElement.parentElement.id;
}
const { top } = header.getBoundingClientRect();
links.push({
name,
id,
type: header.tagName.toLowerCase(),
top: top,
active: this.selectedLinkId == id ? true : false,
page: page,
section: section
});
}
}
this.links = links;
return links;
}
goToStep(link: Link) {
this.selectedLinkId = link.id;
this.stepFound.emit({
page: link.page,
section: link.section
});
setTimeout(() => {
const target = document.getElementById(link.id);
target.scrollIntoView(true);
var scrolledY = window.scrollY;
if (scrolledY) {
window.scroll(0, scrolledY - 70);
}
}, 200);
}
}
export interface LinkToScroll {
page: number;
section: number;
}