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  }