github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/cmd/noms/splore/src/main.js (about) 1 // Copyright 2017 Attic Labs, Inc. All rights reserved. 2 // Licensed under the Apache License, version 2.0: 3 // http://www.apache.org/licenses/LICENSE-2.0 4 5 // @flow 6 7 import React from 'react'; 8 import ReactDOM from 'react-dom'; 9 import {notNull} from './assert.js'; 10 import {layout, TreeNode} from './buchheim.js'; 11 import Layout from './layout.js'; 12 import type {NodeGraph, SploreNode} from './types.js'; 13 14 // The ID of the root node is always ''. 15 const rootId = ''; 16 17 const data: NodeGraph = { 18 keyLinks: {}, 19 links: {}, 20 nodes: {}, 21 open: {}, 22 }; 23 24 window.onload = window.onpopstate = load; 25 window.onresize = render; 26 27 async function load(): Promise<void> { 28 await fetchNode(rootId); 29 toggleNode(rootId); 30 } 31 32 async function fetchNode(id: string): Promise<void> { 33 if (nodeIsFetched(id)) { 34 throw new Error(`node ${id} has already been fetched`); 35 } 36 37 const idParam = encodeURIComponent(id); 38 const node: SploreNode = await fetch(`/getNode?id=${idParam}`).then(r => r.json()); 39 addNode(node); 40 41 const autoExpand = []; 42 43 for (const child of notNull(node.children)) { 44 const {key, label, value} = child; 45 46 addNode(value); 47 48 // Auto-expand @target because typically we'll only see these when 49 // expanding a ref, and if a ref is being expanded, chances are they want 50 // to see its target immediately. 51 if (value.id.match(/@target$/)) { 52 autoExpand.push(value.id); 53 } 54 55 if (label !== '') { 56 // Includes a label. Create a fake node for it, then key-link to the 57 // value. The @label annotation is just to trick the graph rendering. 58 const labelNode = { 59 hasChildren: false, 60 id: value.id + '@label', 61 name: label, 62 }; 63 addNode(labelNode); 64 data.links[id].push(labelNode.id); 65 data.keyLinks[labelNode.id].push(value.id); 66 } else if (key.id !== '') { 67 // Includes a key. Add the node for it, then key-link to the value. 68 // Note: unlike labels, keys can have their own subgraph. 69 addNode(key); 70 data.links[id].push(key.id); 71 data.keyLinks[key.id].push(value.id); 72 } else { 73 // Only has a value. 74 data.links[id].push(value.id); 75 } 76 } 77 78 await Promise.all(autoExpand.map(fetchNode)); 79 } 80 81 function nodeIsFetched(id: string): boolean { 82 // The node might exist in data.nodes, but only as populated from the 83 // children of another node. A node has only been fetched if it also has a 84 // `children` property. 85 const node = data.nodes[id]; 86 return !!(node && node.children); 87 } 88 89 function addNode(node: SploreNode): void { 90 if (!nodeIsFetched(node.id)) { 91 data.nodes[node.id] = node; 92 } 93 const init = arr => (arr[node.id] = arr[node.id] || []); 94 init(data.links); 95 init(data.keyLinks); 96 } 97 98 function handleNodeClick(e: MouseEvent, id: string) { 99 if (e.shiftKey) { 100 // TODO: Implement our own prompt which does pretty printing and allows copy. 101 alert(JSON.stringify(data.nodes[id])); 102 } else { 103 toggleNode(id); 104 } 105 } 106 107 async function toggleNode(id: string) { 108 const node = data.nodes[id]; 109 if (!node || !node.hasChildren) { 110 return; 111 } 112 113 if (!nodeIsFetched(id)) { 114 await fetchNode(id); 115 } 116 117 data.open[id] = !data.open[id]; 118 render(); 119 } 120 121 function render() { 122 if (Object.keys(data).length === 0) { 123 // Data hasn't loaded yet. 124 return; 125 } 126 127 const dt = new TreeNode(data, rootId, null, 0, 0, {}); 128 layout(dt); 129 ReactDOM.render( 130 <Layout tree={dt} data={data} onNodeClick={handleNodeClick} />, 131 document.querySelector('#splore'), 132 ); 133 }