svelte-tree-view
Version:
Display JSON objects in a customizable tree-view
154 lines (153 loc) • 5.47 kB
JavaScript
export function createNode(index, key, value, depth, parent) {
const path = parent ? [...parent.path, index] : [];
return {
id: `[${path.join(',')}]`,
index,
key,
value,
depth,
collapsed: true,
type: getValueType(value),
path,
parentId: parent ? parent.id : null,
circularOfId: null,
children: []
};
}
export function getValueType(value) {
if (Array.isArray(value)) {
return 'array';
}
else if (value instanceof Map) {
return 'map';
}
else if (value instanceof Set) {
return 'set';
}
else if (value instanceof Date) {
return 'date';
}
else if (value === null) {
return 'null';
}
else {
return typeof value;
}
}
// From redux-dev-tools
// case 'Object':
// case 'Error':
// case 'Array':
// case 'Iterable':
// case 'Map':
// case 'Set':
// case 'MapEntry':
// case 'Number':
// return undefined;
// case 'String':
// return raw => `"${raw}"`;
// case 'Boolean':
// return raw => (raw ? 'true' : 'false');
// case 'Date':
// return raw => raw.toISOString();
// case 'Null':
// return () => 'null';
// case 'Undefined':
// return () => 'undefined';
// case 'Function':
// case 'Symbol':
// export function objType(obj: any) {
// const type = Object.prototype.toString.call(obj).slice(8, -1)
// if (type === 'Object') {
// if (typeof obj[Symbol.iterator] === 'function') {
// return 'Iterable'
// }
// return obj.constructor.name
// }
// return type
// }
function getChildren(value, type) {
switch (type) {
case 'array':
return value.map((v, i) => [i.toString(), v]);
case 'map':
// eslint-disable-next-line no-case-declarations
const entries = Array.from(value.entries());
return entries.map(([key, value], i) => [
`[map entry ${i}]`,
{
'[key]': key,
'[value]': value
}
]);
case 'set':
return Array.from(value.values()).map((v, i) => [`[set entry ${i}]`, v]);
case 'object':
return Object.entries(value);
default:
return [];
}
}
function shouldRecurseChildren(node, parent, iteratedValues, opts) {
if (!parent) {
// The root node's children should always be recursed
return true;
}
else if (node.collapsed && (parent === null || parent === void 0 ? void 0 : parent.collapsed)) {
// If the node's parent is uncollapsed the node's children should still be recursed
// in order to compute its value properly eg "{} 4 keys" and to place clickable arrow caret.
// Only when the node is completely hidden it should not be recursed
return false;
}
else if (!opts.stopCircularRecursion) {
return true;
}
else if (opts.isCircularNode) {
return opts.isCircularNode(node, iteratedValues);
}
else if (node.type === 'object' || node.type === 'array') {
const existingNodeWithValue = iteratedValues.get(node.value);
if (existingNodeWithValue && node.id !== existingNodeWithValue.id) {
node.circularOfId = existingNodeWithValue.id;
return false;
}
iteratedValues.set(node.value, node);
}
return true;
}
export function recurseObjectProperties(index, key, value, depth, ensureNotCollapsed, parent, treeMap, oldTreeMap, iteratedValues, recomputeExpandNode, opts) {
var _a;
if (((_a = opts.omitKeys) === null || _a === void 0 ? void 0 : _a.includes(key)) || (opts.maxDepth && depth > opts.maxDepth)) {
return null;
}
const node = createNode(index, key, value, depth, parent);
const oldNode = oldTreeMap.get(node.id);
if (ensureNotCollapsed) {
// Used to ensure that either root node is always uncollapsed or when uncollapsing new nodes
// with expandNodeChildren the node children are recursed (if applicable) with mapChildren
node.collapsed = false;
}
else if (oldNode && !recomputeExpandNode) {
// Maintain the same expanded/collapsed toggle for a node in this path/id
// EXCEPT when the shouldExpandNode prop is changed...
node.collapsed = oldNode.collapsed;
}
else if (opts.shouldExpandNode) {
node.collapsed = !opts.shouldExpandNode(node);
}
treeMap.set(node.id, node);
if (shouldRecurseChildren(node, parent, iteratedValues, opts)) {
const mappedChildren = opts.mapChildren && opts.mapChildren(value, getValueType(value), node);
const children = mappedChildren !== null && mappedChildren !== void 0 ? mappedChildren : getChildren(value, getValueType(value));
node.children = children
.map(([key, val], idx) => recurseObjectProperties(idx, key, val, depth + 1, false, node, treeMap, oldTreeMap, iteratedValues, recomputeExpandNode, opts))
.filter(n => n !== null);
}
return node;
}
export function recomputeTree(data, oldTreeMap, recursionOpts, recomputeExpandNode) {
const treeMap = new Map();
const iteratedValues = new Map();
const newTree = recurseObjectProperties(-1, 'root', data, 0, true, null, treeMap, oldTreeMap, iteratedValues, recomputeExpandNode, recursionOpts);
return { treeMap, tree: newTree, iteratedValues };
}