model-navigator-standalone
Version:
Standalone MDF graph model visualizer
252 lines (250 loc) • 8.76 kB
JavaScript
import "core-js/modules/es.array.flat-map.js";
import "core-js/modules/es.array.includes.js";
import "core-js/modules/es.array.unscopables.flat-map.js";
import "core-js/modules/es.global-this.js";
import "core-js/modules/es.string.includes.js";
import "core-js/modules/web.dom-collections.iterator.js";
/* eslint-disable no-undef */
import { createSlice } from '@reduxjs/toolkit';
import _ from 'lodash';
import { filterConfig, controlVocabConfig } from "../../components/ModelNavigator/Config/nav.config";
const initialState = {
configs: {
filterConfig,
controlVocabConfig
},
hiddenNodes: [],
haveFilters: false,
allTaggedNodes: [],
fullTagMatrix: null,
displayedTagMatrix: {},
checkboxState: null,
filtersSelected: []
};
const filterSlice = createSlice({
name: 'filter',
initialState,
reducers: {
filtersInitRequested(state, action) {
const model = action.payload;
globalThis.model = model;
const tags = globalThis.config.facetFilters.map(filt => filt.tag);
if (tags) {
// does the model have any of these tags?
let tagset = tags.flatMap(tag => model.tag_kvs(tag));
if (tagset && tagset.length > 0) {
state.haveFilters = true;
if (!state.fullTagMatrix) {
// initialize allTaggedNodes
state.fullTagMatrix = calcNodeTagMatrix(model, globalThis.config.facetFilters, []);
state.displayedTagMatrix = _.cloneDeep(state.fullTagMatrix);
let taggedNodes = new Set();
Object.keys(state.fullTagMatrix).forEach(key => {
Object.keys(state.fullTagMatrix[key]).forEach(val => {
state.fullTagMatrix[key][val].nodes.forEach(hndl => {
taggedNodes.add(hndl);
});
});
});
state.allTaggedNodes = [];
taggedNodes.forEach(hndl => {
state.allTaggedNodes.push(hndl);
});
}
if (!state.checkboxState) {
let acc = {};
globalThis.config.facetFilters.flatMap(
// eslint-disable-line no-undef
filt => {
filt.checkboxItems.map(item => {
acc["checkbox_".concat(item.tag, "_").concat(item.value)] = false;
});
});
state.checkboxState = acc;
}
} else {
state.haveFilters = false;
}
} else {
state.haveFilters = false;
}
},
hiddenNodesUpdated(state, action) {
// hiddenNodes: an Array of node handles (strings)
const {
model,
hiddenNodes
} = action.payload;
// replacement
state.hiddenNodes = hiddenNodes;
state.displayedTagMatrix = calcNodeTagMatrix(model, globalThis.config.facetFilters, hiddenNodes);
},
filterSelectorToggled(state, action) {
// checkBoxInfo - filterConfig element plus isChecked
// dispatch after modelReceived
const {
tag,
value
} = action.payload;
if (tag !== undefined || value !== undefined) {
// update checkboxes
state.checkboxState["checkbox_".concat(tag, "_").concat(value)] = !state.checkboxState["checkbox_".concat(tag, "_").concat(value)];
const filter = {
tag,
value
};
let fs = [];
// update filtersSelected
fs = _.cloneDeep(state.filtersSelected);
if (state.checkboxState["checkbox_".concat(tag, "_").concat(value)]) {
fs.push(filter);
} else {
fs = fs.filter(item => !(item.tag === tag && item.value === value));
}
// determine nodes to hide
state.hiddenNodes = calcHiddenNodes(fs, state.fullTagMatrix, state.allTaggedNodes);
state.filtersSelected = fs;
// calculate counts
if (state.hiddenNodes.length === 0) {
state.displayedTagMatrix = _.cloneDeep(state.fullTagMatrix);
} else {
state.displayedTagMatrix = calcNodeTagMatrix(globalThis.model, globalThis.config.facetFilters, state.hiddenNodes);
}
}
},
filterGroupCleared(state, action) {
const {
facetItem
} = action.payload;
// update checkboxes
facetItem.checkboxItems.forEach(item => {
state.checkboxState["checkbox_".concat(item.tag, "_").concat(item.value)] = false;
});
// update filtersSelected
const fs = _.cloneDeep(state.filtersSelected);
state.filtersSelected = fs.filter(item => item.tag !== _.toLower(facetItem.tag));
// determine nodes to hide
state.hiddenNodes = calcHiddenNodes(state.filtersSelected, state.fullTagMatrix, state.allTaggedNodes);
// calculate counts
if (state.hiddenNodes.length === 0) {
state.displayedTagMatrix = _.cloneDeep(state.fullTagMatrix);
} else {
state.displayedTagMatrix = calcNodeTagMatrix(globalThis.model, globalThis.config.facetFilters, state.hiddenNodes);
}
},
allFiltersCleared(state, action) {
// reset all checkboxes
if (state.checkboxState) {
Object.keys(state.checkboxState).forEach(key => {
state.checkboxState[key] = false;
});
}
// reset filtersSelected
state.filtersSelected = [];
// clear hidden nodes
state.hiddenNodes = [];
// reset displayed counts to totals
state.displayedTagMatrix = _.cloneDeep(state.fullTagMatrix);
}
}
});
function calcHiddenNodes(filtersSelected, fullTagMatrix, allTaggedNodes) {
if (filtersSelected.length === 0) {
return [];
}
let showNodes = [];
let nodes = {};
let universe = new Set();
let arrUniverse = [];
filtersSelected.forEach(_ref => {
let {
tag,
value
} = _ref;
if (!nodes[tag]) {
nodes[tag] = new Set();
}
// OR/union within group (= tag key)
fullTagMatrix[tag][value].nodes.forEach(hndl => {
nodes[tag].add(hndl);
universe.add(hndl);
});
});
// AND/intersection between groups
universe.forEach(hndl => {
arrUniverse.push(hndl);
});
showNodes = arrUniverse.filter(
// node should be shown if its handle appears in every group set
// (i.e., if it is in the intersection of all the group sets)
hndl => Object.values(nodes).every(set => set.has(hndl)));
// return allTaggedNodes - showNodes = hiddenNodes
return allTaggedNodes.filter(hndl => !showNodes.includes(hndl));
}
function calcNodeTagMatrix(model, facetFilters, hiddenNodes) {
let nc = {};
const facetTypes = facetFilters.map(facetFilter => facetFilter.type);
facetTypes.filter(type => type.entity === 'node').forEach(type => {
model.tag_kvs(type.tag).forEach(_ref2 => {
let [key, val] = _ref2;
const taggedNodes = model.tagged_items(key, val).filter(ent => ent._kind === 'Node' && !hiddenNodes.includes(ent.handle));
if (!nc[key]) {
nc[key] = {};
}
let info = {
nodes: [],
count: 0
};
if (taggedNodes) {
info.nodes = taggedNodes.map(n => n.handle);
info.count = taggedNodes.length;
}
nc[key][val] = info;
});
});
facetTypes.filter(type => type.entity === 'prop').forEach(type => {
model.tag_kvs(type.tag).forEach(_ref3 => {
let [key, val] = _ref3;
const taggedProps = model.tagged_items(key, val).filter(ent => ent._kind === 'Property');
let info = {
nodes: [],
count: 0
};
if (taggedProps) {
let propsCount = 0;
let nodeSet = new Set();
taggedProps.forEach(prop => {
// if prop.owner is null => the Property is defined but doesn't
// appear under any Node in MDF.
if (prop.owner && prop.owner._kind === 'Node' && !hiddenNodes.includes(prop.owner.handle)) {
propsCount += 1;
nodeSet.add(prop.owner.handle);
}
});
info.count = propsCount;
nodeSet.forEach(n => {
info.nodes.push(n);
});
}
if (!nc[key]) {
nc[key] = {};
}
nc[key][val] = info;
});
});
return nc;
}
export const {
filtersInitRequested,
hiddenNodesUpdated,
filterSelectorToggled,
filterGroupCleared,
allFiltersCleared
} = filterSlice.actions;
export default filterSlice.reducer;
export const selectHaveFilters = state => state.filter.haveFilters;
export const selectHiddenNodes = state => state.filter.hiddenNodes;
export const selectDisplayedTagMatrix = state => state.filter.displayedTagMatrix;
export const selectFullTagMatrix = state => state.filter.fullTagMatrix;
export const selectCheckboxState = state => state.filter.checkboxState;
export const selectFiltersSelected = state => state.filter.filtersSelected;