Dataset description and table-of-contents works with tocentries.

This commit is contained in:
Kristian Ntavidi 2021-03-17 11:08:59 +02:00
parent 720fa60f9b
commit ae378899e2
13 changed files with 571 additions and 78 deletions

View File

@ -54,7 +54,7 @@
<div (click)="changeStep(0)" *ngIf="datasetInfoValid()" class="main-info" [ngClass]="{'active': this.step === 0}">0. {{'DMP-EDITOR.STEPPER.MAIN-INFO' | translate}} (<mat-icon class="done-icon">done</mat-icon>)</div>
<div class="row toc-pane-container" #boundary (click)="changeStep(1)">
<div #spacer></div>
<table-of-contents class="toc-pane-container" [links]="links" [boundary]="boundary" [spacer]="spacer" [isActive]="step !== 0" stickyThing (stepFound)="onStepFound($event)" (currentLinks)="getLinks($event)"></table-of-contents>
<table-of-contents [hasFocus]="step === 1" [formGroup]="formGroup" *ngIf="formGroup && formGroup.get('datasetProfileDefinition')" [links]="links" [boundary]="boundary" [spacer]="spacer" [isActive]="step !== 0" stickyThing (stepFound)="onStepFound($event)" (currentLinks)="getLinks($event)" [visibilityRules]="formGroup.get('datasetProfileDefinition').get('rules').value"></table-of-contents>
</div>
</div>
</div>
@ -89,14 +89,14 @@
</div>
<div class="row">
<!-- <div class="row">
<div class="col-12">
<button (click)="printForm()">Print form</button>
</div>
<div class="col-12">
<button (click)="printFormValue()">Print form value</button>
</div>
</div>
</div> -->
<!-- <div class="main-content">
<div class="container-fluid">

View File

