sparnatural
Version:
Visual client-side SPARQL query builder and knowledge graph exploration tool
264 lines (233 loc) • 11.8 kB
text/typescript
import { SelectAllValue } from "../components/builder-section/groupwrapper/criteriagroup/edit-components/EditComponents";
import ObjectPropertyGroup from "../components/builder-section/groupwrapper/criteriagroup/objectpropertygroup/ObjectPropertyGroup";
import { OptionTypes } from "../components/builder-section/groupwrapper/criteriagroup/optionsgroup/OptionsGroup";
import ClassTypeId from "../components/builder-section/groupwrapper/criteriagroup/startendclassgroup/ClassTypeId";
import EndClassGroup from "../components/builder-section/groupwrapper/criteriagroup/startendclassgroup/EndClassGroup";
import StartClassGroup from "../components/builder-section/groupwrapper/criteriagroup/startendclassgroup/StartClassGroup";
import GroupWrapper from "../components/builder-section/groupwrapper/GroupWrapper";
import NoOrderBtn from "../components/buttons/NoOrderBtn";
import { SelectedVal } from "../components/SelectedVal";
import SparnaturalComponent from "../components/SparnaturalComponent";
import { WidgetValue } from "../components/widgets/AbstractWidget";
import { Branch, ISparJson, Order, VariableTerm } from "../generators/json/ISparJson";
export default class QueryLoader{
static sparnatural: SparnaturalComponent;
static query: ISparJson
static loadQuery(query:ISparJson){
this.query = query
// set Sparnatural quiet so it does not emit the update callbacks
this.sparnatural.setQuiet(true);
// first reset the current query
this.sparnatural.BgWrapper.resetCallback();
// build Sparnatural query
// use a deep copy of the query to avoid modifying the original copy
let clone = JSON.parse(JSON.stringify(query)) as ISparJson;
let varMapping = this.#buildSparnatural(this.sparnatural, clone.branches);
// set the correct variable names
this.#updateNamingOfVariables(varMapping)
// set the correct ordering of the draggables
this.#updateOrderingOfVariables()
// then reset the quiet flag
this.sparnatural.setQuiet(false);
// trigger query generation at then
this.sparnatural.html[0].dispatchEvent(
new CustomEvent("generateQuery")
);
this.sparnatural.html[0].dispatchEvent(
new CustomEvent("redrawBackgroundAndLinks")
);
}
static #buildSparnatural(sparnatural: SparnaturalComponent, branches: Array<Branch>):Map<string,string> {
let varMapping = new Map<string,string>();
if(branches?.length === 0) throw Error('No Branches on query detected')
// first build the rootGroupWrapper
let rootGrpWrapper =
sparnatural.BgWrapper.componentsList.rootGroupWrapper;
// build the root groupwrapper and remove from branches array
let rootBranch = branches.shift();
let localVarMapping = this.#buildCriteriaGroup(rootGrpWrapper, rootBranch);
localVarMapping.forEach((value: string, key:string) => { varMapping.set(key, value); });
// by default, the very first start class group will be selected
// if the first variable is *not* selected, then unselect it
const firstStartClassVal = { type: rootBranch.line.sType, variable: rootBranch.line.s };
if(!QueryLoader.#hasSelectedVar(this.query.variables,firstStartClassVal.variable)) {
// click on first eye btn to unselect it
this.#clickOn((rootGrpWrapper.CriteriaGroup.StartClassGroup.inputSelector as ClassTypeId)?.selectViewVariableBtn?.widgetHtml)
}
let parent = rootGrpWrapper;
branches.forEach((b) => {
this.#clickOn(parent.CriteriaGroup.ActionsGroup.actions.ActionAnd.btn);
let localVarMapping = this.#buildCriteriaGroup(parent.andSibling, b);
localVarMapping.forEach((value: string, key:string) => { varMapping.set(key, value); });
parent = parent.andSibling;
});
return varMapping;
}
static #buildCriteriaGroup(grpWarpper: GroupWrapper, branch: Branch):Map<string,string> {
let varMapping = new Map<string,string>();
// set StartClassVal only if there wasn't one set by the parent (e.g whereChild andSibling have it already set)
const startClassVal = { type: branch.line.sType, variable: branch.line.s };
if (!grpWarpper.CriteriaGroup.StartClassGroup.startClassVal.type) {
//set StartClassGroup
this.#setSelectedValue(
grpWarpper.CriteriaGroup.StartClassGroup,
branch.line.sType
);
}
// also set the variable name
// grpWarpper.CriteriaGroup.StartClassGroup.startClassVal = startClassVal;
varMapping.set(grpWarpper.CriteriaGroup.StartClassGroup.startClassVal.variable, branch.line.s);
// set EndClassGroup
const endClassVal = { type: branch.line.oType, variable: branch.line.o };
this.#setSelectedValue(grpWarpper.CriteriaGroup.EndClassGroup, branch.line.oType);
// transparently set the variable name to the one in the query
// before we click on the select button, so that the column is selected with the proper name
// grpWarpper.CriteriaGroup.EndClassGroup.endClassVal = endClassVal;
varMapping.set(grpWarpper.CriteriaGroup.EndClassGroup.endClassVal.variable, branch.line.o);
//set ObjectPropertyGroup
this.#setSelectedValue(
grpWarpper.CriteriaGroup.ObjectPropertyGroup,
branch.line.p
);
// set WidgetValues
branch.line.values.forEach((v) => {
const parsedVal: WidgetValue = grpWarpper.CriteriaGroup.EndClassGroup.editComponents.widgetWrapper.widgetComponent.parseInput(v)
// if there are multiple values rendered, click first the 'plus' btn, to add more values
if(grpWarpper.CriteriaGroup.endClassWidgetGroup.widgetValues.length > 0) this.#clickOn(grpWarpper.CriteriaGroup.endClassWidgetGroup.addWidgetValueBtn.html)
grpWarpper.CriteriaGroup.EndClassGroup.editComponents.widgetWrapper.widgetComponent.triggerRenderWidgetVal(parsedVal)
});
// if there is no value, and no children, set an "Any" value
if(branch.line.values.length == 0 && branch.children.length == 0) {
grpWarpper.CriteriaGroup.EndClassGroup.editComponents.onSelectAll();
}
// trigger option state
this.#triggerOptions(grpWarpper, branch);
if (branch.children.length > 0) {
this.#clickOn(
grpWarpper.CriteriaGroup.EndClassGroup.editComponents.actionWhere.btn
);
// first child
let localVarMapping = this.#buildCriteriaGroup(grpWarpper.whereChild, branch.children.shift());
localVarMapping.forEach((value:string,key: string) => varMapping.set(key, value));
// the rest of the children are AND connected
let parent = grpWarpper.whereChild;
branch.children.forEach((c) => {
this.#clickOn(parent.CriteriaGroup.ActionsGroup.actions.ActionAnd.btn);
let localVarMapping = this.#buildCriteriaGroup(parent.andSibling, c);
localVarMapping.forEach((value:string,key: string) => varMapping.set(key, value));
parent = parent.andSibling;
});
}
// select if the var is viewed (eye btn)
this.#setSelectViewVariableBtn(
startClassVal,
grpWarpper.CriteriaGroup.StartClassGroup,
endClassVal,
grpWarpper.CriteriaGroup.EndClassGroup
)
return varMapping;
}
static #triggerOptions(grpWrapper: GroupWrapper, branch: Branch) {
if (branch.notExists && grpWrapper.optionState != OptionTypes.NOTEXISTS) {
this.#clickOn(grpWrapper.CriteriaGroup.OptionsGroup.optionalArrow.widgetHtml);
this.#clickOn(grpWrapper.CriteriaGroup.OptionsGroup.NotExistsComponent.html);
}
if (branch.optional && grpWrapper.optionState != OptionTypes.OPTIONAL) {
this.#clickOn(grpWrapper.CriteriaGroup.OptionsGroup.optionalArrow.widgetHtml);
this.#clickOn(grpWrapper.CriteriaGroup.OptionsGroup.OptionalComponent.html);
}
}
// set the value for an inputSelector and trigger the corresponding event
static #setSelectedValue(
component: StartClassGroup | EndClassGroup | ObjectPropertyGroup,
value: string
) {
// set the values to the ClassTypeId | ObjectPropertyTypeId component
component.inputSelector.setSelected(value) ;
component.inputSelector.submitSelected() ;
}
// this method checks if the eye btn was enabled in the loaded query
static #setSelectViewVariableBtn(
startClassVal:SelectedVal,
startClassComponent:StartClassGroup,
endClassVal:SelectedVal,
endClassComponent:EndClassGroup
){
if(QueryLoader.#hasSelectedVar(this.query.variables, endClassVal.variable)) {
// click on eye btn
this.#clickOn((endClassComponent.inputSelector as ClassTypeId)?.selectViewVariableBtn?.widgetHtml)
}
}
static #updateOrderingOfVariables(){
const varMenu =this.sparnatural.variableSection.variableOrderMenu
this.query.variables.forEach(v=>{
varMenu.draggables.forEach(d=>{
let varName;
if("expression" in v) {
varName = v.expression.expression.value
} else {
varName = v.value;
}
if(d.state.selectedVariable.variable === varName){
varMenu.removeDraggableByVarName(varName)
let newDraggable = varMenu.addDraggableComponent(d.state.selectedVariable);
// if this was an aggregated variable, load it by calling a specific function of the draggable
if("expression" in v) {
newDraggable.loadAggregatedVariable(v)
}
}
})
})
// once variables are sorted, synchronize with the actionstore
// this.sparnatural.actionStore.variables = this.sparnatural.variableSection.listVariables();
const variableSortOption =this.sparnatural.variableSection.variableSortOption;
if(this.query.order == Order.ASC) {
variableSortOption.changeSortOrderCallBack(Order.ASC);
} else if(this.query.order == Order.DESC) {
variableSortOption.changeSortOrderCallBack(Order.DESC);
} else {
variableSortOption.changeSortOrderCallBack(Order.NOORDER);
}
}
// map of the names of variables in the current query to their target name
static #updateNamingOfVariables(varNameMapping:Map<string,string>){
const varMenu = this.sparnatural.variableSection.variableOrderMenu
this.query.variables.forEach(v=>{
varMenu.draggables.forEach(d=>{
var varToConsider: VariableTerm;
if("variable" in v) {
// this is an aggregation
// at this stage we are setting the variable name to the *original variable*
// the final aggregated var name will be set when the draggable will be updated
varToConsider = v.expression.expression
} else {
// nominal case
varToConsider = v;
}
if(varNameMapping.get(d.state.selectedVariable.variable) === varToConsider.value){
d.setVarName(varToConsider.value);
}
})
})
}
static #hasSelectedVar(vars:ISparJson["variables"], varName:string):boolean {
var result:boolean = false;
vars.forEach(v => {
if("expression" in v) {
// this is an aggregation
// consider the variable *being aggregated*, not the result of the aggregation
if(v.expression.expression.value == varName) result = true;
}
if("value" in v) {
if(v.value == varName) result = true;
}
});
return result;
}
static #clickOn(el: JQuery<HTMLElement>) {
el[0].dispatchEvent(new Event("click"));
}
static setSparnatural(sparnatural:SparnaturalComponent){
this.sparnatural = sparnatural
}
}