Files
privacy.sexy/src/presentation/Scripts/ScriptsTree/SelectableTree/SelectableTree.vue
2020-07-19 02:26:56 +01:00

158 lines
6.3 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<span>
<span v-if="initialLiquourTreeNodes != null && initialLiquourTreeNodes.length > 0">
<tree :options="liquorTreeOptions"
:data="initialLiquourTreeNodes"
v-on:node:checked="nodeSelected($event)"
v-on:node:unchecked="nodeSelected($event)"
ref="treeElement"
>
<span class="tree-text" slot-scope="{ node }">
<Node :data="convertExistingToNode(node)" />
</span>
</tree>
</span>
<span v-else>Nooo 😢</span>
</span>
</template>
<script lang="ts">
import { Component, Prop, Vue, Emit, Watch } from 'vue-property-decorator';
import LiquorTree, { ILiquorTreeNewNode, ILiquorTreeExistingNode, ILiquorTree, ILiquorTreeOptions } from 'liquor-tree';
import Node from './Node.vue';
import { INode } from './INode';
import { convertExistingToNode, toNewLiquorTreeNode } from './NodeTranslator';
export type FilterPredicate = (node: INode) => boolean;
/** Wrapper for Liquor Tree, reveals only abstracted INode for communication */
@Component({
components: {
LiquorTree,
Node,
},
})
export default class SelectableTree extends Vue {
@Prop() public filterPredicate?: FilterPredicate;
@Prop() public filterText?: string;
@Prop() public selectedNodeIds?: ReadonlyArray<string>;
@Prop() public initialNodes?: ReadonlyArray<INode>;
public initialLiquourTreeNodes?: ILiquorTreeNewNode[] = null;
public liquorTreeOptions = this.getDefaults();
public convertExistingToNode = convertExistingToNode;
public mounted() {
if (this.initialNodes) {
const initialNodes = this.initialNodes.map((node) => toNewLiquorTreeNode(node));
if (this.selectedNodeIds) {
recurseDown(initialNodes,
(node) => node.state.checked = this.selectedNodeIds.includes(node.id));
}
this.initialLiquourTreeNodes = initialNodes;
} else {
throw new Error('Initial nodes are null or empty');
}
if (this.filterText) {
this.updateFilterText(this.filterText);
}
}
public nodeSelected(node: ILiquorTreeExistingNode) {
this.$emit('nodeSelected', convertExistingToNode(node));
return;
}
@Watch('filterText')
public updateFilterText(filterText: |string) {
const api = this.getLiquorTreeApi();
if (!filterText) {
api.clearFilter();
} else {
api.filter('filtered'); // text does not matter, it'll trigger the filterPredicate
}
}
@Watch('selectedNodeIds')
public setSelectedStatus(selectedNodeIds: ReadonlyArray<string>) {
if (!selectedNodeIds) {
throw new Error('Selected nodes are undefined');
}
const newNodes = updateCheckedState(this.getLiquorTreeApi().model, selectedNodeIds);
this.getLiquorTreeApi().setModel(newNodes);
/* Alternative:
this.getLiquorTreeApi().recurseDown((node) => {
node.states.checked = selectedNodeIds.includes(node.id);
});
Problem: Does not check their parent if all children are checked, because it does not
trigger update on parent as we work with scripts not categories. */
/* Alternative:
this.getLiquorTreeApi().recurseDown((node) => {
if(selectedNodeIds.includes(node.id)) { node.select(); } else { node.unselect(); }
});
Problem: Emits nodeSelected() event again which will cause an infinite loop. */
}
private getLiquorTreeApi(): ILiquorTree {
if (!this.$refs.treeElement) {
throw new Error('Referenced tree element cannot be found. Probably it\'s not rendered?');
}
return (this.$refs.treeElement as any).tree;
}
private getDefaults(): ILiquorTreeOptions {
return {
multiple: true,
checkbox: true,
checkOnSelect: true,
autoCheckChildren: true,
parentSelect: false,
keyboardNavigation: true,
deletion: (node) => !node.children || node.children.length === 0,
filter: {
matcher: (query: string, node: ILiquorTreeExistingNode) => {
if (!this.filterPredicate) {
throw new Error('Cannot filter as predicate is null');
}
return this.filterPredicate(convertExistingToNode(node));
},
emptyText: '🕵Hmm.. Can not see one 🧐',
},
};
}
}
function recurseDown(
nodes: ReadonlyArray<ILiquorTreeNewNode>,
handler: (node: ILiquorTreeNewNode) => void) {
for (const node of nodes) {
handler(node);
if (node.children) {
recurseDown(node.children, handler);
}
}
}
function updateCheckedState(
oldNodes: ReadonlyArray<ILiquorTreeExistingNode>,
selectedNodeIds: ReadonlyArray<string>): ReadonlyArray<ILiquorTreeNewNode> {
const result = new Array<ILiquorTreeNewNode>();
for (const oldNode of oldNodes) {
const newState = oldNode.states;
newState.checked = selectedNodeIds.some((id) => id === oldNode.id);
const newNode: ILiquorTreeNewNode = {
id: oldNode.id,
text: oldNode.data.text,
data: {
documentationUrls: oldNode.data.documentationUrls,
isReversible: oldNode.data.isReversible,
},
children: oldNode.children == null ? [] :
updateCheckedState(oldNode.children, selectedNodeIds),
state: newState,
};
result.push(newNode);
}
return result;
}
</script>