blob: 370ed286fc2c406c222af5c2f8bece7458e12180 [file] [log] [blame]
var mercury = require('mercury');
var insertCss = require('insert-css');
var vis = require('vis');
var namespaceService = require('../../../../services/namespace/service');
var log = require('../../../../lib/log')('components:browse:items:tree-view');
var css = require('./index.css');
var h = mercury.h;
module.exports = create;
module.exports.render = render;
function create() {}
function render(itemsState, browseState, browseEvents, navEvents) {
insertCss(css);
return [
h('h2', 'Visualize View'),
new TreeWidget(browseState, browseEvents)
];
}
// Maximum number of levels that are automatically shown
// TODO(aghassemi) Switch to globbing <namespace>/... instead
var MAX_AUTO_LOAD_DEPTH = 3;
function TreeWidget(browseState, browseEvents) {
this.browseState = browseState;
this.browseEvents = browseEvents;
this.nodes = new vis.DataSet();
this.edges = new vis.DataSet();
}
TreeWidget.prototype.type = 'Widget';
// Dom element to initialize the network in
var networkElem;
TreeWidget.prototype.init = function() {
this.initNetworkElem();
requestAnimationFrame(this.updateNetwork.bind(this));
// wrap in a new element, needed for Mercury vdom to patch properly.
var wrapper = document.createElement('div');
wrapper.appendChild(networkElem);
return wrapper;
};
TreeWidget.prototype.initNetworkElem = function() {
if (!networkElem) {
networkElem = document.createElement('div');
networkElem.className = 'network';
}
};
// We keep track of previous namespace that was browsed to so we can
// know when navigating to a different namespace happens.
var previousNamespace;
TreeWidget.prototype.updateNetwork = function() {
if (previousNamespace === this.browseState.namespace) {
return;
}
previousNamespace = this.browseState.namespace;
var self = this;
// Add the initial node.
var rootNodeId = this.browseState.namespace;
this.nodes.add({
id: rootNodeId,
label: rootNodeId || '<root>',
level: 0
});
// Load the subnodes.
this.rootNode = this.nodes.get(rootNodeId);
this.loadSubNodes(this.rootNode);
var options = {
hover: false,
selectable: true, // Need this or nodes won't be click-able
smoothCurves: false,
edges: {
width: 1
},
nodes: {
radiusMin: 16,
radiusMax: 32,
fontColor: '#333333',
shape: 'dot',
color: {
background: '#03a9f4',
border: '#0288d1'
}
}
};
// Start drawing the network.
var network = new vis.Network(networkElem, {
nodes: this.nodes,
edges: this.edges
}, options);
// Event listeners.
network.on('click', function onClick(data) {
// refresh side view
var nodeId = data.nodes[0];
var node = network.nodes[nodeId];
if (node) {
self.browseEvents.selectItem({
name: nodeId
});
}
});
network.on('doubleClick', function onClick(data) {
// drill
var nodeId = data.nodes[0];
var node = network.nodes[nodeId];
if (node && !node.subNodesLoaded) {
self.loadSubNodes(node);
}
});
return network;
};
TreeWidget.prototype.loadSubNodes = function(node) {
var namespace = node.id;
node.subNodesLoaded = true;
node.title = undefined;
var self = this;
namespaceService.getChildren(namespace).then(function(resultObservable) {
mercury.watch(resultObservable, function(results) {
// TODO(aghassemi) support removed and updated nodes when we switch to
// watchGlob
var existingIds = self.nodes.getIds();
var nodesToAdd = results.filter(function(item) {
var isNew = existingIds.indexOf(item.objectName) === -1;
return isNew;
});
var newNodes = nodesToAdd.map(function(item) {
var shape = 'dot';
var color;
if (item.isServer) {
shape = 'triangle';
color = '#ffab40';
}
return {
id: item.objectName,
label: item.mountedName,
level: node.level + 1,
shape: shape,
color: color,
isGlobbable: item.isGlobbable
};
});
var newEdges = nodesToAdd.map(function(item) {
return {
from: namespace,
to: item.objectName,
color: '#cccccc'
};
});
newNodes.forEach(function(item) {
// recurse if within the MAX_AUTO_LOAD_DEPTH
if (!item.isGlobbable) {
return;
}
if (item.level - self.rootNode.level < MAX_AUTO_LOAD_DEPTH) {
self.loadSubNodes(item);
} else {
item.title = 'Double-click to expand';
}
});
self.nodes.add(newNodes);
self.edges.add(newEdges);
});
}).catch(function(err) {
log.error('glob failed', err);
});
};
TreeWidget.prototype.update = function(prev, networkElem) {
requestAnimationFrame(this.updateNetwork.bind(this));
};