@ -277,6 +277,7 @@
display: flex;
flex-direction: column;
height: calc(100vh - 246px);
max-width: 366px;
}
.stepper-options {
@ -316,6 +317,8 @@
.stepper-list {
.toc-pane-container {
padding-left: 0.2rem;
overflow-x: hidden;
span {
text-align: left;
font-weight: 400;

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute, Router } from '@angular/router';
@ -45,6 +45,7 @@ import { SaveType } from '@app/core/common/enum/save-type';
import { DatasetWizardModel } from '@app/core/model/dataset/dataset-wizard';
import { MatomoService } from '@app/core/services/matomo/matomo-service';
import { HttpClient } from '@angular/common/http';
import { ToCEntry, ToCEntryType } from '@app/ui/misc/dataset-description-form/dataset-description.component';
@Component({
selector: 'app-dataset-wizard-component',
@ -886,18 +887,28 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
}
public changeStep(index: number, dataset?: FormControl) {
if(this.step != index){ //view is changing
this.resetScroll();
}
this.step = index;
this.resetScroll();
}
public nextStep() {
this.step = this.step < this.maxStep ? this.step + 1 : this.step;
this.resetScroll();
if(this.step < this.maxStep){//view is changing
this.step +=1;
this.resetScroll();
}
// this.step = this.step < this.maxStep ? this.step + 1 : this.step;
}
public previousStep() {
this.step = this.step !== 0 ? this.step - 1 : this.step;
this.resetScroll();
if(this.step !== 0){
this.resetScroll();
this.step =-1;
}
// this.step = this.step !== 0 ? this.step - 1 : this.step;
}
private resetScroll() {
@ -945,4 +956,133 @@ export class DatasetWizardComponent extends BaseComponent implements OnInit, IBr
printFormValue(){
console.log(this.formGroup.value);
}
// tocentries;
// this.tocentries = this.getTocEntries(this.formGroup.get('datasetProfileDefinition')); //TODO
// get tocentries(){
// const form = this.formGroup.get('datasetProfileDefinition')
// if(!form) return null;
// return this.getTocEntries(this.formGroup.get('datasetProfileDefinition'));
// }
// 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 &&sections.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 &&sections.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;
// }
}

View File

@ -75,7 +75,7 @@
<ng-container *ngSwitchCase="tocentriesType.FieldSet">
<!-- FIELDSET CASE -->
<div *ngFor="let fieldsetEntry of tocentry.subEntries; let i = index;" class="col-12">
<div *ngFor="let fieldsetEntry of tocentry.subEntries; let i = index;" class="col-12" [id]="fieldsetEntry.id">
<!-- <div *ngIf="isElementVisible(compositeField)" class="row"> -->
<!-- *ngIf="this.visibilityRulesService.checkElementVisibility(compositeFieldFormGroup.get('id').value)" -->
<div class="row" *ngIf="this.visibilityRulesService.checkElementVisibility(fieldsetEntry.form.get('id').value) && this.visibilityRulesService.scanIfChildsOfCompositeFieldHasVisibleItems(fieldsetEntry.form)">

View File

@ -16,22 +16,34 @@
</div>
</div>
</ng-container>
<!-- TOCENTRIES -->
<ng-template #toctemplate>
<!--FIRST LEVEL ALWAYS PAGE-->
<div *ngFor="let pageEntry of tocentries; let z = index;">
<h4 >
{{pageEntry.numbering}} - {{pageEntry.label}}
</h4>
<mat-accordion [multi]="true">
<div *ngFor="let sectionEntry of pageEntry.subEntries; let i = index;">
<div class="row">
<app-form-section class="col-12" [tocentry]="sectionEntry" [path]="z+1"
[pathName]="'pages.'+z+'.sections.'+i" [datasetProfileId]="datasetProfileId"
[linkToScroll]="linkToScroll"></app-form-section>
</div>
<div *ngFor="let pageEntry of tocentries; let z = index;">
<mat-expansion-panel [expanded]="true">
<mat-expansion-panel-header>
<mat-panel-title>
<h4 class="panel-title toc-page-header">
{{pageEntry.numbering}}. {{pageEntry.label |uppercase}}
</h4>
</mat-panel-title>
</mat-expansion-panel-header>
<!--
<h4 class="toc-page-header">
</h4> -->
<div *ngFor="let sectionEntry of pageEntry.subEntries; let i = index;">
<div class="row">
<app-form-section class="col-12" [tocentry]="sectionEntry" [path]="z+1"
[pathName]="'pages.'+z+'.sections.'+i" [datasetProfileId]="datasetProfileId"
[linkToScroll]="linkToScroll"></app-form-section>
</div>
</div>
</mat-expansion-panel>
</div>
</div>
</mat-accordion>
</ng-template>
</div>

View File

@ -44,7 +44,7 @@ export class DatasetDescriptionComponent extends BaseComponent implements OnInit
// this.formChanged.emit(val);
// });
// }
this.tocentries = this.getTocEntries();
}

View File

@ -0,0 +1,38 @@
<div *ngFor="let entry of tocentries; index as idx">
<!-- check if is visible -->
<ng-container *ngIf="visibilityRulesService.checkElementVisibility(entry.id)">
<!-- Is fieldset and has no visible inputs -->
<ng-container *ngIf="!(entry.type === tocEntryTypeEnum.FieldSet && !visibilityRulesService.scanIfChildsOfCompositeFieldHasVisibleItems(entry.form))">
<span class="table-entry"
(click)="toggleExpand(idx);navigateToFieldSet(entry, $event); onEntrySelected(entry)"
[ngStyle]="calculateStyle(entry)"
[ngClass]="calculateClass(entry)"
>
{{entry.numbering}}. {{entry.label}}
<!-- <ng-container *ngIf="entry.subEntries && entry.subEntries.length && !expandChildren[idx]">
<small>
({{entry.subEntries.length}})
</small>
</ng-container> -->
</span>
<!-- <div class="table-entry-container">
</div> -->
<div class="internal-table">
<table-of-contents-internal
[tocentries]="entry.subEntries"
*ngIf="entry.subEntries && entry.subEntries.length && expandChildren[idx]"
(entrySelected)="onEntrySelected($event)"
[selected]="selected">
</table-of-contents-internal>
</div>
</ng-container>
</ng-container>
</div>

View File

@ -0,0 +1,29 @@
.internal-table{
margin-left: 1.2em;
// width: inherit;
}
// .table-entry-container{
// // overflow: hidden;
// // width: inherit;
// }
.table-entry{
cursor: pointer;
// display: block;
text-overflow: ellipsis;
white-space: nowrap;
// overflow: hidden;
color: rgba(0, 0, 0, 0.54);
transition: color 100ms;
}
.table-entry:hover{
background-color: #ececec;
border-radius: 6px;
}
.selected {
color: #212121 !important;
font-weight: 700 !important;
opacity: 1 !important;
}

View File

@ -0,0 +1,86 @@
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 { ToCEntry, ToCEntryType } from '../../dataset-description.component';
import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service';
import { Rule } from '@app/core/model/dataset-profile-definition/rule';
@Component({
selector: 'table-of-contents-internal',
styleUrls: ['./table-of-contents-internal.scss'],
templateUrl: './table-of-contents-internal.html'
})
export class TableOfContentsInternal implements OnInit {
@Input() tocentries: ToCEntry[] = null;
@Input() selected: ToCEntry = null;
// @Input() visibilityRules:Rule[] = [];
@Output() entrySelected = new EventEmitter<ToCEntry>();
expandChildren:boolean[];
tocEntryTypeEnum = ToCEntryType;
constructor(public visibilityRulesService: VisibilityRulesService){
}
ngOnInit(): void {
// console.log('component created');
if(this.tocentries){
this.expandChildren = this.tocentries.map(()=>false);
}
}
ngOnChanges(changes: SimpleChanges) {
// if (!this.isActive && this.links && this.links.length > 0) {
// this.links.forEach(link => {
// link.selected = false;
// })
// this.links[0].selected = true;
// }
}
toggleExpand(index){
this.expandChildren[index] = !this.expandChildren[index];
// console.log(this.expandChildren);
}
navigateToFieldSet(entry:ToCEntry, event){
if(entry.type === ToCEntryType.FieldSet){
const fieldSetId = entry.id;
const element = document.getElementById(fieldSetId);
if(element){
element.scrollIntoView({behavior:'smooth'});
// event.stopPropagation();
}
}
}
onEntrySelected(entry:ToCEntry){
this.entrySelected.emit(entry);
}
calculateStyle(entry: ToCEntry){
const style = {};
style['font-size'] = entry.type ===this.tocEntryTypeEnum.FieldSet? '.9em': '1em';
return style;
}
calculateClass(entry:ToCEntry){
if(this.selected && entry.id === this.selected.id){
return{'selected': true};
}
return {};
}
}

View File

@ -9,3 +9,30 @@
<!-- </nav> -->
</div>
</div>
<!--
<ng-container *ngIf="tocentries">
<div class="internal-table-outer col-12">
<table-of-contents-internal [tocentries]="tocentries"
(entrySelected)="onToCentrySelected($event)"
[selected]="tocentrySelected"
>
</table-of-contents-internal>
</div>
</ng-container> -->
<div *ngIf="tocentries" class="docs-toc-container">
<div class="scroll-container col-12">
<table-of-contents-internal [tocentries]="tocentries"
(entrySelected)="onToCentrySelected($event)"
[selected]="tocentrySelected"
>
</table-of-contents-internal>
</div>
</div>

View File

@ -2,11 +2,14 @@ import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {TableOfContents} from './table-of-contents';
import {RouterModule} from '@angular/router';
import { TableOfContentsInternal } from './table-of-contents-internal/table-of-contents-internal';
import { VisibilityRulesService } from '../visibility-rules/visibility-rules.service';
@NgModule({
imports: [CommonModule, RouterModule],
declarations: [TableOfContents],
declarations: [TableOfContents, TableOfContentsInternal],
exports: [TableOfContents],
entryComponents: [TableOfContents],
providers:[VisibilityRulesService]
})
export class TableOfContentsModule { }

View File

@ -68,3 +68,8 @@ span {
.docs-level-h5 {
margin-left: 24px;
}
// .internal-table-outer{
// padding-left: 1.1em;
// width: 100%;
// }

View File

@ -5,6 +5,10 @@ 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*/
@ -39,72 +43,99 @@ export class TableOfContents extends BaseComponent implements OnInit {
linksSubject: Subject<HTMLElement[]> = new Subject<HTMLElement[]>();
@Input() isActive: boolean;
tocentries: ToCEntry[] = null;
// visibilityRules:Rule[] = [];
@Input() visibilityRules:Rule[] = [];
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) {
@Inject(DOCUMENT) private _document: Document,
public visibilityRulesService: VisibilityRulesService
) {
super();
}
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.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<Link> = [];
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;
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
});
}
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;
})
this.links = links;
// Initialize selected for button next on dataset wizard component editor
this.links.length > 0 ? this.links[0].selected = true : null;
})
}
}
}
@ -158,6 +189,125 @@ export class TableOfContents extends BaseComponent implements OnInit {
// 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 &&sections.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 &&sections.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);
}
}
export interface LinkToScroll {