UNPKG

sparnatural

Version:

Visual client-side SPARQL query builder and knowledge graph exploration tool

591 lines (543 loc) 24.6 kB
import { getSettings } from "../../../../../settings/defaultSettings"; import { Config } from "../../../../../ontologies/SparnaturalConfig"; import { SelectedVal } from "../../../../SelectedVal"; import HTMLComponent from "../../../../HtmlComponent"; import ISparnaturalSpecification from "../../../../../spec-providers/ISparnaturalSpecification"; import StartClassGroup from "./StartClassGroup"; import EndClassGroup from "./EndClassGroup"; import { WidgetValue } from "../../../../widgets/AbstractWidget"; import { I18n } from "../../../../../settings/I18n"; import { DagIfc, DagNodeIfc} from "../../../../../dag/Dag"; import ISpecificationEntity from "../../../../../spec-providers/ISpecificationEntity"; import UiuxConfig from "../../../../IconsConstants"; import tippy from "tippy.js"; import { TOOLTIP_CONFIG } from "../../../../../settings/defaultSettings"; export interface JsonDagRow { label: string, id: string, tooltip: string, color: string, icon: string, highlightedIcon:string, count: number, disabled: boolean, childs: Array<JsonDagRow>, } export interface DagWidgetDefaultValue { value: string, path: string, } export interface DagWidgetValue { value: string, path: string, icon: string, } export interface BreadCrumData { parentsLabels: Array<string>, returnPath: string, } /** * Builds a selector for a class based on provided domainId, by reading the * configuration. If the given domainId is null, this means we populate the first * class selection (starting point) so reads all classes that are domains of any property. * **/ export class HierarchicalClassSelectBuilder extends HTMLComponent { specProvider: ISparnaturalSpecification; html: JQuery<HTMLElement>; //htmlCoreSelect: JQuery<HTMLElement>; htmlSelectUiUx: JQuery<HTMLElement>; htmlSelectUiUxBreadCrum: JQuery<HTMLElement>; htmlBreadCrumBack: JQuery<HTMLElement>; htmlBreadCrumPath: JQuery<HTMLElement>; htmlBreadCrumPathHome: JQuery<HTMLElement>; htmlBreadCrumPathParentsPrefix: JQuery<HTMLElement>; htmlBreadCrumPathParents: JQuery<HTMLElement>; htmlBreadCrumParentLabel: JQuery<HTMLElement>; htmlSelectUiUxLists: JQuery<HTMLElement>; //htmlCurentValue: JQuery<HTMLElement>; breadcrumItems: Array<string> = []; hierarchyData: Array<JsonDagRow> = []; value: string; valuePath: string; hasIcon: boolean = false; defaultValue: DagWidgetDefaultValue = { value: '', path: '' }; widgetValue: DagWidgetValue = { value: '', path: '', icon: '' }; constructor(ParentComponent: HTMLComponent, specProvider: ISparnaturalSpecification, hierarchyData: Array<JsonDagRow>, defaultValue: DagWidgetDefaultValue) { super("HierarchicalClassSelectBuilder", ParentComponent, null); this.specProvider = specProvider; this.hierarchyData = hierarchyData; this.defaultValue = defaultValue; } render(): this { super.render(); return this; } moveHasAncestor(el:any) { el.classList.remove('active-pane') ; el.classList.add('active-pane-hide-left'); if (el.getAttribute('parent') == '') { this.htmlSelectUiUxLists[0].classList.add('root-display') ; } } moveHasChild(el:any) { el.classList.add('active-pane') ; this.htmlSelectUiUxLists[0].classList.remove('root-display') ; let parentItem = this.getParentLiToUl(el) ; let liParentLabel = parentItem.querySelector('.item-label').innerText ; this.downAncestorInParentLabel(liParentLabel) ; //test if liparent is not on root ul let ulOfLiParent = parentItem.closest('ul') ; if (ulOfLiParent.getAttribute('parent') != '') { //need to add ancestor item label to path, need to animate first acestor let liAncestor = this.getParentLiToUl(ulOfLiParent); let liAncestorLabel = liAncestor.querySelector('.item-label').innerText ; this.downAncestorInBreadcrumPath(liAncestorLabel) ; } else { this.htmlBreadCrumParentLabel[0].classList.add('display'); } } moveHasLeaveChild(el:any) { el.classList.remove('active-pane') ; } moveHasEnterAncestor(el:any) { el.classList.remove('active-pane-hide-left'); el.classList.add('active-pane') ; if (el.getAttribute('parent') == '') { this.htmlSelectUiUxLists[0].classList.add('root-display') ; } this.upAncestorInParentLabel() ; } isFlatHierarchy():boolean { let isFlat = true ; this.hierarchyData.forEach(element => { if (element.childs.length > 0) { isFlat = false ; } }) ; return isFlat } withIcons() { return this.hasIcon ; } getParentLiToUl(el:any):any { let uniqPath = el.getAttribute('parent') ; return this.htmlSelectUiUx[0].querySelector(`li[list-child-id="${uniqPath}"]`) ; } getInput() { return this.html ; } downAncestorInBreadcrumPath(newAcestorLabel:any) { let parentList = this.htmlBreadCrumPathParents[0].querySelectorAll('span:not(.onHiddenUp)') ; if (parentList.length == 2) { parentList[0].classList.add('onHiddenUp'); this.htmlBreadCrumPathHome[0].classList.add('onHiddenUp'); this.htmlBreadCrumPathParentsPrefix[0].classList.add('onHiddenUp'); } let NewAncestor = $(`<span class="ancestor-item">&nbsp;/ ${newAcestorLabel}</span>`) ; //this.htmlBreadCrumPathParents[0].innerHTML = '' ; this.htmlBreadCrumPathParents.append(NewAncestor) ; setTimeout(function() { NewAncestor[0].classList.toggle('appened'); }, 10); } /*upAncestorInBreadcrumPath() { let parentList = this.htmlBreadCrumPathParents[0].querySelectorAll('span') ; if (parentList.length == 2) { this.htmlBreadCrumPathParents[0].querySelector('span:last-of-type').classList.remove('appened') ; let htmlBreadCrumPathParents = this.htmlBreadCrumPathParents[0] ; setTimeout(function() { htmlBreadCrumPathParents.querySelector('span:last-of-type').remove() ; }, 500); return true; } }*/ downAncestorInParentLabel(newParentLabel:any) { let new_item = $(`<span>${newParentLabel}</span>`) ; if(this.htmlBreadCrumParentLabel[0].querySelector('span:last-of-type') != null) { this.htmlBreadCrumParentLabel[0].querySelector('span:last-of-type').classList.add('move-left') ; this.htmlBreadCrumParentLabel[0].querySelector('span:last-of-type').classList.remove('move-to-display') ; } this.htmlBreadCrumParentLabel.append(new_item) ; let htmlBreadCrumParentLabel = this.htmlBreadCrumParentLabel[0] ; setTimeout(function() { htmlBreadCrumParentLabel.querySelector('span:last-of-type').classList.add('move-to-display') }, 1); } upAncestorInParentLabel() { let parentList = this.htmlBreadCrumParentLabel[0].querySelectorAll('span') ; if (parentList.length == 1) { let htmlBreadCrumParentLabel = this.htmlBreadCrumParentLabel[0] ; setTimeout(function() { htmlBreadCrumParentLabel.querySelector('span:last-of-type').remove() ; }, 500); return true; } if(this.htmlBreadCrumParentLabel[0].querySelector('span:last-of-type') != null) { this.htmlBreadCrumParentLabel[0].querySelector('span:last-of-type').classList.add('move-right') ; this.htmlBreadCrumParentLabel[0].querySelector('span:last-of-type').classList.remove('move-to-display') ; } let indexToDisplay = parentList.length - 2; if (indexToDisplay >= 0) { parentList[indexToDisplay].classList.remove('move-left') ; parentList[indexToDisplay].classList.add('move-to-display') ; this.htmlBreadCrumPathParents[0].querySelector('span:last-of-type').classList.remove('appened') ; let htmlBreadCrumPathParents = this.htmlBreadCrumPathParents[0] ; setTimeout(function() { htmlBreadCrumPathParents.querySelector('span:last-of-type').remove() ; }, 500); let pathHiddenParentList = this.htmlBreadCrumPathParents[0].querySelectorAll('span.onHiddenUp') ; if (pathHiddenParentList.length > 0) { pathHiddenParentList[(pathHiddenParentList.length - 1)].classList.remove('onHiddenUp'); } if (this.htmlBreadCrumPathParents[0].querySelectorAll('span.onHiddenUp').length == 0) { this.htmlBreadCrumPathHome[0].classList.remove('onHiddenUp'); this.htmlBreadCrumPathParentsPrefix[0].classList.remove('onHiddenUp'); } } let htmlBreadCrumParentLabel = this.htmlBreadCrumParentLabel[0] ; setTimeout(function() { htmlBreadCrumParentLabel.querySelector('span:last-of-type').remove() ; }, 500); } /*setCurrentContent() { let entity = this.specProvider.getEntity(this.widgetValue.value) ; let entity_icon = entity.getIcon() ; let icon = `` ; if (entity_icon != '') { icon = `<span><i class="fa ${entity_icon} fa-fw"></i></span>` ; } this.htmlCurentValue.html(`${icon} ${entity.getLabel()} `) ; this.htmlCurentValue[0].classList.add('selected') ; }*/ initSelectUiUxListsHeight() { let listToDisplay = this.htmlSelectUiUx[0].querySelector('ul[parent].active-pane') ; //let allLists = this.htmlSelectUiUx[0].querySelectorAll('ul[parent]') ; let listToDisplayLiCount = this.htmlSelectUiUx[0].querySelectorAll('ul[parent].active-pane li').length ; let breadcrumHeight = 55; if (listToDisplay.classList.contains('root')) { breadcrumHeight = 0 ; } let newHeight = 40 * listToDisplayLiCount + breadcrumHeight ; if (newHeight > 400) { newHeight = 400 ; } this.htmlSelectUiUxLists[0].style.height = newHeight+'px' ; this.htmlSelectUiUx[0].querySelectorAll('ul[parent]').forEach((el:HTMLElement) => el.style.height = (newHeight - breadcrumHeight) +'px' ); } initClassSelector() { /*Search class with childs*/ let li_elements = this.htmlSelectUiUx[0].querySelectorAll('li '); //let ul_elements = this.htmlSelectUiUx[0].querySelectorAll('ul') ; for (var element of li_elements) { //let class_id = element.getAttribute('value') ; let class_childs_list_id = element.getAttribute('list-child-id') ; //Search in all ul if have parent attr with this class_id let hasChild = this.htmlSelectUiUx[0].querySelector('ul[parent="'+class_childs_list_id+'"]'); if (hasChild !== null) { element.classList.add("have-childs"); element.querySelector('span.item-traverse').addEventListener( "click", (e: CustomEvent) => { let class_target = (e.target as any).closest('li').getAttribute('list-child-id') ; let parent_ul_return = (e.target as any).closest('ul').getAttribute('parent') ; this.htmlSelectUiUx[0].querySelectorAll('ul[parent].active-pane').forEach(el => this. moveHasAncestor(el)); this.moveHasChild(this.htmlSelectUiUx[0].querySelector('ul[parent="'+class_target+'"]')); this.initSelectUiUxListsHeight() ; this.htmlBreadCrumBack[0].setAttribute('return-target', parent_ul_return) ; } ); } } let li_elements_disabled = this.htmlSelectUiUx[0].querySelectorAll('li.disabled'); //let ul_elements = this.htmlSelectUiUx[0].querySelectorAll('ul') ; for (var element of li_elements_disabled) { //let class_id = element.getAttribute('value') ; let class_childs_list_id = element.getAttribute('list-child-id') ; //Search in all ul if have parent attr with this class_id let hasChild = this.htmlSelectUiUx[0].querySelector('ul[parent="'+class_childs_list_id+'"]'); if (hasChild !== null) { element.classList.add("have-childs"); element.querySelector('span.item-sel').addEventListener( "click", (e: CustomEvent) => { let class_target = (e.target as any).closest('li').getAttribute('list-child-id') ; let parent_ul_return = (e.target as any).closest('ul').getAttribute('parent') ; this.htmlSelectUiUx[0].querySelectorAll('ul[parent].active-pane').forEach(el => this. moveHasAncestor(el)); this.moveHasChild(this.htmlSelectUiUx[0].querySelector('ul[parent="'+class_target+'"]')); this.initSelectUiUxListsHeight() ; this.htmlBreadCrumBack[0].setAttribute('return-target', parent_ul_return) ; } ); } } //All breadcrum return action this.htmlBreadCrumBack[0].addEventListener( "click", (e: CustomEvent) => { let class_target = (e.target as any).closest('.htmlBreadCrumBack').getAttribute('return-target') ; this.htmlSelectUiUx[0].querySelectorAll('ul[parent].active-pane').forEach(el => this.moveHasLeaveChild(el)); this.moveHasEnterAncestor(this.htmlSelectUiUx[0].querySelector('ul[parent="'+class_target+'"]')); this.initSelectUiUxListsHeight() ; if (class_target != '') { let new_tareget_back = this.getParentLiToUl(this.htmlSelectUiUx[0].querySelector('ul[parent="'+class_target+'"]')).closest('ul').getAttribute('parent') ; this.htmlBreadCrumBack[0].setAttribute('return-target', new_tareget_back) ; } } ); // Listen click for selectable class let li_elements_sel = this.htmlSelectUiUx[0].querySelectorAll('li.enabled .item-sel'); li_elements_sel.forEach(element => { element.addEventListener( "click", (e: CustomEvent) => { let class_id = (e.target as any).closest('li').getAttribute('data-id') ; let class_path = (e.target as any).closest('li').getAttribute('data-parent') ; let class_icon = (e.target as any).closest('li').getAttribute('data-icon') ; this.widgetValue = { value: class_id, path: class_path, icon: class_icon, }; this.html[0].dispatchEvent( new CustomEvent("widgetItemSelected", { bubbles: true, detail: this.widgetValue, }) ); //this.setCurrentContent() ; } ); }); } initBreadCrum() { this.htmlBreadCrumBack = $('<div class="htmlBreadCrumBack"></div>'); let BackContent = $(`<span>${UiuxConfig.ICON_DAG_ARROW_LEFT}</span>`); this.htmlBreadCrumPath = $('<div class="htmlBreadCrumPath"></div>'); this.htmlBreadCrumPathHome = $('<div class="htmlBreadCrumPathHome">Tout</div>'); this.htmlBreadCrumPathParentsPrefix = $('<div class="htmlBreadCrumPathParentsPrefix">...</div>'); this.htmlBreadCrumPathParents = $('<div class="htmlBreadCrumPathParents"></div>'); this.htmlBreadCrumParentLabel = $('<div class="htmlBreadCrumParentLabel"></div>'); //First block for backward action let leftBlock = $('<div class="htmlBreadCrumLeft"></div>') ; this.htmlBreadCrumBack.append(BackContent) ; leftBlock.append(this.htmlBreadCrumBack) ; this.htmlSelectUiUxBreadCrum.append(leftBlock) ; //Second block for navigation path display let rightBlock = $('<div class="htmlBreadCrumRight"></div>') ; this.htmlBreadCrumPath.append(this.htmlBreadCrumPathHome) ; this.htmlBreadCrumPath.append(this.htmlBreadCrumPathParentsPrefix) ; this.htmlBreadCrumPath.append(this.htmlBreadCrumPathParents) ; rightBlock.append(this.htmlBreadCrumPath) ; rightBlock.append(this.htmlBreadCrumParentLabel) ; this.htmlSelectUiUxBreadCrum.append(rightBlock) ; } buildClassSelectList(arrayNode: Array<JsonDagRow>, parentClass: string, path: string, breadCrumData: BreadCrumData) { if(arrayNode.length > 0) { let Ul = $(`<ul class="`+parentClass+`" parent="${path}"></ul>`) ; let i = 0 ; let total_count = 0; let hasTraverseItem = false ; let hasNoEnabledWithChilds = true ; arrayNode.forEach(element => { let htmlItem = this.buildClassSelectItem(element, path) ; let child_path = path+'$'+element.id ; Ul.append(htmlItem) ; i++ ; if (element.childs.length > 0) { let element_breadCrumData = null ; hasTraverseItem = true ; if (element.disabled == false) { hasNoEnabledWithChilds = false ; } element_breadCrumData = JSON.parse(JSON.stringify(breadCrumData)); element_breadCrumData.parentsLabels.push(element.label) ; element_breadCrumData.returnPath = path ; this.buildClassSelectList(element.childs, 'root-child', child_path, element_breadCrumData) ; htmlItem.attr('list-child-id', path+'$'+element.id) ; } if (element.count != undefined) { total_count += element.count ; } }); let breadcrumHeight = 55; if (hasTraverseItem) { Ul[0].classList.add('hasTraverseItem') ; } if (hasNoEnabledWithChilds) { Ul[0].classList.add('hasNoEnabledWithChilds') ; } if (Ul[0].classList.contains('root')) { breadcrumHeight = 0 ; this.htmlSelectUiUxLists[0].style.height = (40*i)+'px' ; } if (i > 10) { Ul[0].style.height = 400+'px' ; } else { Ul[0].style.height = (40*i + breadcrumHeight)+'px' ; } if (total_count > 0) { let li_count = Ul[0].querySelectorAll('li') ; let prop_coef = 100 / total_count ; li_count.forEach(element => { let count = parseInt(element.getAttribute('data-count')) ; let span_gradiant = element.querySelector<HTMLElement>('.item-count>span') ; let span_number = element.querySelector<HTMLElement>('.item-count-number') ; let pourcent = prop_coef * count ; span_gradiant.style.width = pourcent + '%'; span_number.innerHTML = '<span>'+this.getCountDisplay(count);+ ' %</span>'; }); } else { // explicitely set te width of the gradient to 0% if there is no count // so that it is invisible let li_count = Ul[0].querySelectorAll('li') ; li_count.forEach(element => { let span_gradiant = element.querySelector<HTMLElement>('.item-count>span') ; span_gradiant.style.width = '0%'; }); } this.htmlSelectUiUxLists.append(Ul); } } buildClassSelectItem(element: JsonDagRow, parent:string) { let enabledClass = element.disabled == true ? ` disabled` : `enabled` ; let icon = `` ; let iconValue = ``; if (element.icon != '') { // TODO : handle iconHover icon = (element.icon.startsWith("fa"))?`<span><i class="fa ${element.icon} fa-fw"></i></span>`:`<span><img src="${element.icon}" /></span>` ; iconValue = `data-icon="${element.icon}"` ; this.hasIcon = true; } let selected = this.defaultValue.value == element.id ? 'selected="selected"' : ""; //Add tooltip content if description let tooltip = element.tooltip != undefined ? `${element.tooltip}` : ""; // Prepand label if contain more than 32 caracteres if (element.label.length > 32) { let sep = tooltip != '' ? ` - ` : "" ; tooltip = `${element.label}${sep}${tooltip}` ; } // set typpy attribute if tooltip content if (tooltip != '') { tooltip = `data-tippy-content="${tooltip}"` ; } // set count attribute let count = element.count ; if (element.count == undefined) { count = 0 ; } let count_attr = `data-count="${count}"` ; let countUiUX = `<span class="item-count"><span></span></span><span class="item-count-number"></span>`; let color = element.color != undefined ? `data-color="${element.color}"` : ""; let item = $(`<li value="${element.id}" data-id="${element.id}" data-parent="`+ parent +`" ${selected} ${color} ${iconValue} ${count_attr} class="${enabledClass}">${countUiUX}<span class="item-sel" ${tooltip}><span class="label-icon">${icon}</span><span class="item-label">${element.label}</span></span><span class="item-traverse">${UiuxConfig.ICON_DAG_ARROW_RIGHT}</span></li>`) ; return item ; } displayClassSelector() { this.htmlSelectUiUx.addClass('open') ; this.parentComponent.html[0].classList.add('focus') ; } hideClassSelector() { this.parentComponent.html[0].classList.remove('focus') ; this.htmlSelectUiUx.removeClass('open') ; } setValue(selectedValue:string) { this.defaultValue = { value: selectedValue, path: '', } } submitSelectedValue() { if (this.defaultValue.value != '') { //this.setCurrentContent() ; let entity = this.specProvider.getEntity(this.defaultValue.value) ; let entity_icon = entity.getIcon() ; this.widgetValue = { value: this.defaultValue.value, path: this.defaultValue.path, icon: entity_icon, }; this.html[0].dispatchEvent( new CustomEvent("change", { bubbles: true, detail: {value: this.defaultValue.value, valuePath: this.defaultValue.path}, }) ); this.hideClassSelector() ; /*let clickEvent = new Event( 'click', { bubbles: true } ); //let changeEvent = new Event( 'change', { bubbles: true } ); let allWithId = this.htmlSelectUiUx[0].querySelectorAll(`li[value="${this.defaultValue.value}"] .item-sel`); allWithId[0].dispatchEvent(clickEvent); //this.htmlInputValueClass[0].dispatchEvent(changeEvent);*/ } } buildClassSelectFromJson() { //init select options array let selectOptionList: Array<string> = []; this.htmlSelectUiUx = $(`<div class="htmlSelectUiUx"></div>`) ; this.htmlSelectUiUxLists = $(`<div class="htmlSelectUiUxLists root-display"></div>`) ; this.htmlSelectUiUxBreadCrum = $(`<div class="htmlSelectUiUxBreadCrum"></div>`) ; //this.htmlCurentValue = $('<span class="current">Chercher des ressources</span>') ; let initBreadcrumData: BreadCrumData = { parentsLabels: ['Tout'], returnPath: '', } this.initBreadCrum() ; this.htmlSelectUiUxLists.append(this.htmlSelectUiUxBreadCrum) ; console.log(this.hierarchyData) ; this.buildClassSelectList(this.hierarchyData, 'root active-pane', '', initBreadcrumData) ; this.html = $('<div></div>'); //let currentWrapper = $('<div class="currentWrapper"></div>') ; //currentWrapper.append(this.htmlCurentValue) ; //this.htmlSelectUiUx.append(currentWrapper) ; this.htmlSelectUiUx.append(this.htmlSelectUiUxLists) ; this.html.append(this.htmlSelectUiUx) ; this.initClassSelector() ; if (this.isFlatHierarchy()) { this.htmlSelectUiUx[0].classList.add('isFlat') ; this.parentComponent.html[0].classList.add('selectorIsFlat') ; } if (this.hasIcon) { this.htmlSelectUiUx[0].classList.add('hasIcon') ; this.parentComponent.html[0].classList.add('selectorHasIcon') ; } this.displayClassSelector() ; tippy( this.html[0].querySelectorAll(".item-sel[data-tippy-content]"), TOOLTIP_CONFIG ); this.html[0].addEventListener( "widgetItemSelected", (e: CustomEvent) => { this.html[0].dispatchEvent( new CustomEvent("change", { bubbles: true, detail: e.detail, }) ); this.hideClassSelector() ; } ); return this.html.children(); } getCountDisplay(count:number) { let countToDiplay = '' ; switch (true) { case count >= 1000000: countToDiplay = '~'+Math.round(count / 1000000)+ 'M' ; break; case count >= 1000: countToDiplay = '~'+Math.round(count / 1000)+ 'K' ; break; default: countToDiplay = count+'' ; } return countToDiplay ; } } export default HierarchicalClassSelectBuilder;