splore now uses js2

This commit is contained in:
Rafael Weinstein
2015-11-02 14:23:42 -08:00
parent 3f4fb1e9f7
commit 8330394fcd
10 changed files with 314 additions and 180 deletions
+11
View File
@@ -0,0 +1,11 @@
{
"optional": ["asyncToGenerator"],
"blacklist": [
"es6.blockScoping",
"es6.constants",
"es6.forOf",
"es6.properties.computed",
"es6.properties.shorthand",
"es6.templateLiterals"
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"parser": "babel-eslint",
"rules": {
"array-bracket-spacing": [2, "never"],
"camelcase": 2,
"eqeqeq": 2,
"indent": [2, 2, {"SwitchCase": 1}],
"linebreak-style": [2, "unix"],
"no-multi-spaces": 2,
"no-new-wrappers": 2,
"no-throw-literal": 2,
"no-var": 2,
"object-curly-spacing": [2, "never"],
"quotes": [2, "single"],
"radix": 2,
"semi": 2,
"space-after-keywords": 2,
"space-before-function-paren": [2, "never"],
"space-infix-ops": 2,
"space-in-parens": [2, "never"]
},
"env": {
"es6": true,
"node": true,
"browser": true
},
"extends": "eslint:recommended",
"ecmaFeatures": {
"jsx": true,
"experimentalObjectRestSpread": true
},
"plugins": [
"react"
]
}
+10
View File
@@ -0,0 +1,10 @@
[ignore]
.*/node_modules/react/.*
[include]
[libs]
[options]
unsafe.enable_getters_and_setters=true
munge_underscores=true
+89 -58
View File
@@ -1,3 +1,5 @@
/* @flow */
'use strict';
// JavaScript implementation of Christoph Buchheim, Michael Jünger, Sebastian Leipert's tree layout algorithm. See: http://dl.acm.org/citation.cfm?id=729576.
@@ -5,16 +7,51 @@
// Thanks also to Bill Mill for the explanation and Python sample code: http://billmill.org/pymag-trees/.
// TreeNode represents one node of the tree visualization.
class TreeNode {
constructor(data, id, parent, depth, number, seen) {
function assertNotNull<T>(v: ?T): T {
if (v !== null && v !== undefined) {
return v;
}
throw new Error('Non-null assertion failed');
}
export type NodeData = {
name: string,
fullName: string,
isOpen: boolean,
canOpen: boolean
};
export type NodeGraph = {
nodes: {[key: string]: NodeData},
links: {[key: string]: Array<string>}
};
export class TreeNode {
x: number;
y: number;
data: NodeData;
id: string;
children: Array<TreeNode>;
parent: ?TreeNode;
thread: ?TreeNode;
offset: number;
ancestor: TreeNode;
change: number;
shift: number;
number: number;
mod: number;
constructor(graph: NodeGraph, id: string, parent: ?TreeNode, depth: number, number: number, seen: {[key: string]: boolean}) {
seen[id] = true;
this.x = -1;
this.y = depth;
this.data = data.nodes[id];
this.data = graph.nodes[id];
this.id = id;
this.children = ((this.data.isOpen && data.links[id]) || [])
this.children = ((this.data.isOpen && graph.links[id]) || [])
.filter(cid => !(cid in seen))
.map((cid, i) => new TreeNode(data, cid, this, depth+1, i+1, seen));
.map((cid, i) => new TreeNode(graph, cid, this, depth + 1, i + 1, seen));
this.parent = parent;
this.thread = null;
this.offset = 0;
@@ -25,24 +62,24 @@ class TreeNode {
this.mod = 0;
}
left() {
left(): ?TreeNode {
if (this.children.length > 0) {
return this.children[0];
}
return this.thread;
}
right() {
right(): ?TreeNode {
if (this.children.length > 0) {
return this.children[this.children.length - 1];
}
return this.thread;
}
leftBrother() {
var n = null;
leftBrother(): ?TreeNode {
let n = null;
if (this.parent) {
for (var node of this.parent.children) {
for (let node of this.parent.children) {
if (node === this) {
return n;
} else {
@@ -53,8 +90,8 @@ class TreeNode {
return n;
}
getLeftMostSibling() {
if (this.parent && this != this.parent.children[0]) {
getLeftMostSibling(): ?TreeNode {
if (this.parent && this !== this.parent.children[0]) {
return this.parent.children[0];
} else {
return null;
@@ -62,31 +99,29 @@ class TreeNode {
}
}
function layout(tree) {
export function layout(tree: TreeNode): void{
firstWalk(tree, 1);
secondWalk(tree, 0, 0);
}
function firstWalk(v, distance) {
function firstWalk(v: TreeNode, distance: number): void {
if (v.children.length === 0) {
if (v.getLeftMostSibling()) {
v.x = v.leftBrother().x + distance;
v.x = assertNotNull(v.leftBrother()).x + distance;
} else {
v.x = 0;
}
} else {
var defaultAncestor = v.children[0];
for (var w of v.children) {
let defaultAncestor = v.children[0];
for (let w of v.children) {
firstWalk(w, distance);
defaultAncestor = apportion(w, defaultAncestor, distance);
}
executeShifts(v);
var midpoint = (v.children[0].x + v.children[v.children.length - 1].x) / 2;
let midpoint = (v.children[0].x + v.children[v.children.length - 1].x) / 2;
var ell = v.children[0];
var arr = v.children[v.children.length - 1];
var w = v.leftBrother();
let w = v.leftBrother();
if (w) {
v.x = w.x + distance;
v.mod = v.x - midpoint;
@@ -94,30 +129,28 @@ function firstWalk(v, distance) {
v.x = midpoint;
}
}
return v;
}
function apportion(v, defaultAncestor, distance) {
var w = v.leftBrother();
if (w != null) {
var vir = v;
var vor = v;
var vil = w;
var vol = v.getLeftMostSibling();
var sir = v.mod;
var sor = v.mod;
var sil = vil.mod;
var sol = vol.mod;
function apportion(v: TreeNode, defaultAncestor: TreeNode, distance: number): TreeNode {
let w = v.leftBrother();
if (w !== null) {
let vir = v;
let vor = v;
let vil = assertNotNull(w);
let vol = assertNotNull(v.getLeftMostSibling());
let sir = v.mod;
let sor = v.mod;
let sil = vil.mod;
let sol = vol.mod;
while (vil.right() && vir.left()) {
vil = vil.right();
vir = vir.left();
vol = vol.left();
vor = vor.right();
vor.ancestor = v
var shift = (vil.x + sil) - (vir.x + sir) + distance;
vil = assertNotNull(vil.right());
vir = assertNotNull(vir.left());
vol = assertNotNull(vol.left());
vor = assertNotNull(vor.right());
vor.ancestor = v;
let shift = (vil.x + sil) - (vir.x + sir) + distance;
if (shift > 0) {
var a = ancestor(vil, v, defaultAncestor);
let a = ancestor(vil, v, defaultAncestor);
moveSubtree(a, v, shift);
sir = sir + shift;
sor = sor + shift;
@@ -138,11 +171,11 @@ function apportion(v, defaultAncestor, distance) {
defaultAncestor = v;
}
}
return defaultAncestor
return defaultAncestor;
}
function moveSubtree(wl, wr, shift) {
var subtrees = wr.number - wl.number;
function moveSubtree(wl: TreeNode, wr: TreeNode, shift: number): void {
let subtrees = wr.number - wl.number;
wr.change -= shift / subtrees;
wr.shift += shift;
wl.change += shift / subtrees;
@@ -150,11 +183,11 @@ function moveSubtree(wl, wr, shift) {
wr.mod += shift;
}
function executeShifts(v) {
var shift = 0;
var change = 0;
for (var i = v.children.length - 1; i >= 0; i--) {
var w = v.children[i];
function executeShifts(v: TreeNode): void {
let shift = 0;
let change = 0;
for (let i = v.children.length - 1; i >= 0; i--) {
let w = v.children[i];
w.x += shift;
w.mod += shift;
change += w.change;
@@ -162,21 +195,19 @@ function executeShifts(v) {
}
}
function ancestor(vil, v, defaultAncestor) {
if (v.parent.children.indexOf(vil.ancestor) > -1) {
function ancestor(vil: TreeNode, v: TreeNode, defaultAncestor: TreeNode): TreeNode {
if (v.parent && v.parent.children.indexOf(vil.ancestor) > -1) {
return vil.ancestor;
} else {
return defaultAncestor;
}
}
function secondWalk(v, m, depth) {
v.x += m
v.y = depth
function secondWalk(v: TreeNode, m: number, depth: number): void {
v.x += m;
v.y = depth;
for (var w of v.children) {
secondWalk(w, m + v.mod, depth+1)
for (let w of v.children) {
secondWalk(w, m + v.mod, depth + 1);
}
}
module.exports = {TreeNode, layout};
+1 -1
View File
@@ -5,4 +5,4 @@
<script src="out.js"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body/>
<body><div id="splore"></div></body>
+42 -33
View File
@@ -1,44 +1,55 @@
/* @flow */
'use strict';
var Node = require('./node.js');
var React = require('react');
import Node from './node.js';
import React from 'react';
import type {NodeGraph} from './buchheim.js';
import {TreeNode} from './buchheim.js';
var Layout = React.createClass({
propTypes: {
data: React.PropTypes.object.isRequired,
onNodeClick: React.PropTypes.func.isRequired,
tree: React.PropTypes.object.isRequired,
},
type Props = {
data: NodeGraph,
onNodeClick: (e: Event, s: string) => void,
tree: TreeNode
}
render() {
var children = [];
var edges = [];
var lookup = {};
export default class Layout extends React.Component {
static defaultProps: {};
props: Props;
constructor(props: Props) {
super(props);
}
render(): React.Element {
let children = [];
let edges = [];
let lookup = {};
const spaceX = 75;
const spaceY = 20;
var getX = d => d.y * spaceX;
var getY = d => d.x * spaceY;
var maxX = 0;
var minY = 0;
var maxY = 0;
let getX = d => d.y * spaceX;
let getY = d => d.x * spaceY;
let maxX = 0;
let minY = 0;
let maxY = 0;
var process = (treeNode, fromX, fromY) => {
var links = this.props.data.links[treeNode.id] || [];
var hasChildren = treeNode.data.canOpen || links.length > 0;
var x = getX(treeNode);
var y = getY(treeNode);
var title = '';
let process = (treeNode, fromX, fromY) => {
let links = this.props.data.links[treeNode.id] || [];
let hasChildren = treeNode.data.canOpen || links.length > 0;
let x = getX(treeNode);
let y = getY(treeNode);
let title = '';
if (treeNode.data.fullName) {
title = 'Alt-click for full ref'
title = 'Alt-click for full ref';
}
maxX = Math.max(x + spaceX, maxX);
minY = Math.min(y, minY);
maxY = Math.max(y + spaceY, maxY);
var n = (
let n = (
<Node
key={'n' + treeNode.id}
shape='circle'
@@ -67,16 +78,16 @@ var Layout = React.createClass({
process(this.props.tree, 0, 0);
edges.forEach(e => {
var from = lookup[e[0]];
var to = lookup[e[1]];
let from = lookup[e[0]];
let to = lookup[e[1]];
children.push(
<path key={'p' + e[0] + '-' + e[1]} className='link' d={`M${getX(from)},${getY(from)}L${getX(to)},${getY(to)}`}/>);
});
var sortOrder = (elm => elm.type == 'path' ? 0 : 1);
let sortOrder = (elm => elm.type === 'path' ? 0 : 1);
children.sort((a, b) => sortOrder(a) - sortOrder(b));
var translateY = spaceY;
let translateY = spaceY;
if (minY < 0) {
translateY -= minY;
maxY -= minY;
@@ -89,7 +100,5 @@ var Layout = React.createClass({
</g>
</svg>
);
},
});
module.exports = React.createFactory(Layout);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
pushd ../../js
pushd ../../js2
npm run build
./link.sh
popd
+69 -49
View File
@@ -1,41 +1,43 @@
/* @flow */
'use strict';
var buchheim = require('./buchheim.js');
var Layout = require('./layout.js');
var Immutable = require('immutable');
var noms = require('noms');
var queryString = require('query-string');
var React = require('react');
var {Ref} = require('noms');
import {layout, NodeGraph, TreeNode} from './buchheim.js';
import Layout from './layout.js';
import React from 'react'; //eslint-disable-line no-unused-vars
import ReactDOM from 'react-dom';
import {readValue, HttpStore, Ref} from 'noms';
var data = {nodes: {}, links: {}};
var rootRef = null;
let data: NodeGraph = {nodes: {}, links: {}};
let rootRef: Ref;
let httpStore: HttpStore;
let renderNode: ?HTMLElement;
window.onload = function() {
var target = document.getElementById('root');
var w = window.innerWidth;
var h = window.innerHeight;
window.addEventListener('load', () => {
renderNode = document.getElementById('splore');
httpStore = new HttpStore('http://localhost:8000');
noms.getRoot().then(ref => {
httpStore.getRoot().then(ref => {
rootRef = ref;
handleChunkLoad(ref, new Ref(ref));
handleChunkLoad(ref, ref);
});
};
});
window.onresize = render;
function handleChunkLoad(ref, val, fromRef) {
var counter = 0
var process = (ref, val, fromId) => {
if (typeof val == 'undefined') {
function handleChunkLoad(ref: Ref, val: any, fromRef: ?string) {
let counter = 0;
function process(ref, val, fromId): ?string {
if (typeof val === 'undefined') {
return null;
}
// Assign a unique ID to this node.
// We don't use the noms ref because we only want to represent values as shared in the graph if they are actually in the same chunk.
var id;
let id;
if (val instanceof Ref) {
id = val.ref;
id = val.toString();
} else {
id = ref + '/' + counter++;
}
@@ -55,48 +57,65 @@ function handleChunkLoad(ref, val, fromRef) {
if (val instanceof Blob) {
data.nodes[id] = {name: `Blob (${val.size})`};
} else if (Immutable.List.isList(val)) {
data.nodes[id] = {name: `List (${val.size})`};
} else if (Array.isArray(val)) {
data.nodes[id] = {name: `List (${val.length})`};
val.forEach(c => process(ref, c, id));
} else if (Immutable.Set.isSet(val)) {
} else if (val instanceof Set) {
data.nodes[id] = {name: `Set (${val.size})`};
val.forEach(c => process(ref, c, id));
} else if (Immutable.Map.isMap(val)) {
var structName = val.get('$name');
if (structName) {
data.nodes[id] = {name: structName};
} else {
data.nodes[id] = {name: `Map (${val.size})`};
}
val.keySeq().filter(k => k != '$name')
.forEach(k => {
// TODO: handle non-string keys
var kid = process(ref, k, id);
} else if (val instanceof Map) {
data.nodes[id] = {name: `Map (${val.size})`};
val.forEach((v, k) => {
// TODO: handle non-string keys
let kid = process(ref, k, id);
if (kid) {
// Start map keys open, just makes it easier to use.
data.nodes[kid].isOpen = true;
process(ref, val.get(k), kid);
});
process(ref, v, kid);
} else {
throw new Error('No kid id.');
}
});
} else if (val instanceof Ref) {
let refStr = val.toString();
data.nodes[id] = {
canOpen: true,
name: val.ref.substr(5, 6),
fullName: val.ref,
name: refStr.substr(5, 6),
fullName: refStr
};
} else if (val._typeRef) {
// Struct
let structName = val._typeRef.name;
data.nodes[id] = {name: structName};
Object.keys(val).forEach(k => {
if (k === '_typeRef') {
return;
}
// TODO: handle non-string keys
let kid = process(ref, k, id);
if (kid) {
// Start map keys open, just makes it easier to use.
data.nodes[kid].isOpen = true;
process(ref, val[k], kid);
} else {
throw new Error('No kid id.');
}
});
}
return id;
};
}
process(ref, val, fromRef);
render();
}
function handleNodeClick(e, id) {
function handleNodeClick(e: Event, id: string) {
if (e.altKey) {
if (data.nodes[id].fullName) {
window.prompt("Full ref", data.nodes[id].fullName);
window.prompt('Full ref', data.nodes[id].fullName);
}
return;
}
@@ -111,14 +130,15 @@ function handleNodeClick(e, id) {
if (data.links[id] || !data.nodes[id].isOpen) {
render();
} else {
noms.readValue(id, noms.getChunk)
.then(chunk => handleChunkLoad(id, chunk, id));
readValue(Ref.parse(id), httpStore).then(value => {
handleChunkLoad(id, value, id);
});
}
}
}
function render() {
var dt = new buchheim.TreeNode(data, rootRef, null, 0, 0, {});
buchheim.layout(dt);
React.render(<Layout tree={dt} data={data} onNodeClick={handleNodeClick}/>, document.body);
let dt = new TreeNode(data, rootRef.toString(), null, 0, 0, {});
layout(dt);
ReactDOM.render(<Layout tree={dt} data={data} onNodeClick={handleNodeClick}/>, renderNode);
}
+48 -33
View File
@@ -1,41 +1,58 @@
/* @flow */
'use strict';
var classNames = require('classnames');
var React = require('react');
import classNames from 'classnames';
import React from 'react';
var Node = React.createClass({
propTypes: {
canOpen: React.PropTypes.bool.isRequired,
isOpen: React.PropTypes.bool.isRequired,
shape: React.PropTypes.string.isRequired,
text: React.PropTypes.string.isRequired,
title: React.PropTypes.string,
fromX: React.PropTypes.number.isRequired,
fromY: React.PropTypes.number.isRequired,
x: React.PropTypes.number.isRequired,
y: React.PropTypes.number.isRequired,
onClick: React.PropTypes.func.isRequired,
},
type Props = {
canOpen: boolean,
isOpen: boolean,
shape: string,
text: string,
title: string,
fromX: number,
fromY: number,
x: number,
y: number,
onClick: (e: Event, s: String) => void;
}
getInitialState() {
return {
type State = {
x: number,
y: number
}
export default class Node extends React.Component {
static defaultProps: {};
props: Props;
state: State;
constructor(props: Props) {
super(props);
this.state = {
x: this.props.fromX,
y: this.props.fromY,
y: this.props.fromY
};
},
}
render() {
if (this.state.x != this.props.x ||
this.state.y != this.props.y) {
setState(state: State) {
super.setState(state);
}
render(): React.Element {
if (this.state.x !== this.props.x ||
this.state.y !== this.props.y) {
window.requestAnimationFrame(() => this.setState({
x: this.props.x,
y: this.props.y,
y: this.props.y
}));
}
var textAnchor = 'start';
var textX = 10;
var translate = `translate3d(${this.state.x}px, ${this.state.y}px, 0)`;
let textAnchor = 'start';
let textX = 10;
let translate = `translate3d(${this.state.x}px, ${this.state.y}px, 0)`;
if (this.props.canOpen) {
textAnchor = 'end';
@@ -51,10 +68,10 @@ var Node = React.createClass({
<title>{this.props.title}</title>
</g>
);
},
}
getShape() {
var className = classNames('icon', {open:this.props.isOpen});
let className = classNames('icon', {open:this.props.isOpen});
switch (this.props.shape) {
case 'circle':
return <circle className={className} r='4.5'/>;
@@ -62,9 +79,7 @@ var Node = React.createClass({
// rx:1.35 and ry:1.35 for rounded corners, but not doing until I learn how to make the triangle match below.
return <rect className={className} x='-4.5' y='-4.5' width='9' height='9'/>;
case 'triangle':
return <polygon className={className} points='0,-4.5 4.5,4.5 -4.5,4.5' rx='1.35' ry='1.35'/>
return <polygon className={className} points='0,-4.5 4.5,4.5 -4.5,4.5' rx='1.35' ry='1.35'/>;
}
},
});
module.exports = React.createFactory(Node);
}
}
+8 -5
View File
@@ -2,18 +2,21 @@
"name": "noms-splore",
"dependencies": {
"classnames": "^2.1.3",
"immutable": "^3.7.4",
"query-string": "^2.4.0",
"react": "^0.12.0"
"react": "^0.14.1",
"react-dom": "^0.14.1"
},
"devDependencies": {
"babel": "^5.6.23",
"babel-eslint": "^4.1.3",
"babelify": "^6.1.3",
"browserify": "^6.2.0",
"uglify-js": "~2.4.15",
"eslint": "^1.7.3",
"eslint-plugin-react": "^3.6.3",
"watchify": "^2.1.1"
},
"scripts": {
"start": "watchify -o out.js -t babelify -v -d main.js",
"build": "NODE_ENV=production browserify main.js -t babelify | uglifyjs -cm > out.js"
"build": "NODE_ENV=production browserify main.js -t babelify > out.js",
"test": "rm -f out.js && eslint *.js && flow"
}
}