+
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.scss b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.scss
index 0c2851cd0..372eb669e 100644
--- a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.scss
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.scss
@@ -12,6 +12,28 @@
}
}
+.toc-and-content {
+ display: flex;
+ align-items: flex-start;
+ text-align: left;
+ max-width: 940px;
+ margin: 0 auto;
+}
+
+// table-of-contents {
+// top: 0px;
+// position: sticky;
+
+// Reposition on top of content on small screens and remove
+// sticky positioning
+// @media (max-width: 720px) {
+// order: -1;
+// position: inherit;
+// width: auto;
+// padding-left: 0;
+// }
+// }
+
// .ng-sidebar {
// width: 40%;
// }
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.ts
index 1dd3ab9a2..af61f5406 100644
--- a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.ts
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.component.ts
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { BaseComponent } from '../../../core/common/base/base.component';
import { Rule } from '../../../core/model/dataset-profile-definition/rule';
import { FormFocusService } from './form-focus/form-focus.service';
+import { LinkToScroll } from './tableOfContentsMaterial/table-of-contents';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
@Component({
@@ -31,6 +32,9 @@ export class DatasetDescriptionFormComponent extends BaseComponent implements On
// pageTrackByFn = (index, item) => item['id'];
@Input() datasetProfileId: String;
+ linkToScroll: LinkToScroll;
+
+ // uniqueId = Math.random().toString(36).substr(2, 9);
constructor(
private router: Router,
@@ -79,6 +83,14 @@ export class DatasetDescriptionFormComponent extends BaseComponent implements On
}
}
+ onStepFound(linkToScroll: LinkToScroll) {
+
+ if (linkToScroll.page >= 0) {
+ this.stepper.selectedIndex = linkToScroll.page;
+ }
+ this.linkToScroll = linkToScroll;
+ }
+
ngAfterViewInit() {
//this.visibilityRulesService.triggerVisibilityEvaluation();
// this.route.queryParams
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.module.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.module.ts
index fa3f68655..5ce506604 100644
--- a/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.module.ts
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/dataset-description-form.module.ts
@@ -9,12 +9,15 @@ import { FormSectionComponent } from './components/form-section/form-section.com
import { DatasetDescriptionFormComponent } from './dataset-description-form.component';
import { FormFocusService } from './form-focus/form-focus.service';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
+import { TableOfContentsModule } from './tableOfContentsMaterial/table-of-contents.module';
+import { TableOfContents } from './tableOfContentsMaterial/table-of-contents';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
- AutoCompleteModule
+ AutoCompleteModule,
+ TableOfContentsModule
],
declarations: [
DatasetDescriptionFormComponent,
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.html b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.html
new file mode 100644
index 000000000..a2bb11d99
--- /dev/null
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.html
@@ -0,0 +1,12 @@
+
+
Contents
+
+
+
+ {{link.name}}
+
+
+
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.module.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.module.ts
new file mode 100644
index 000000000..b28ec7804
--- /dev/null
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.module.ts
@@ -0,0 +1,12 @@
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {TableOfContents} from './table-of-contents';
+import {RouterModule} from '@angular/router';
+
+@NgModule({
+ imports: [CommonModule, RouterModule],
+ declarations: [TableOfContents],
+ exports: [TableOfContents],
+ entryComponents: [TableOfContents],
+})
+export class TableOfContentsModule { }
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.scss b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.scss
new file mode 100644
index 000000000..be241f387
--- /dev/null
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.scss
@@ -0,0 +1,58 @@
+:host {
+ font-size: 13px;
+ // Width is container width minus content width
+ width: 19%;
+ height: fit-content;
+ position: -webkit-sticky;
+ position: sticky;
+ // display: inline-flex;
+ align-self: flex-start;
+ top: 0;
+ padding-left: 25px;
+ box-sizing: border-box;
+}
+
+.docs-toc-container {
+ width: 100%;
+ padding: 5px 0 10px 10px;
+ cursor: pointer;
+ border-left: solid 4px #0c7489;
+
+ .docs-link {
+ color: rgba(0, 0, 0, 0.54);
+ // color: mat-color($app-blue-theme-foreground, secondary-text);
+ transition: color 100ms;
+
+ &:hover,
+ &.docs-active {
+ color: #0c7489;
+ // color: mat-color($primary, if($is-dark-theme, 200, default));
+ }
+ }
+}
+
+.docs-toc-heading {
+ margin: 0;
+ padding: 0;
+ font-size: 13px;
+ font-weight: bold;
+}
+
+span {
+ line-height: 16px;
+ margin: 8px 0 0;
+ position: relative;
+ text-decoration: none;
+ display: block;
+ text-overflow: ellipsis !important;
+ overflow: hidden;
+ color: rgba(0, 0, 0, 0.54);
+}
+
+.docs-level-mat-expansion-panel {
+ margin-left: 12px;
+}
+
+.docs-level-h5 {
+ margin-left: 24px;
+}
diff --git a/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.ts b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.ts
new file mode 100644
index 000000000..682fffcb1
--- /dev/null
+++ b/dmp-frontend/src/app/ui/misc/dataset-description-form/tableOfContentsMaterial/table-of-contents.ts
@@ -0,0 +1,221 @@
+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
();
+
+ _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 = [];
+ 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 = [];
+ 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;
+}