namespace-browser: Adding "Delete" functionality for mountpoint and
also changing the reload action not to reload the whole page and just
clear cache and refresh current view.
Closes https://github.com/vanadium/browser/issues/63
Change-Id: I2c93212808ea248819df6206bcbc2492f265007a
diff --git a/src/app.js b/src/app.js
index b61a882..3bd8ef3 100644
--- a/src/app.js
+++ b/src/app.js
@@ -9,12 +9,13 @@
var onboarding = require('./onboarding');
var router = require('./router');
var registerItemPlugins = require('./item-plugins/register-plugins');
-var debug = require('./components/debug/index');
-var browse = require('./components/browse/index');
-var error = require('./components/error/index');
-var help = require('./components/help/index');
-var viewport = require('./components/viewport/index');
-var userAccount = require('./components/user-account/index');
+var debug = require('./components/debug');
+var browse = require('./components/browse');
+var error = require('./components/error');
+var help = require('./components/help');
+var viewport = require('./components/viewport');
+var views = require('./components/browse/views');
+var userAccount = require('./components/user-account');
var namespaceService = require('./services/namespace/service');
var sampleWorld = require('./services/sample-world');
var stateService = require('./services/state/service');
@@ -120,7 +121,13 @@
* }
* is expected as data for the event
*/
- 'navigate'
+ 'navigate',
+
+ /*
+ * Event indicating a request to reload the current namespace
+ * The current namespace will be passed as data into the handlers.
+ */
+ 'reload'
]);
events.browse = browseComponent.events;
events.help = helpComponent.events;
@@ -161,12 +168,39 @@
events.browse.error(onError);
events.browse.toast(onToast);
+ events.navigation.reload(onReload);
+
// Hook up external help events.
events.help.navigate = events.navigation.navigate;
events.help.error(onError);
}
/*
+ * Reload the views for the current namespace
+ */
+function onReload() {
+ var namespace = state.browse.namespace();
+ log.debug('reloading', namespace);
+
+ // clear the service cache
+ namespaceService.clearCache(namespace);
+
+ // tell views to clear their caches
+ views.clearCache(state.browse.views, namespace);
+
+ // navigate to the namespace again
+ // TODO(aghassemi) Ideally we only reset the selected item if the old one
+ // no longer is in the view, but that's a bit tricky and depends on
+ // https://github.com/vanadium/browser/issues/81
+ state.browse.selectedItemName.set(namespace);
+ events.navigation.navigate({
+ path: browseRoute.createUrl(state.browse(), {
+ namespace: namespace
+ })
+ });
+}
+
+/*
* Given an error, navigate to the error page and display that error.
*/
function onError(err) {
@@ -291,5 +325,4 @@
*/
function onVanadiumCrash(crashErr) {
events.browse.error(crashErr);
-}
-
+}
\ No newline at end of file
diff --git a/src/components/browse/index.css b/src/components/browse/index.css
index a901c4c..edef1ac 100644
--- a/src/components/browse/index.css
+++ b/src/components/browse/index.css
@@ -137,6 +137,7 @@
.namespace-box core-tooltip.icontooltip {
margin-top: var(--size-space-xxsmall);
+ align-self: center;
}
.namespace-box core-tooltip.nstooltip {
diff --git a/src/components/browse/index.js b/src/components/browse/index.js
index 9898ba7..4cf3b01 100644
--- a/src/components/browse/index.js
+++ b/src/components/browse/index.js
@@ -351,8 +351,10 @@
}, [
h('div.resize-handle', {
'ev-mousedown': function(e) {
- browseEvents.slideSidePanel({ rawEvent: e,
- collapsed: browseState.sidePanelCollapsed });
+ browseEvents.slideSidePanel({
+ rawEvent: e,
+ collapsed: browseState.sidePanelCollapsed
+ });
}
}),
sideView
@@ -433,9 +435,7 @@
'icon': 'refresh',
'label': 'Reload'
},
- 'ev-click': function() {
- location.reload();
- }
+ 'ev-click': mercury.send(navEvents.reload)
})
),
h('core-tooltip.nstooltip', {
@@ -621,17 +621,17 @@
});
breadCrumbs.push(h('li.breadcrumb-item.relative-name' +
(parentParts.length ? '.breadcrumb-item-prefix' : ''), [
- //TODO(aghassemi) refactor link generation code
- h('a', {
- 'href': rootUrl,
- 'ev-click': mercury.event(navEvents.navigate, {
- path: rootUrl
- })
- }, '<Home>')
- ]));
+ //TODO(aghassemi) refactor link generation code
+ h('a', {
+ 'href': rootUrl,
+ 'ev-click': mercury.event(navEvents.navigate, {
+ path: rootUrl
+ })
+ }, '<Home>')
+ ]));
}
- parentParts.pop(); // remove last part (current view root)
+ parentParts.pop(); // remove last part (current view root)
for (var i = 0; i < namespaceParts.length; i++) {
var namePart = namespaceParts[i].trim();
@@ -707,19 +707,19 @@
}
function slideEnd(e) { // release
- window.removeEventListener('mouseup', slideEnd);
- window.removeEventListener('mousemove', slideMove);
- drawer.querySelector('::shadow core-selector').
- classList.add('transition');
- var drawerWidth = drawer.getAttribute('drawerWidth');
+ window.removeEventListener('mouseup', slideEnd);
+ window.removeEventListener('mousemove', slideMove);
+ drawer.querySelector('::shadow core-selector').
+ classList.add('transition');
+ var drawerWidth = drawer.getAttribute('drawerWidth');
- // async call to persist the drawer width
- stateService.saveSidePanelWidth(drawerWidth);
+ // async call to persist the drawer width
+ stateService.saveSidePanelWidth(drawerWidth);
- state.sidePanelWidth.set(drawerWidth);
- state.sidePanelCollapsed.set(false);
- fireResizeEvent(null);
- } // end slideEnd
+ state.sidePanelWidth.set(drawerWidth);
+ state.sidePanelCollapsed.set(false);
+ fireResizeEvent(null);
+ } // end slideEnd
}); // end events.slideSidePanel
function fireResizeEvent(e) { // resize on end animation
@@ -733,4 +733,4 @@
}
}
-}
+}
\ No newline at end of file
diff --git a/src/components/browse/item-details/display-item-details.js b/src/components/browse/item-details/display-item-details.js
index c7bff09..1e50a62 100644
--- a/src/components/browse/item-details/display-item-details.js
+++ b/src/components/browse/item-details/display-item-details.js
@@ -29,11 +29,6 @@
function displayItemDetails(state, events, data) {
var name = data.name;
- // Return if we are already on that item.
- if (isCurrentlySelected()) {
- return;
- }
-
lastRequestedName = name;
state.put('plugins', mercury.array([]));
diff --git a/src/components/browse/item-details/index.js b/src/components/browse/item-details/index.js
index 73b0d41..c4c2cab 100644
--- a/src/components/browse/item-details/index.js
+++ b/src/components/browse/item-details/index.js
@@ -102,8 +102,6 @@
events.serverDetails = serverDetailsComponent.events;
events.mountPointDetails = mountPointDetailsComponent.events;
- events.serverDetails.toast = events.toast;
- events.mountPointDetails.toast = events.toast;
wireUpEvents(state, events);
return {
@@ -347,19 +345,18 @@
'tabkey': tabKey
},
'ev-click': new polymerEvent(function(data) {
- events.tabSelected({
- tabKey: tabKey
- });
- })
- }, [
- h('core-icon.tab-icon', {
- attributes: {
- 'icon': icon,
- 'alt': '' // because we have the title beside it
- }
- }), title
- ]
- );
+ events.tabSelected({
+ tabKey: tabKey
+ });
+ })
+ }, [
+ h('core-icon.tab-icon', {
+ attributes: {
+ 'icon': icon,
+ 'alt': '' // because we have the title beside it
+ }
+ }), title
+ ]);
}
/*
@@ -386,9 +383,15 @@
// Wire up events that we know how to handle
function wireUpEvents(state, events) {
+ events.serverDetails.toast = function(data) {
+ events.toast(data);
+ };
+ events.mountPointDetails.toast = function(data) {
+ events.toast(data);
+ };
events.bookmark(bookmark.bind(null, state, events));
events.displayItemDetails(displayItemDetails.bind(null, state, events));
events.tabSelected(function(data) {
state.selectedTabKey.set(data.tabKey);
});
-}
+}
\ No newline at end of file
diff --git a/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js b/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js
index d418736..2bd3733 100644
--- a/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js
+++ b/src/components/browse/item-details/mount-point-details/display-mountpoint-details.js
@@ -22,11 +22,6 @@
var itemObs = data.itemObs;
var name = itemObs().objectName;
- // Return if we are already on that item.
- if (isCurrentlySelected()) {
- return;
- }
-
lastRequestedName = name;
state.put('item', itemObs);
diff --git a/src/components/browse/item-details/mount-point-details/index.js b/src/components/browse/item-details/mount-point-details/index.js
index 061eca9..11a47b5 100644
--- a/src/components/browse/item-details/mount-point-details/index.js
+++ b/src/components/browse/item-details/mount-point-details/index.js
@@ -6,10 +6,16 @@
var insertCss = require('insert-css');
var displayMountPointDetails = require('./display-mountpoint-details');
+var mountPointManager = require('./manage-mountpoint');
+var dialogClickHook = require('../../../../lib/mercury/dialog-click-hook');
var FieldItem = require('../field-item');
var ErrorBox = require('../../../error/error-box');
+var log = require('../../../../lib/log')(
+ 'components:browse:item-details:mount-point'
+);
+
var css = require('./index.css');
var h = mercury.h;
@@ -60,7 +66,7 @@
permissions: mercury.value(null),
/*
- * whether user is even authorized to see the permission for the mount point
+ * Whether user is even authorized to see the permission for the mount point
* @type {boolean}
*/
notAuthorizedToSeePermissions: mercury.value(false),
@@ -69,14 +75,43 @@
* The objectAddresses as resolveToMounttable
* @type {mercury.array<string>}
*/
- objectAddresses: mercury.array([])
+ objectAddresses: mercury.array([]),
+
+ /*
+ * Whether we should render a dialog prompting for an action.
+ * @type {boolean}
+ */
+ promptAction: mercury.value(false),
+
+ /*
+ * The text for the prompt.
+ * @type {string}
+ */
+ promptActionText: mercury.value(''),
+
+ /*
+ * The text for the positive button of the prompt.
+ * @type {string}
+ */
+ promptActionButtonText: mercury.value(''),
+
+ /*
+ * The event handler that will be called when confirmed.
+ * @type {function}
+ */
+ promptActionCallback: mercury.value()
});
var events = mercury.input([
- 'toast'
+ 'toast',
+ 'promptDeleteMountPoint',
+ 'promptCanceled',
+ 'promptConfirmed'
]);
+ wireUpEvents(state, events);
+
return {
state: state,
events: events
@@ -97,6 +132,7 @@
displayItems.push(renderNameField(state));
displayItems.push(renderObjectAddressesField(state));
displayItems.push(renderPermissionsField(state));
+ displayItems.push(renderActionsField(state, events, navEvents));
content.push(h('div', displayItems));
}
@@ -156,12 +192,84 @@
}
/*
+ * Renders the mountpoint actions.
+ */
+function renderActionsField(state, events, navEvents) {
+ var actions = [
+ renderDeleteAction(state, events, navEvents)
+ ];
+
+ var filteredActions = actions.filter(function(a) {
+ return !!a;
+ });
+
+ if (filteredActions.length === 0) {
+ return;
+ }
+
+ return [
+ FieldItem.render('Manage', h('div', filteredActions)),
+ renderPrompt(state, events)
+ ];
+}
+
+/*
+ * Renders a modal prompt dialog if an action is taking place to confirm.
+ */
+function renderPrompt(state, events) {
+ return h('paper-action-dialog', {
+ attributes: {
+ 'autoCloseDisabled': true,
+ 'layered': true,
+ 'backdrop': true,
+ },
+ 'opened': state.promptAction
+ }, [
+ h('p', state.promptActionText),
+
+ h('paper-button', {
+ attributes: {
+ 'dismissive': true,
+ },
+ 'click-hook': dialogClickHook(mercury.send(events.promptCanceled))
+ }, 'Cancel'),
+
+ h('paper-button', {
+ attributes: {
+ 'affirmative': true,
+ 'autofocus': true
+ },
+ 'click-hook': dialogClickHook(mercury.send(events.promptConfirmed))
+ }, state.promptActionButtonText)
+ ]);
+}
+
+/*
+ * Renders the mountpoint delete action.
+ */
+function renderDeleteAction(state, events, navEvents) {
+ /* TODO(aghassemi) We really should only render items user has access to.
+ * This was attempted by trying to match remoteBlessings with peerBlessings
+ * but we intentionally do not expose blessing names for peerBlessings so
+ * approach did not work.
+ * This needs https://github.com/vanadium/issues/issues/210 to be fixed first.
+ */
+ var action = h('paper-button', {
+ 'ev-click': mercury.send(events.promptDeleteMountPoint, {
+ cb: navEvents.reload,
+ name: state.itemName
+ })
+ }, 'Delete');
+
+ return action;
+}
+
+/*
* Formats a permissions object to string
* TODO(aghassemi): we need a nicer permission formatter
* @param {vanadium.security.Permissions} perms
*/
function formatPermissions(perms) {
- //
var results = [];
perms.forEach(function(p, key) {
results.push(
@@ -207,4 +315,36 @@
}
return h('div', results);
+}
+
+// Wire up events that we know how to handle
+function wireUpEvents(state, events) {
+ events.promptDeleteMountPoint(function(data) {
+ state.promptAction.set(true);
+ state.promptActionText.set(
+ 'Are you sure you want to delete ' + data.name + ' ?'
+ );
+ state.promptActionButtonText.set('Delete');
+ state.promptActionCallback.set(deleteMountPoint.bind(null, data));
+ });
+
+ events.promptCanceled(function() {
+ state.promptAction.set(false);
+ });
+
+ events.promptConfirmed(function() {
+ var cb = state.promptActionCallback();
+ cb();
+ state.promptAction.set(false);
+ });
+
+ function deleteMountPoint(data) {
+ mountPointManager.deleteMountPoint(state, events).then(function() {
+ if (data.cb) {
+ data.cb();
+ }
+ }, function(err) {
+ log.error('Could not delete mount point', err);
+ });
+ }
}
\ No newline at end of file
diff --git a/src/components/browse/item-details/mount-point-details/manage-mountpoint.js b/src/components/browse/item-details/mount-point-details/manage-mountpoint.js
new file mode 100644
index 0000000..f292a90
--- /dev/null
+++ b/src/components/browse/item-details/mount-point-details/manage-mountpoint.js
@@ -0,0 +1,39 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+var namespaceService = require('../../../../services/namespace/service');
+
+var log = require('../../../../lib/log')(
+ 'components:browse:item-details:mount-point:manage-mountpoint'
+);
+
+module.exports = {
+ deleteMountPoint: deleteMountPoint
+};
+
+/*
+ * Delete a given mountpoint
+ */
+ //TODO(aghassemi) Prompt for confirmation
+function deleteMountPoint(state, events) {
+ var name = state.itemName;
+ return namespaceService.deleteMountPoint(name).then(function() {
+ events.toast({
+ text: name + ' deleted successfully'
+ });
+ }).catch(function(err) {
+ var errText = 'Could not delete ' + name;
+ if (err && err.id === 'v.io/v23/verror.NoAccess') {
+ errText = 'Not authorized to delete ' + name;
+ }
+
+ log.error(errText, name, err);
+ events.toast({
+ text: errText,
+ type: 'error'
+ });
+
+ return Promise.reject(err);
+ });
+}
diff --git a/src/components/browse/views/index.js b/src/components/browse/views/index.js
index 2384b70..e1dc96f 100644
--- a/src/components/browse/views/index.js
+++ b/src/components/browse/views/index.js
@@ -18,6 +18,7 @@
module.exports.render = render;
module.exports.load = load;
module.exports.trySetViewType = trySetViewType;
+module.exports.clearCache = clearCache;
var VALID_VIEW_TYPES = ['grid', 'tree', 'visualize'];
@@ -99,6 +100,14 @@
if (state.viewType() === 'tree') {
TreeView.expand(state.tree, namespace);
+ // During a reload, some tree nodes may already have been expanded.
+ // If so, call expand to re-glob their children.
+ var expandedNames = state.tree.expandedMap();
+ for (var name in expandedNames) {
+ if (expandedNames[name] === true) {
+ TreeView.expand(state.tree, name);
+ }
+ }
return namespaceService.getNamespaceItem(namespace)
.then(function(item) {
state.tree.put('rootItem', item);
@@ -161,4 +170,11 @@
default:
log.error('Unsupported viewType: ' + state.viewType);
}
+}
+
+// Clears any locally cached data
+function clearCache(state, namespace) {
+ state.put('items', mercury.array([]));
+ TreeView.clearCache(state.tree, namespace);
+ VisualizeView.clearCache();
}
\ No newline at end of file
diff --git a/src/components/browse/views/tree-view/index.js b/src/components/browse/views/tree-view/index.js
index 90f69b8..6b1742e 100644
--- a/src/components/browse/views/tree-view/index.js
+++ b/src/components/browse/views/tree-view/index.js
@@ -6,6 +6,8 @@
var insertCss = require('insert-css');
var extend = require('extend');
+var namespaceService = require('../../../../services/namespace/service');
+
var polymerEvent = require('../../../../lib/mercury/polymer-event');
var expand = require('./expand');
var getServiceIcon = require('../../get-service-icon');
@@ -16,6 +18,7 @@
module.exports = create;
module.exports.render = render;
module.exports.expand = expand;
+module.exports.clearCache = clearCache;
function create() {
@@ -138,3 +141,22 @@
});
});
}
+
+/*
+ * Given a name, it clears local cache of any items that match the name or
+ * are a suffix of it.
+ */
+function clearCache(state, namespace) {
+ deleteFromVarHashByPrefix(state.childrenMap, namespace);
+}
+
+function deleteFromVarHashByPrefix(varhash, prefix) {
+ var childrenKeys = Object.keys(varhash());
+ childrenKeys.forEach(function(ck) {
+ // Ideally we want a transaction here but VarHash does not support it yet
+ // https://github.com/nrw/observ-varhash/issues/15
+ if (namespaceService.prefixes(prefix, ck)) {
+ varhash.delete(ck);
+ }
+ });
+}
\ No newline at end of file
diff --git a/src/components/browse/views/visualize-view/index.js b/src/components/browse/views/visualize-view/index.js
index d0a76e8..3d384e2 100644
--- a/src/components/browse/views/visualize-view/index.js
+++ b/src/components/browse/views/visualize-view/index.js
@@ -10,83 +10,62 @@
var browseRoute = require('../../../../routes/browse');
var getServiceIcon = require('../../get-service-icon');
-var log = require('../../../../lib/log'
- )('components:browse:items:visualize-view');
+var log = require('../../../../lib/log')(
+ 'components:browse:items:visualize-view'
+);
var css = require('./index.css');
var h = mercury.h;
module.exports = create;
module.exports.render = render;
-
-// Maximum number of levels that are automatically loaded below the root
-// var MAX_AUTO_LOAD_DEPTH = 3;
+module.exports.clearCache = clearCache;
var DURATION = 500; // d3 animation duration
var STAGGER = 5; // mS delay for each node
var MIN_ZOOM = 0.5; // minimum zoom allowed
-var MAX_ZOOM = 20; // maximum zoom allowed
+var MAX_ZOOM = 20; // maximum zoom allowed
var SYMBOL_STROKE_COLOR = '#00838F';
-// var HAS_CHILDREN_COLOR = 'gray';
-// var NO_CHILDREN_COLOR = 'white';
-var SELECTED_COLOR = '#E65100'; // color of selected node
-var ZOOM_INC = 0.06; // zoom factor per animation frame
-var PAN_INC = 3; // pan per animation frame
-var ROT_INC = 0.5; // rotation per animation frame
+var SELECTED_COLOR = '#E65100'; // color of selected node
+var ZOOM_INC = 0.06; // zoom factor per animation frame
+var PAN_INC = 3; // pan per animation frame
+var ROT_INC = 0.5; // rotation per animation frame
var RELATIVE_ROOT = '<Home>';
-var widget; // instance of D3Widget
-var networkElem; // DOM element for visualization
-
-var selNode; // currently selected node
-var selectItem; // function to select item in app
-
-var width, height; // size of the visualization diagram
-
-var curX, curY, curR, curZ; // transforms (x, y, rotate, zoom)
-var modZ; // modified zoom for nodes and text
-
-var diagonal; // d3 diagonal projection for use by the node paths
-var treeD3; // d3 tree layout
-var svgBase, svgGroup; // svg elements
-
-var root; // data trees
-var rootIndex = {}; // index id to nodes
-
// keyboard key codes
-var KEY_PLUS = 187; // + (zoom in)
-var KEY_MINUS = 189; // - (zoom out)
-var KEY_PAGEUP = 33; // (rotate CCW)
-var KEY_PAGEDOWN = 34; // (rotate CW)
-var KEY_LEFT = 37; // left arrow
-var KEY_UP = 38; // up arrow
-var KEY_RIGHT = 39; // right arrow
-var KEY_DOWN = 40; // down arrow
-var KEY_SPACE = 32; // (expand node)
-var KEY_RETURN = 13; // (expand tree)
-var KEY_HOME = 36; // (center root)
-var KEY_END = 35; // (center selection)
+var KEY_PLUS = 187; // + (zoom in)
+var KEY_MINUS = 189; // - (zoom out)
+var KEY_PAGEUP = 33; // (rotate CCW)
+var KEY_PAGEDOWN = 34; // (rotate CW)
+var KEY_LEFT = 37; // left arrow
+var KEY_UP = 38; // up arrow
+var KEY_RIGHT = 39; // right arrow
+var KEY_DOWN = 40; // down arrow
+var KEY_SPACE = 32; // (expand node)
+var KEY_RETURN = 13; // (expand tree)
+var KEY_HOME = 36; // (center root)
+var KEY_END = 35; // (center selection)
-function create() { }
+function create() {}
+
+var widget;
+
+function clearCache() {
+ widget = null;
+}
function render(itemsState, browseState, browseEvents, navEvents) {
insertCss(css);
- // handle changed selection
- selNode = rootIndex[browseState.selectedItemName] || selNode;
-
- browseInto.browseState = browseState;
- browseInto.navEvents = navEvents;
-
- if (widget === undefined) {
- widget = new D3Widget(browseState, browseEvents);
+ if (!widget) {
+ widget = createWidget(browseState, browseEvents, navEvents);
} else {
widget.update(browseState, browseEvents);
}
return [
widget,
- h('div.vismenu', { // visualization menu
+ h('div.vismenu', { // visualization menu
}, [
h('paper-fab.zoom', {
attributes: {
@@ -96,8 +75,8 @@
title: 'Zoom In (+)',
'aria-label': 'zoom in'
},
- 'ev-down': polydown.bind(undefined, KEY_PLUS),
- 'ev-up': polyup.bind(undefined, KEY_PLUS)
+ 'ev-down': widget.polydown.bind(undefined, KEY_PLUS),
+ 'ev-up': widget.polyup.bind(undefined, KEY_PLUS)
}),
h('paper-fab.zoom', {
attributes: {
@@ -106,8 +85,8 @@
title: 'Zoom Out (\u2212)',
'aria-label': 'zoom out'
},
- 'ev-down': polydown.bind(undefined, KEY_MINUS),
- 'ev-up': polyup.bind(undefined, KEY_MINUS),
+ 'ev-down': widget.polydown.bind(undefined, KEY_MINUS),
+ 'ev-up': widget.polyup.bind(undefined, KEY_MINUS),
}),
h('paper-fab.rotate', {
attributes: {
@@ -116,8 +95,8 @@
title: 'Rotate CCW (Page Up)',
'aria-label': 'rotate counterclockwise'
},
- 'ev-down': polydown.bind(undefined, KEY_PAGEUP),
- 'ev-up': polyup.bind(undefined, KEY_PAGEUP)
+ 'ev-down': widget.polydown.bind(undefined, KEY_PAGEUP),
+ 'ev-up': widget.polyup.bind(undefined, KEY_PAGEUP)
}),
h('paper-fab.rotate', {
attributes: {
@@ -126,8 +105,8 @@
title: 'Rotate CW (Page Down)',
'aria-label': 'rotate clockwise'
},
- 'ev-down': polydown.bind(undefined, KEY_PAGEDOWN),
- 'ev-up': polyup.bind(undefined, KEY_PAGEDOWN)
+ 'ev-down': widget.polydown.bind(undefined, KEY_PAGEDOWN),
+ 'ev-up': widget.polyup.bind(undefined, KEY_PAGEDOWN)
}),
h('paper-fab.expand', {
attributes: {
@@ -136,321 +115,355 @@
title: 'Load +1 Level (space bar)',
'aria-label': 'load +1 level'
},
- 'ev-click': menu.bind(undefined, KEY_SPACE, false)
+ 'ev-click': widget.menu.bind(undefined, KEY_SPACE, false)
})
- ] ),
+ ]),
h('paper-shadow.contextmenu', { // context menu
- attributes: {
- z: 3 // height above background
- }
- }, [ // context menu
- h('paper-item',
- { 'ev-mouseup': menu.bind(undefined, KEY_RETURN, false),
- 'ev-mousedown': menu.bind(undefined, KEY_RETURN, false) },
- [ h('div.ecnode', 'Expand Node'), h('div.ksc', 'Return') ]),
- h('paper-item',
- { 'ev-mouseup': menu.bind(undefined, KEY_RETURN, true),
- 'ev-mousedown': menu.bind(undefined, KEY_RETURN, true) },
- [ h('div', 'Show Loaded'), h('div.ksc', '\u21E7 Return') ]),
- h('paper-item',
- { 'ev-mouseup': menu.bind(undefined, KEY_SPACE, false),
- 'ev-mousedown': menu.bind(undefined, KEY_SPACE, false) },
- [ h('div', 'Load +1 Level'), h('div.ksc', 'space bar') ]),
- h('paper-item',
- { 'ev-mouseup': menu.bind(undefined, KEY_SPACE, true),
- 'ev-mousedown': menu.bind(undefined, KEY_SPACE, true) },
- [ h('div', 'Browse Into'), h('div.ksc', '\u21E7 space bar') ]),
- h('paper-item',
- { 'ev-mouseup': menu.bind(undefined, KEY_END, false),
- 'ev-mousedown': menu.bind(undefined, KEY_END, false) },
- [ h('div', 'Center Selected'), h('div.ksc', 'End') ]),
- h('paper-item',
- { 'ev-mouseup': menu.bind(undefined, KEY_HOME, false),
- 'ev-mousedown': menu.bind(undefined, KEY_HOME, false) },
- [ h('div', 'Center Root'), h('div.ksc', 'Home') ])
- ] )
+ attributes: {
+ z: 3 // height above background
+ }
+ }, [ // context menu
+ h('paper-item', {
+ 'ev-mouseup': widget.menu.bind(undefined, KEY_RETURN, false),
+ 'ev-mousedown': widget.menu.bind(undefined, KEY_RETURN, false)
+ }, [h('div.ecnode', 'Expand Node'), h('div.ksc', 'Return')]),
+ h('paper-item', {
+ 'ev-mouseup': widget.menu.bind(undefined, KEY_RETURN, true),
+ 'ev-mousedown': widget.menu.bind(undefined, KEY_RETURN, true)
+ }, [h('div', 'Show Loaded'), h('div.ksc', '\u21E7 Return')]),
+ h('paper-item', {
+ 'ev-mouseup': widget.menu.bind(undefined, KEY_SPACE, false),
+ 'ev-mousedown': widget.menu.bind(undefined, KEY_SPACE, false)
+ }, [h('div', 'Load +1 Level'), h('div.ksc', 'space bar')]),
+ h('paper-item', {
+ 'ev-mouseup': widget.menu.bind(undefined, KEY_SPACE, true),
+ 'ev-mousedown': widget.menu.bind(undefined, KEY_SPACE, true)
+ }, [h('div', 'Browse Into'), h('div.ksc', '\u21E7 space bar')]),
+ h('paper-item', {
+ 'ev-mouseup': widget.menu.bind(undefined, KEY_END, false),
+ 'ev-mousedown': widget.menu.bind(undefined, KEY_END, false)
+ }, [h('div', 'Center Selected'), h('div.ksc', 'End')]),
+ h('paper-item', {
+ 'ev-mouseup': widget.menu.bind(undefined, KEY_HOME, false),
+ 'ev-mousedown': widget.menu.bind(undefined, KEY_HOME, false)
+ }, [h('div', 'Center Root'), h('div.ksc', 'Home')])
+ ])
];
}
-// Constructor for mercury widget for d3 element
-function D3Widget(browseState, browseEvents) {
- this.browseState = browseState;
- this.browseEvents = browseEvents;
-}
+function createWidget(browseState, browseEvents, navEvents) {
-D3Widget.prototype.type = 'Widget';
+ var networkElem; // DOM element for visualization
-D3Widget.prototype.init = function() {
- if (!networkElem) {
- networkElem = document.createElement('div');
- networkElem.className = 'network';
- networkElem.setAttribute('tabindex', 0); // allow focus
- selectItem = this.browseEvents.selectItem.bind(this.browseEvents);
- requestAnimationFrame(initD3);
+ var selNode; // currently selected node
+ var selectItem; // function to select item in app
+
+ var width, height; // size of the visualization diagram
+
+ var curX, curY, curR, curZ; // transforms (x, y, rotate, zoom)
+ var modZ; // modified zoom for nodes and text
+
+ var diagonal; // d3 diagonal projection for use by the node paths
+ var treeD3; // d3 tree layout
+ var svgBase, svgGroup; // svg elements
+
+ var root; // data trees
+ var rootIndex = {}; // index id to nodes
+
+ browseInto.browseState = browseState;
+ browseInto.navEvents = navEvents;
+
+ // Constructor for mercury widget for d3 element
+ function D3Widget(browseState, browseEvents) {
+ this.browseState = browseState;
+ this.browseEvents = browseEvents;
}
- // wrap in a new element, needed for Mercury vdom to patch properly.
- var wrapper = document.createElement('div');
- wrapper.className = 'networkParent';
- wrapper.appendChild(networkElem);
+ D3Widget.prototype.type = 'Widget';
- requestAnimationFrame(this.updateRoot.bind(this));
-
- return wrapper;
-};
-
-// Keep track of previous namespace that was browsed to so we can
-// know when navigating to a different namespace happens.
-var previousNamespace;
-
-D3Widget.prototype.update = function(browseState, browseEvents) {
- this.browseState = browseState;
- this.browseEvents = browseEvents;
-
- // check to see if window was resized while we were away
- if (width !== networkElem.offsetWidth) { resize(); }
-
- this.updateRoot();
-
- networkElem.focus();
-};
-
-// build new data tree
-D3Widget.prototype.updateRoot = function() {
- var rootNodeId = this.browseState.namespace;
-
- if (previousNamespace !== rootNodeId) {
-
- previousNamespace = rootNodeId;
-
- // parse root id
- var parts = namespaceService.util.parseName(rootNodeId);
-
- var isRooted = namespaceService.util.isRooted(rootNodeId);
- var buildId = '';
- if (!isRooted) { parts.unshift(''); } // create <Home> node
-
- var parent; // used to connect each new node to their parent
-
- parts.forEach(function(v) {
- buildId += (buildId.length > 0 || isRooted ? '/' : '') + v;
- var nn = rootIndex[buildId] || {};
- var isNew = (nn.id === undefined);
- nn.id = buildId;
- nn.name = v || RELATIVE_ROOT;
- nn.isLeaf = false;
- nn.hasServer = true; // assume true, fix later
- nn.hasMountPoint = true; // assume true, fix later
- if (parent !== undefined) {
- nn.parent = parent;
- if (parent.children === undefined && parent._children === undefined) {
- parent._children = [nn]; // initially hidden
- }
- }
- if (isNew) {
- rootIndex[buildId] = nn;
- }
- parent = nn;
- });
-
- root = parent; // new root
-
- if (selNode === undefined) {
- selectNode(root);
+ D3Widget.prototype.init = function() {
+ if (!networkElem) {
+ networkElem = document.createElement('div');
+ networkElem.className = 'network';
+ networkElem.setAttribute('tabindex', 0); // allow focus
+ selectItem = this.browseEvents.selectItem.bind(this.browseEvents);
+ requestAnimationFrame(initD3);
}
- loadItem(root); // load rest of information for this node
- loadSubItems(root); // load the children
- }
+ // wrap in a new element, needed for Mercury vdom to patch properly.
+ var wrapper = document.createElement('div');
+ wrapper.className = 'networkParent';
+ wrapper.appendChild(networkElem);
- updateD3(root, true); // always animate
-};
+ requestAnimationFrame(this.updateRoot.bind(this));
-// initialize d3 HTML elements
-function initD3() {
+ return wrapper;
+ };
- // size of the diagram
- width = networkElem.offsetWidth;
- height = networkElem.offsetHeight;
+ // Keep track of previous namespace that was browsed to so we can
+ // know when navigating to a different namespace happens.
+ var previousNamespace;
- // current pan, zoom, and rotation
- curX = width / 2; // center
- curY = height / 2;
- curZ = modZ = 1.0; // current zoom
- curR = 270; // current rotation
+ D3Widget.prototype.polyup = polyup;
+ D3Widget.prototype.polydown = polydown;
+ D3Widget.prototype.menu = menu;
+ D3Widget.prototype.update = function(browseState, browseEvents) {
- // d3 diagonal projection for use by the node paths
- diagonal= d3.svg.diagonal.radial().
+ // handle changed selection
+ selNode = rootIndex[browseState.selectedItemName] || selNode;
+
+ this.browseState = browseState;
+ this.browseEvents = browseEvents;
+
+ // check to see if window was resized while we were away
+ if (width !== networkElem.offsetWidth) {
+ resize();
+ }
+
+ this.updateRoot();
+
+ networkElem.focus();
+ };
+
+ // build new data tree
+ D3Widget.prototype.updateRoot = function() {
+ var rootNodeId = this.browseState.namespace;
+
+ if (previousNamespace !== rootNodeId) {
+
+ previousNamespace = rootNodeId;
+
+ // parse root id
+ var parts = namespaceService.util.parseName(rootNodeId);
+
+ var isRooted = namespaceService.util.isRooted(rootNodeId);
+ var buildId = '';
+ if (!isRooted) {
+ parts.unshift('');
+ } // create <Home> node
+
+ var parent; // used to connect each new node to their parent
+
+ parts.forEach(function(v) {
+ buildId += (buildId.length > 0 || isRooted ? '/' : '') + v;
+ var nn = rootIndex[buildId] || {};
+ var isNew = (nn.id === undefined);
+ nn.id = buildId;
+ nn.name = v || RELATIVE_ROOT;
+ nn.isLeaf = false;
+ nn.hasServer = true; // assume true, fix later
+ nn.hasMountPoint = true; // assume true, fix later
+ if (parent !== undefined) {
+ nn.parent = parent;
+ if (parent.children === undefined && parent._children === undefined) {
+ parent._children = [nn]; // initially hidden
+ }
+ }
+ if (isNew) {
+ rootIndex[buildId] = nn;
+ }
+ parent = nn;
+ });
+
+ root = parent; // new root
+
+ if (selNode === undefined) {
+ selectNode(root);
+ }
+
+ loadItem(root); // load rest of information for this node
+ loadSubItems(root); // load the children
+ }
+
+ updateD3(root, true); // always animate
+ };
+
+ // initialize d3 HTML elements
+ function initD3() {
+
+ // size of the diagram
+ width = networkElem.offsetWidth;
+ height = networkElem.offsetHeight;
+
+ // current pan, zoom, and rotation
+ curX = width / 2; // center
+ curY = height / 2;
+ curZ = modZ = 1.0; // current zoom
+ curR = 270; // current rotation
+
+ // d3 diagonal projection for use by the node paths
+ diagonal = d3.svg.diagonal.radial().
projection(function(d) {
- return [d.y, d.x / 180 * Math.PI];
+ return [d.y, d.x / 180 * Math.PI];
});
- // d3 tree layout
- treeD3 = d3.layout.tree().
- // circular coordinates to fit in window
- // 120 is to allow space for text strings
+ // d3 tree layout
+ treeD3 = d3.layout.tree().
+ // circular coordinates to fit in window
+ // 120 is to allow space for text strings
size([360, Math.min(width, height) / 2 - 120]).
- // space between nodes, depends on if they have same parent
- // dividing by a.depth is for radial coordinates
+ // space between nodes, depends on if they have same parent
+ // dividing by a.depth is for radial coordinates
separation(function(a, b) {
- return (a.parent === b.parent ? 1 : 2) / (a.depth + 1);
+ return (a.parent === b.parent ? 1 : 2) / (a.depth + 1);
});
- // define the svgBase, attaching a class for styling and the zoomListener
- svgBase = d3.select('.network').append('svg').
+ // define the svgBase, attaching a class for styling and the zoomListener
+ svgBase = d3.select('.network').append('svg').
attr('width', width).
attr('height', height).
attr('class', 'overlay').
on('mousedown', mousedown);
- // Group which holds all nodes and manages pan, zoom, rotate
- svgGroup = svgBase.append('g').
+ // Group which holds all nodes and manages pan, zoom, rotate
+ svgGroup = svgBase.append('g').
attr('transform', 'translate(' + curX + ',' + curY + ')');
- networkElem.focus();
- d3.select('.network'). // set up document events
- on('wheel', wheel). // zoom, rotate
+ networkElem.focus();
+ d3.select('.network'). // set up document events
+ on('wheel', wheel). // zoom, rotate
on('keydown', keydown).
on('keyup', keyup).
on('mouseover', function() {
networkElem.focus();
});
- d3.select(window).on('resize', resize);
-}
+ d3.select(window).on('resize', resize);
+ }
-// draw tree using d3js
-// subroot - source node of the update
-// doAni - whether to do a transition animation
-function updateD3(subroot, doAni) {
- // length of d3 animation
- var duration = (d3.event && d3.event.altKey ? DURATION * 4 : DURATION);
+ // draw tree using d3js
+ // subroot - source node of the update
+ // doAni - whether to do a transition animation
+ function updateD3(subroot, doAni) {
+ // length of d3 animation
+ var duration = (d3.event && d3.event.altKey ? DURATION * 4 : DURATION);
- // Compute the new tree layout.
- var d3nodes = treeD3.nodes(root);
- var d3links = treeD3.links(d3nodes);
+ // Compute the new tree layout.
+ var d3nodes = treeD3.nodes(root);
+ var d3links = treeD3.links(d3nodes);
- // Update the view
- var view = doAni ? svgGroup.transition().duration(duration) : svgGroup;
+ // Update the view
+ var view = doAni ? svgGroup.transition().duration(duration) : svgGroup;
- view.attr('transform',
+ view.attr('transform',
'rotate(' + curR + ' ' + curX + ' ' + curY +
')translate(' + curX + ' ' + curY +
')scale(' + curZ + ')');
- var gnode = svgGroup.selectAll('g.node').
- data(d3nodes, function(d) {
+ var gnode = svgGroup.selectAll('g.node').
+ data(d3nodes, function(d) {
return d.id;
- });
+ });
- // Enter any new nodes at the parent's previous position
- var nodeEnter = gnode.enter().insert('g', ':first-child').
- attr('class', 'node').
- attr('opacity', 0).
- attr('transform', 'rotate(' + (subroot.x - 90) +
+ // Enter any new nodes at the parent's previous position
+ var nodeEnter = gnode.enter().insert('g', ':first-child').
+ attr('class', 'node').
+ attr('opacity', 0).
+ attr('transform', 'rotate(' + (subroot.x - 90) +
')translate(' + subroot.y + ')').
- on('click', click).on('dblclick', dblclick).
- on('contextmenu', showContextMenu);
+ on('click', click).on('dblclick', dblclick).
+ on('contextmenu', showContextMenu);
- nodeEnter.append('title').text(function(d) {
- return getServiceIcon(d).title;
- });
+ nodeEnter.append('title').text(function(d) {
+ return getServiceIcon(d).title;
+ });
- nodeEnter.filter(function(d) { // Mount Point
- return d.hasMountPoint;
- }).append('path').attr('class', 'mountpointicon').
- attr('d', d3.svg.symbol().type('square'));
+ nodeEnter.filter(function(d) { // Mount Point
+ return d.hasMountPoint;
+ }).append('path').attr('class', 'mountpointicon').
+ attr('d', d3.svg.symbol().type('square'));
- nodeEnter.filter(function(d) { // Server
- return d.hasServer;
- }).append('path').attr('class', 'servericon').
- attr('d', d3.svg.symbol().type('circle').size(60));
+ nodeEnter.filter(function(d) { // Server
+ return d.hasServer;
+ }).append('path').attr('class', 'servericon').
+ attr('d', d3.svg.symbol().type('circle').size(60));
- nodeEnter.append('text').
- text(function(d) { return d.name; }).
- attr('transform', ((subroot.x + curR) % 360 <= 180 ?
- 'rotate(-7)translate(8)scale(' : 'rotate(187)translate(-8)scale('
- ) + modZ + ')' );
+ nodeEnter.append('text').
+ text(function(d) {
+ return d.name;
+ }).
+ attr('transform', ((subroot.x + curR) % 360 <= 180 ?
+ 'rotate(-7)translate(8)scale(' : 'rotate(187)translate(-8)scale('
+ ) + modZ + ')');
- // update existing graph nodes
+ // update existing graph nodes
- // set path style for selection, loading, and scale
- gnode.select('path').
- attr('transform', 'scale(' + modZ + ')').
- classed('loading', function(d) { return d.loading; }).
- // style('fill', function(d) {
- // // only show children_color if the children are not shown
- // return d.isLeaf || d.children ? NO_CHILDREN_COLOR : HAS_CHILDREN_COLOR;
- // }).
- attr('stroke', function(d) {
+ // set path style for selection, loading, and scale
+ gnode.select('path').
+ attr('transform', 'scale(' + modZ + ')').
+ classed('loading', function(d) {
+ return d.loading;
+ }).
+ attr('stroke', function(d) {
return d === selNode ? SELECTED_COLOR : SYMBOL_STROKE_COLOR;
- }).
- attr('stroke-width', function(d) {
+ }).
+ attr('stroke-width', function(d) {
return d === selNode ? 3 : 2;
- });
+ });
- gnode.select('title').text(function(d) {
- return getServiceIcon(d).title;
- });
+ gnode.select('title').text(function(d) {
+ return getServiceIcon(d).title;
+ });
- // remove icons if assumed true but turn out to be false
- gnode.select('path.servericon').filter(function(d) {
- return !d.hasServer;
- }).remove();
- gnode.select('path.mountpointicon').filter(function(d) {
- return !d.hasMountPoint;
- }).remove();
+ // remove icons if assumed true but turn out to be false
+ gnode.select('path.servericon').filter(function(d) {
+ return !d.hasServer;
+ }).remove();
+ gnode.select('path.mountpointicon').filter(function(d) {
+ return !d.hasMountPoint;
+ }).remove();
- gnode.select('text').
- attr('text-anchor', function(d) {
+ gnode.select('text').
+ attr('text-anchor', function(d) {
return (d.x + curR) % 360 <= 180 ? 'start' : 'end';
- }).
- attr('transform', function(d) {
- return ((d.x + curR) % 360 <= 180 ?
+ }).
+ attr('transform', function(d) {
+ return ((d.x + curR) % 360 <= 180 ?
'rotate(-7)translate(8)scale(' :
'rotate(187)translate(-8)scale('
- ) + modZ +')';
- }).
- attr('fill', function(d) {
+ ) + modZ + ')';
+ }).
+ attr('fill', function(d) {
return d === selNode ? SELECTED_COLOR : 'black';
- }).
- attr('dy', '5px');
+ }).
+ attr('dy', '5px');
- var nodeUpdate = (doAni ?
- gnode.transition().duration(duration).
+ var nodeUpdate = (doAni ?
+ gnode.transition().duration(duration).delay(function(d, i) {
+ return i * STAGGER + Math.max(0, d.depth - selNode.depth);
+ }) : gnode);
+
+ nodeUpdate.attr('transform', function(d) {
+ return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')';
+ }).style('opacity', 1);
+
+ nodeUpdate.select('path').
+ attr('transform', 'scale(' + modZ + ')');
+
+ // Transition exiting nodes to the parent's new position and remove
+ var nodeExit = doAni ? gnode.exit().transition().duration(duration).
delay(function(d, i) {
- return i * STAGGER + Math.max(0, d.depth - selNode.depth);
- }) : gnode);
+ return i * STAGGER;
+ }): gnode.exit();
- nodeUpdate.attr('transform', function(d) {
- return 'rotate(' + (d.x - 90) + ')translate(' + d.y + ')';
- }).style('opacity', 1);
+ nodeExit.attr('transform', function(d) {
+ return 'rotate(' + (subroot.x - 90) + ')translate(' + subroot.y + ')';
+ }).
+ attr('opacity', 0).
+ remove();
- nodeUpdate.select('path').
- attr('transform', 'scale(' + modZ + ')');
+ nodeExit.select('.node path').attr('transform', 'scale(0)');
+ nodeExit.select('.node text').style('fill-opacity', 0);
- // Transition exiting nodes to the parent's new position and remove
- var nodeExit = doAni ? gnode.exit().transition().duration(duration).
- delay(function(d, i) { return i * STAGGER; }) : gnode.exit();
+ // Update the links…
+ var glink = svgGroup.selectAll('path.link').
+ data(d3links, function(d) {
+ return d.target.id;
+ });
- nodeExit.attr('transform', function(d) {
- return 'rotate(' + (subroot.x - 90) +')translate(' + subroot.y + ')';
- }).
- attr('opacity', 0).
- remove();
-
- nodeExit.select('.node path').attr('transform', 'scale(0)');
- nodeExit.select('.node text').style('fill-opacity', 0);
-
- // Update the links…
- var glink = svgGroup.selectAll('path.link').
- data(d3links, function(d) {
- return d.target.id;
- });
-
- // Enter any new links at the parent's previous position
- glink.enter().insert('path', 'g').
- attr('class', 'link').
- attr('d', function() {
+ // Enter any new links at the parent's previous position
+ glink.enter().insert('path', 'g').
+ attr('class', 'link').
+ attr('d', function() {
var o = {
x: subroot.x,
y: subroot.y
@@ -459,18 +472,17 @@
source: o,
target: o
});
- });
+ });
- // Transition links to their new position
- (doAni ? glink.transition().duration(duration).
- delay(function(d, i) {
- return i * STAGGER + Math.max(0, d.source.depth - selNode.depth);
- }) : glink).
- attr('d', diagonal);
+ // Transition links to their new position
+ (doAni ? glink.transition().duration(duration).delay(function(d, i) {
+ return i * STAGGER + Math.max(0, d.source.depth - selNode.depth);
+ }) : glink).
+ attr('d', diagonal);
- // Transition exiting nodes to the parent's new position
- (doAni ? glink.exit().transition().duration(duration) : glink.exit()).
- attr('d', function() {
+ // Transition exiting nodes to the parent's new position
+ (doAni ? glink.exit().transition().duration(duration) : glink.exit()).
+ attr('d', function() {
var o = {
x: subroot.x,
y: subroot.y
@@ -479,518 +491,559 @@
source: o,
target: o
});
- }).
- remove(); // remove edge at end of animation
-} // end updateD3
+ }).
+ remove(); // remove edge at end of animation
+ } // end updateD3
-// find place to insert new node in children
-var bisectfun = d3.bisector(function(d) { return d.name; }).right;
+ // find place to insert new node in children
+ var bisectfun = d3.bisector(function(d) {
+ return d.name;
+ }).right;
-// create node or merge new item data into it
-function mergeNode(item, parent) {
- var nn = rootIndex[item.objectName] || {};
- var isNew = nn.id === undefined; // not found in rootIndex
- nn.id = item.objectName;
- nn.name = item.mountedName || RELATIVE_ROOT;
- nn.parent = parent || nn.parent;
- nn.isLeaf = item.isLeaf;
- nn.hasMountPoint = item.hasMountPoint;
- nn.hasServer = item.hasServer;
- if (isNew && parent !== undefined) { // insert node in proper place
- rootIndex[nn.id] = nn;
- if (parent.children === undefined) {
- parent.children = [nn];
+ // create node or merge new item data into it
+ function mergeNode(item, parent) {
+ var nn = rootIndex[item.objectName] || {};
+ var isNew = nn.id === undefined; // not found in rootIndex
+ nn.id = item.objectName;
+ nn.name = item.mountedName || RELATIVE_ROOT;
+ nn.parent = parent || nn.parent;
+ nn.isLeaf = item.isLeaf;
+ nn.hasMountPoint = item.hasMountPoint;
+ nn.hasServer = item.hasServer;
+ if (isNew && parent !== undefined) { // insert node in proper place
+ rootIndex[nn.id] = nn;
+ if (parent.children === undefined) {
+ parent.children = [nn];
+ } else {
+ parent.children.splice(bisectfun(parent.children, nn.name), 0, nn);
+ }
+ }
+ return isNew; // need to animate it in
+ } // end mergeNode
+
+ function loadItem(node) { // load a single item (used for root of tree)
+ namespaceService.getNamespaceItem(node.id).then(function(observable) {
+ mercury.watch(observable, updateItem);
+
+ function updateItem(item) { // currently only gets called once
+ mergeNode(item, node.parent); // update elsewhere
+ }
+ });
+ }
+
+ // load children items asynchronously
+ function loadSubItems(node) {
+ if (node.subNodesLoaded) {
+ return;
+ }
+ var namespace = node.id;
+ if (node._children) {
+ node.children = node._children;
+ node._children = null;
+ }
+ node.subNodesLoaded = true;
+ showLoading(node, true); // node is loading
+
+ namespaceService.getChildren(namespace).then(function(resultObservable) {
+ var initialValues = resultObservable();
+ initialValues.forEach(function(item) {
+ batchUpdate(node, mergeNode(item, node));
+ });
+ resultObservable.events.once('end', function() {
+ showLoading(node, false); // node no longer loading
+ });
+
+ resultObservable(updatedValues);
+
+ function updatedValues(results) {
+ // TODO(wmleler) support removed and updated nodes for watchGlob
+ var item = results._diff[0][2]; // changed item from Mercury
+ batchUpdate(node, mergeNode(item, node));
+ }
+ }).catch(function(err) {
+ log.error('glob failed', err);
+ });
+ } // end loadSubItems
+
+ // batch up groups of updates to speed up transitions
+ var batchNode = null;
+ var batchId = null;
+
+ function batchUpdate(node, doAni) {
+ if (node !== batchNode) {
+ if (batchNode !== null) {
+ updateD3(batchNode, doAni);
+ }
+ batchNode = node;
+ if (batchId !== null) {
+ clearTimeout(batchId);
+ batchId = null;
+ }
} else {
- parent.children.splice(bisectfun(parent.children, nn.name), 0, nn);
+ batchId = setTimeout(function() {
+ updateD3(batchNode, doAni);
+ batchId = null;
+ }, DURATION);
}
}
- return isNew; // need to animate it in
-} // end mergeNode
-function loadItem(node) { // load a single item (used for root of tree)
- namespaceService.getNamespaceItem(node.id).then(function(observable) {
- mercury.watch(observable, updateItem);
-
- function updateItem(item) { // currently only gets called once (no updates)
- mergeNode(item, node.parent); // update elsewhere
+ function selectNode(node) { // highlight node and show details
+ if (node === selNode) {
+ return;
}
- });
-}
-
-// load children items asynchronously
-function loadSubItems(node) {
- if (node.subNodesLoaded) { return; }
- var namespace = node.id;
- if (node._children) {
- node.children = node._children;
- node._children = null;
+ selNode = node;
+ selectItem({
+ name: node.id
+ }); // notify rest of app
}
- node.subNodesLoaded = true;
- showLoading(node, true); // node is loading
- namespaceService.getChildren(namespace).then(function(resultObservable) {
- var initialValues = resultObservable();
- initialValues.forEach(function(item) {
- batchUpdate(node, mergeNode(item, node));
+ function browseInto(node) { // make this node the root
+ var browseUrl = browseRoute.createUrl(browseInto.browseState, {
+ namespace: node.id
});
- resultObservable.events.once('end', function() {
- showLoading(node, false); // node no longer loading
+ browseInto.navEvents.navigate({
+ path: browseUrl
});
-
- resultObservable(updatedValues);
-
- function updatedValues(results) {
- // TODO(wmleler) support removed and updated nodes for watchGlob
- var item = results._diff[0][2]; // changed item from Mercury
- batchUpdate(node, mergeNode(item, node));
- }
- }).catch(function(err) {
- log.error('glob failed', err);
- });
-} // end loadSubItems
-
-// batch up groups of updates to speed up transitions
-var batchNode = null;
-var batchId = null;
-
-function batchUpdate(node, doAni) {
- if (node !== batchNode) {
- if (batchNode !== null) { updateD3(batchNode, doAni); }
- batchNode = node;
- if (batchId !== null) {
- clearTimeout(batchId);
- batchId = null;
- }
- } else {
- batchId = setTimeout(function() {
- updateD3(batchNode, doAni);
- batchId = null;
- }, DURATION);
}
-}
-function selectNode(node) { // highlight node and show details
- if (node === selNode) { return; }
- selNode = node;
- selectItem({ name: node.id }); // notify rest of app
-}
-
-function browseInto(node) { // make this node the root
- var browseUrl = browseRoute.createUrl(browseInto.browseState, {
- namespace: node.id
- });
- browseInto.navEvents.navigate({ path: browseUrl });
-}
-
-// set view with no animation
-function setview() {
- svgGroup.attr('transform',
+ // set view with no animation
+ function setview() {
+ svgGroup.attr('transform',
'rotate(' + curR + ' ' + curX + ' ' + curY +
')translate(' + curX + ' ' + curY +
')scale(' + curZ + ')');
- svgGroup.selectAll('text').
- attr('text-anchor', function(d) {
- return (d.x + curR) % 360 <= 180 ? 'start' : 'end';
- }).
- attr('transform', function(d) {
- return ((d.x + curR) % 360 <= 180 ?
- 'rotate(-7)translate(8)scale(' :
- 'rotate(187)translate(-8)scale('
- ) + modZ +')';
- });
- svgGroup.selectAll('.node path').
+ svgGroup.selectAll('text').
+ attr('text-anchor', function(d) {
+ return (d.x + curR) % 360 <= 180 ? 'start' : 'end';
+ }).
+ attr('transform', function(d) {
+ return ((d.x + curR) % 360 <= 180 ?
+ 'rotate(-7)translate(8)scale(' :
+ 'rotate(187)translate(-8)scale('
+ ) + modZ + ')';
+ });
+ svgGroup.selectAll('.node path').
attr('transform', 'scale(' + modZ + ')').
attr('stroke-width', function(d) {
- return d === selNode ? 3 : 2;
+ return d === selNode ? 3 : 2;
});
-}
-
-// show nodes that are loading
-function showLoading(node, v) {
- node.loading = v;
- svgGroup.selectAll('.node path').
- classed('loading', function(d) { return d.loading; });
-}
-
-//
-// Helper functions for collapsing and expanding nodes
-//
-
-// Toggle expand / collapse
-function toggle(d) {
- if (d.children) {
- d._children = d.children;
- d.children = null;
- } else if (d._children && d.subNodesLoaded) {
- d.children = d._children;
- d._children = null;
- } else {
- loadSubItems(d);
}
-}
-function collapse(d) { // collapse one level
- if (d.children) {
- d._children = d.children;
- d.children = null;
+ // show nodes that are loading
+ function showLoading(node, v) {
+ node.loading = v;
+ svgGroup.selectAll('.node path').
+ classed('loading', function(d) {
+ return d.loading;
+ });
}
-}
-// expand all loaded children and descendents
-function expandTree(d) {
- if (d._children) {
- d.children = d._children;
- d._children = null;
- }
- if (d.children) {
- d.children.forEach(expandTree);
- }
-}
+ //
+ // Helper functions for collapsing and expanding nodes
+ //
-// expand one level of tree using breadth first search
-function expand1Level(d) {
- var q = [d]; // non-recursive using queue
- var cn;
- var done = null;
- while (q.length > 0) {
- cn = q.shift();
- if (done !== null && done < cn.depth) { return; }
- if (cn._children) {
- done = cn.depth;
- cn.children = cn._children;
- cn._children = null;
- cn.children.forEach(collapse);
- } else if (!(cn.isLeaf || cn.subNodesLoaded)) {
- done = cn.depth;
- loadSubItems(cn);
- }
- if (cn.children) {
- q = q.concat(cn.children);
+ // Toggle expand / collapse
+ function toggle(d) {
+ if (d.children) {
+ d._children = d.children;
+ d.children = null;
+ } else if (d._children && d.subNodesLoaded) {
+ d.children = d._children;
+ d._children = null;
+ } else {
+ loadSubItems(d);
}
}
- // no nodes to open
-}
-var moveX = 0, moveY = 0, moveZ = 0, moveR = 0; // animations
-var keysdown = []; // which keys are currently down
-var animation = null;
-var aniTime = null; // time since last animation frame
-
-// update animation frame
-function frame(frametime) {
- var diff = aniTime ? (frametime - aniTime) / 16 : 0;
- aniTime = frametime;
-
- var dz = Math.pow(1.2, diff * moveZ);
- var newZ = limitZ(curZ * dz);
- dz = newZ / curZ;
- curZ = newZ;
- modZ = Math.pow(1.1, -curZ); // limit text and node size as scale increases
- curX += diff * moveX - (width / 2- curX) * (dz - 1);
- curY += diff * moveY - (height / 2 - curY) * (dz - 1);
- curR = limitR(curR + diff * moveR);
- setview();
- animation = requestAnimationFrame(frame);
-}
-
-// enforce zoom extent
-function limitZ(z) {
- return Math.max(Math.min(z, MAX_ZOOM), MIN_ZOOM);
-}
-
-// keep rotation between 0 and 360
-function limitR(r) {
- return (r + 360) % 360;
-}
-
-//
-// d3 event handlers
-//
-
-function resize() { // window resize
- if (networkElem.offsetWidth === 0) { return; }
- var oldwidth = width;
- var oldheight = height;
- width = networkElem.offsetWidth;
- height = networkElem.offsetHeight;
- treeD3.size([360, Math.min(width, height) / 2 - 120]);
- svgBase.attr('width', width).attr('height', height);
- curX += (width - oldwidth) / 2;
- curY += (height - oldheight) / 2;
- svgGroup.attr('transform',
- 'rotate(' + curR + ' ' + curX + ' ' + curY +
- ')translate(' + curX + ' ' + curY +
- ')scale(' + curZ + ')');
- updateD3(root, false);
-}
-
-function click(d) { // Select node
- if (d3.event.defaultPrevented || d === selNode) { return; }
- selectNode(d);
- updateD3(d, false);
- d3.event.preventDefault();
-}
-
-function dblclick(d) { // Toggle children of node
- if (d3.event.defaultPrevented) { return; } // click suppressed
- d3.event.preventDefault();
- if (d3.event.shiftKey) {
- expand1Level(d);
- } else {
- toggle(d);
+ function collapse(d) { // collapse one level
+ if (d.children) {
+ d._children = d.children;
+ d.children = null;
+ }
}
- updateD3(d, true);
-}
-var startposX, startposY; // initial position on mouse button down for pan
+ // expand all loaded children and descendents
+ function expandTree(d) {
+ if (d._children) {
+ d.children = d._children;
+ d._children = null;
+ }
+ if (d.children) {
+ d.children.forEach(expandTree);
+ }
+ }
-function mousedown() { // pan action from mouse drag
- if (d3.event.which !== 1) { return; } // ingore other mouse buttons
- startposX = curX - d3.event.clientX;
- startposY = curY - d3.event.clientY;
- d3.select(document).on('mousemove', mousemove, true);
- d3.select(document).on('mouseup', mouseup, true);
- networkElem.focus();
- d3.event.preventDefault();
-}
+ // expand one level of tree using breadth first search
+ function expand1Level(d) {
+ var q = [d]; // non-recursive using queue
+ var cn;
+ var done = null;
+ while (q.length > 0) {
+ cn = q.shift();
+ if (done !== null && done < cn.depth) {
+ return;
+ }
+ if (cn._children) {
+ done = cn.depth;
+ cn.children = cn._children;
+ cn._children = null;
+ cn.children.forEach(collapse);
+ } else if (!(cn.isLeaf || cn.subNodesLoaded)) {
+ done = cn.depth;
+ loadSubItems(cn);
+ }
+ if (cn.children) {
+ q = q.concat(cn.children);
+ }
+ }
+ // no nodes to open
+ }
-function mousemove() { // drag
- curX = startposX + d3.event.clientX;
- curY = startposY + d3.event.clientY;
- setview();
- d3.event.preventDefault();
-}
+ var moveX = 0,
+ moveY = 0,
+ moveZ = 0,
+ moveR = 0; // animations
+ var keysdown = []; // which keys are currently down
+ var animation = null;
+ var aniTime = null; // time since last animation frame
-function mouseup() { // cleanup
- d3.select(document).on('mousemove', null);
- d3.select(document).on('mouseup', null);
-}
+ // update animation frame
+ function frame(frametime) {
+ var diff = aniTime ? (frametime - aniTime) / 16 : 0;
+ aniTime = frametime;
-function wheel() { // mousewheel (including left-right)
- var dz, newZ;
- var slow = (d3.event && d3.event.altKey) ? 0.25 : 1;
- if (d3.event.wheelDeltaY !== 0) { // up-down = zoom
- dz = Math.pow(1.2, d3.event.wheelDeltaY * 0.001 * slow);
- newZ = limitZ(curZ * dz);
+ var dz = Math.pow(1.2, diff * moveZ);
+ var newZ = limitZ(curZ * dz);
dz = newZ / curZ;
curZ = newZ;
- // zoom around mouse position
- curX -= (d3.event.clientX - curX) * (dz - 1);
- curY -= (d3.event.clientY - curY) * (dz - 1);
+ modZ = Math.pow(1.1, -curZ); // limit text and node size as scale increases
+ curX += diff * moveX - (width / 2 - curX) * (dz - 1);
+ curY += diff * moveY - (height / 2 - curY) * (dz - 1);
+ curR = limitR(curR + diff * moveR);
setview();
- }
- if (d3.event.wheelDeltaX !== 0) { // left-right = rotate
- curR = limitR(curR + d3.event.wheelDeltaX * 0.01 * slow);
- updateD3(root, false);
- }
-}
-
-function polydown(key, evt) { // polymer ev-mousedown event
- actionDown(key, evt.shiftKey, evt.altKey);
-}
-
-function polyup(key, evt) { // polymer ev-mouseup event
- actionUp(key);
-}
-
-function menu(key, shift, evt) { // context menu selection event
- if (evt === undefined) { // shiftkey not supplied
- evt = shift;
- shift = evt.shiftKey;
- }
- actionDown(key, shift, evt.altKey);
- networkElem.focus();
-}
-
-function keydown() { // d3 keydown event
- var evt = d3.event;
- if (evt.repeat) { return; }
- actionDown(evt.which, evt.shiftKey, evt.altKey);
-}
-
-function keyup() { // d3 keyup event
- var evt = d3.event;
- actionUp(evt.which);
-}
-
-// right click, show context menu and select this node
-function showContextMenu(d) {
- d3.event.preventDefault();
- d3.select('.ecnode').text(
- (d.children ? 'Collapse ' : 'Expand ') + 'Node' );
- var cmenu = d3.select('.contextmenu');
- cmenu.style({
- left: Math.min(d3.event.offsetX + 3,
- width - cmenu.style('width').replace('px', '') - 5) + 'px',
- top: (d3.event.offsetY + 8) + 'px',
- display: 'block'
- });
- var doc = d3.select(document);
- doc.on('mousedown.cm', hideContextMenu, true);
- setTimeout(function() {
- doc.on('mouseup.cm', hideContextMenu, true);
- }, 500);
- selectNode(d);
-}
-
-function hideContextMenu() {
- var doc = d3.select(document);
- d3.select('.contextmenu').style('display', 'none');
- doc.on('mouseup.cm', null);
- doc.on('mousedown.cm', null);
- networkElem.focus();
-}
-
-
-// Event actions
-// Almost all UI actions pass through here,
-// even if they are not originally generated from the keyboard
-// There are two types of actions:
-// * Press-and-Hold actions perform some action while they are pressed,
-// until they are released, like pan, zoom, and rotate. These actions end
-// with "break", so the key can be saved, and actionUp can stop the action.
-// * Click actions mostly happen on keydown, like toggling children.
-function actionDown(key, shift, alt) {
- var parch; // parent's children
- var slow = alt ? 0.25 : 1;
- if (keysdown.indexOf(key) >= 0) { return; } // defeat auto repeat
- switch(key) {
- case KEY_PLUS: // zoom in
- moveZ = ZOOM_INC * slow;
- break;
- case KEY_MINUS: // zoom out
- moveZ = -ZOOM_INC * slow;
- break;
- case KEY_PAGEUP: // rotate counterclockwise
- moveR = -ROT_INC * slow;
- break;
- case KEY_PAGEDOWN: // rotate clockwise
- moveR = ROT_INC * slow;
- break;
- case KEY_LEFT:
- if (shift) { // move selection to parent
- if (!selNode) {
- selectNode(root);
- } else if (selNode.parent) {
- selectNode(selNode.parent);
- updateD3(selNode, false);
- }
- return;
- }
- moveX = -PAN_INC * slow; // pan left
- break;
- case KEY_UP:
- if (shift) { // move selection to previous child
- if (!selNode) {
- selectNode(root);
- } else if (selNode.parent) {
- parch = selNode.parent.children;
- selectNode(parch[(parch.indexOf(selNode) +
- parch.length - 1) % parch.length]);
- updateD3(selNode, false);
- }
- return;
- }
- moveY = -PAN_INC * slow; // pan up
- break;
- case KEY_RIGHT:
- if (shift) { // move selection to first/last child
- if (!selNode) {
- selectNode(root);
- } else {
- if (selNode.children && selNode.children.length > 0) {
- selectNode(selNode.children[0]);
- updateD3(selNode, false);
- }
- }
- return;
- }
- moveX = PAN_INC * slow; // pan right
- break;
- case KEY_DOWN:
- if (shift) { // move selection to next child
- if (!selNode) {
- selectNode(root);
- } else if (selNode.parent) {
- parch = selNode.parent.children;
- selectNode(parch[(parch.indexOf(selNode) + 1) % parch.length]);
- updateD3(selNode, false);
- }
- return;
- }
- moveY = PAN_INC * slow; // pan down
- break;
- case KEY_RETURN:
- if (!selNode) {
- selectNode(root);
- }
- if (shift) { // show loaded
- expandTree(selNode);
- loadSubItems(selNode);
- } else {
- toggle(selNode); // expand/collapse node
- }
- updateD3(selNode, true);
- return;
- case KEY_SPACE:
- if (!selNode) {
- selectNode(root);
- }
- if (shift) { // browse into
- browseInto(selNode);
- } else { // load +1 level
- expand1Level(selNode);
- updateD3(selNode, true);
- }
- return;
- case KEY_HOME: // reset transform
- curX = width / 2;
- curY = height / 2;
- curR = limitR(90 - root.x);
- curZ = 1;
- updateD3(root, true);
- return;
- case KEY_END: // zoom to selection
- if (!selNode) { return; }
- curX = width / 2 - selNode.y * curZ;
- curY = height / 2;
- curR = limitR(90 - selNode.x);
- updateD3(selNode, true);
- return;
- default: return; // ignore other keys
- }
- keysdown.push(key);
- // start animation if anything happening
- if (keysdown.length > 0 && animation === null) {
animation = requestAnimationFrame(frame);
}
-}
-function actionUp(key) {
- var pos = keysdown.indexOf(key);
- if (pos < 0) { return; }
-
- switch(key) {
- case KEY_PLUS: // - = zoom out
- case KEY_MINUS: // + = zoom in
- moveZ = 0;
- break;
- case KEY_PAGEUP: // page up = rotate CCW
- case KEY_PAGEDOWN: // page down = rotate CW
- moveR = 0;
- break;
- case KEY_LEFT: // left arrow
- case KEY_RIGHT: // right arrow
- moveX = 0;
- break;
- case KEY_UP: // up arrow
- case KEY_DOWN: // down arrow
- moveY = 0;
- break;
+ // enforce zoom extent
+ function limitZ(z) {
+ return Math.max(Math.min(z, MAX_ZOOM), MIN_ZOOM);
}
- keysdown.splice(pos, 1); // remove key
- if (keysdown.length > 0 || animation === null) { return; }
- cancelAnimationFrame(animation);
- animation = aniTime = null;
- networkElem.focus();
-}
+
+ // keep rotation between 0 and 360
+ function limitR(r) {
+ return (r + 360) % 360;
+ }
+
+ //
+ // d3 event handlers
+ //
+
+ function resize() { // window resize
+ if (networkElem.offsetWidth === 0) {
+ return;
+ }
+ var oldwidth = width;
+ var oldheight = height;
+ width = networkElem.offsetWidth;
+ height = networkElem.offsetHeight;
+ treeD3.size([360, Math.min(width, height) / 2 - 120]);
+ svgBase.attr('width', width).attr('height', height);
+ curX += (width - oldwidth) / 2;
+ curY += (height - oldheight) / 2;
+ svgGroup.attr('transform',
+ 'rotate(' + curR + ' ' + curX + ' ' + curY +
+ ')translate(' + curX + ' ' + curY +
+ ')scale(' + curZ + ')');
+ updateD3(root, false);
+ }
+
+ function click(d) { // Select node
+ if (d3.event.defaultPrevented || d === selNode) {
+ return;
+ }
+ selectNode(d);
+ updateD3(d, false);
+ d3.event.preventDefault();
+ }
+
+ function dblclick(d) { // Toggle children of node
+ if (d3.event.defaultPrevented) {
+ return;
+ } // click suppressed
+ d3.event.preventDefault();
+ if (d3.event.shiftKey) {
+ expand1Level(d);
+ } else {
+ toggle(d);
+ }
+ updateD3(d, true);
+ }
+
+ var startposX, startposY; // initial position on mouse button down for pan
+
+ function mousedown() { // pan action from mouse drag
+ if (d3.event.which !== 1) {
+ return;
+ } // ingore other mouse buttons
+ startposX = curX - d3.event.clientX;
+ startposY = curY - d3.event.clientY;
+ d3.select(document).on('mousemove', mousemove, true);
+ d3.select(document).on('mouseup', mouseup, true);
+ networkElem.focus();
+ d3.event.preventDefault();
+ }
+
+ function mousemove() { // drag
+ curX = startposX + d3.event.clientX;
+ curY = startposY + d3.event.clientY;
+ setview();
+ d3.event.preventDefault();
+ }
+
+ function mouseup() { // cleanup
+ d3.select(document).on('mousemove', null);
+ d3.select(document).on('mouseup', null);
+ }
+
+ function wheel() { // mousewheel (including left-right)
+ var dz, newZ;
+ var slow = (d3.event && d3.event.altKey) ? 0.25 : 1;
+ if (d3.event.wheelDeltaY !== 0) { // up-down = zoom
+ dz = Math.pow(1.2, d3.event.wheelDeltaY * 0.001 * slow);
+ newZ = limitZ(curZ * dz);
+ dz = newZ / curZ;
+ curZ = newZ;
+ // zoom around mouse position
+ curX -= (d3.event.clientX - curX) * (dz - 1);
+ curY -= (d3.event.clientY - curY) * (dz - 1);
+ setview();
+ }
+ if (d3.event.wheelDeltaX !== 0) { // left-right = rotate
+ curR = limitR(curR + d3.event.wheelDeltaX * 0.01 * slow);
+ updateD3(root, false);
+ }
+ }
+
+ function polydown(key, evt) { // polymer ev-mousedown event
+ actionDown(key, evt.shiftKey, evt.altKey);
+ }
+
+ function polyup(key, evt) { // polymer ev-mouseup event
+ actionUp(key);
+ }
+
+ function menu(key, shift, evt) { // context menu selection event
+ if (evt === undefined) { // shiftkey not supplied
+ evt = shift;
+ shift = evt.shiftKey;
+ }
+ actionDown(key, shift, evt.altKey);
+ networkElem.focus();
+ }
+
+ function keydown() { // d3 keydown event
+ var evt = d3.event;
+ if (evt.repeat) {
+ return;
+ }
+ actionDown(evt.which, evt.shiftKey, evt.altKey);
+ }
+
+ function keyup() { // d3 keyup event
+ var evt = d3.event;
+ actionUp(evt.which);
+ }
+
+ // right click, show context menu and select this node
+ function showContextMenu(d) {
+ d3.event.preventDefault();
+ d3.select('.ecnode').text(
+ (d.children ? 'Collapse ' : 'Expand ') + 'Node');
+ var cmenu = d3.select('.contextmenu');
+ cmenu.style({
+ left: Math.min(d3.event.offsetX + 3,
+ width - cmenu.style('width').replace('px', '') - 5) + 'px',
+ top: (d3.event.offsetY + 8) + 'px',
+ display: 'block'
+ });
+ var doc = d3.select(document);
+ doc.on('mousedown.cm', hideContextMenu, true);
+ setTimeout(function() {
+ doc.on('mouseup.cm', hideContextMenu, true);
+ }, 500);
+ selectNode(d);
+ }
+
+ function hideContextMenu() {
+ var doc = d3.select(document);
+ d3.select('.contextmenu').style('display', 'none');
+ doc.on('mouseup.cm', null);
+ doc.on('mousedown.cm', null);
+ networkElem.focus();
+ }
+
+
+ // Event actions
+ // Almost all UI actions pass through here,
+ // even if they are not originally generated from the keyboard
+ // There are two types of actions:
+ // * Press-and-Hold actions perform some action while they are pressed,
+ // until they are released, like pan, zoom, and rotate. These actions end
+ // with "break", so the key can be saved, and actionUp can stop the action.
+ // * Click actions mostly happen on keydown, like toggling children.
+ function actionDown(key, shift, alt) {
+ var parch; // parent's children
+ var slow = alt ? 0.25 : 1;
+ if (keysdown.indexOf(key) >= 0) {
+ return;
+ } // defeat auto repeat
+ switch (key) {
+ case KEY_PLUS: // zoom in
+ moveZ = ZOOM_INC * slow;
+ break;
+ case KEY_MINUS: // zoom out
+ moveZ = -ZOOM_INC * slow;
+ break;
+ case KEY_PAGEUP: // rotate counterclockwise
+ moveR = -ROT_INC * slow;
+ break;
+ case KEY_PAGEDOWN: // rotate clockwise
+ moveR = ROT_INC * slow;
+ break;
+ case KEY_LEFT:
+ if (shift) { // move selection to parent
+ if (!selNode) {
+ selectNode(root);
+ } else if (selNode.parent) {
+ selectNode(selNode.parent);
+ updateD3(selNode, false);
+ }
+ return;
+ }
+ moveX = -PAN_INC * slow; // pan left
+ break;
+ case KEY_UP:
+ if (shift) { // move selection to previous child
+ if (!selNode) {
+ selectNode(root);
+ } else if (selNode.parent) {
+ parch = selNode.parent.children;
+ selectNode(parch[(parch.indexOf(selNode) +
+ parch.length - 1) % parch.length]);
+ updateD3(selNode, false);
+ }
+ return;
+ }
+ moveY = -PAN_INC * slow; // pan up
+ break;
+ case KEY_RIGHT:
+ if (shift) { // move selection to first/last child
+ if (!selNode) {
+ selectNode(root);
+ } else {
+ if (selNode.children && selNode.children.length > 0) {
+ selectNode(selNode.children[0]);
+ updateD3(selNode, false);
+ }
+ }
+ return;
+ }
+ moveX = PAN_INC * slow; // pan right
+ break;
+ case KEY_DOWN:
+ if (shift) { // move selection to next child
+ if (!selNode) {
+ selectNode(root);
+ } else if (selNode.parent) {
+ parch = selNode.parent.children;
+ selectNode(parch[(parch.indexOf(selNode) + 1) % parch.length]);
+ updateD3(selNode, false);
+ }
+ return;
+ }
+ moveY = PAN_INC * slow; // pan down
+ break;
+ case KEY_RETURN:
+ if (!selNode) {
+ selectNode(root);
+ }
+ if (shift) { // show loaded
+ expandTree(selNode);
+ loadSubItems(selNode);
+ } else {
+ toggle(selNode); // expand/collapse node
+ }
+ updateD3(selNode, true);
+ return;
+ case KEY_SPACE:
+ if (!selNode) {
+ selectNode(root);
+ }
+ if (shift) { // browse into
+ browseInto(selNode);
+ } else { // load +1 level
+ expand1Level(selNode);
+ updateD3(selNode, true);
+ }
+ return;
+ case KEY_HOME: // reset transform
+ curX = width / 2;
+ curY = height / 2;
+ curR = limitR(90 - root.x);
+ curZ = 1;
+ updateD3(root, true);
+ return;
+ case KEY_END: // zoom to selection
+ if (!selNode) {
+ return;
+ }
+ curX = width / 2 - selNode.y * curZ;
+ curY = height / 2;
+ curR = limitR(90 - selNode.x);
+ updateD3(selNode, true);
+ return;
+ default:
+ return; // ignore other keys
+ }
+ keysdown.push(key);
+ // start animation if anything happening
+ if (keysdown.length > 0 && animation === null) {
+ animation = requestAnimationFrame(frame);
+ }
+ }
+
+ function actionUp(key) {
+ var pos = keysdown.indexOf(key);
+ if (pos < 0) {
+ return;
+ }
+
+ switch (key) {
+ case KEY_PLUS: // - = zoom out
+ case KEY_MINUS: // + = zoom in
+ moveZ = 0;
+ break;
+ case KEY_PAGEUP: // page up = rotate CCW
+ case KEY_PAGEDOWN: // page down = rotate CW
+ moveR = 0;
+ break;
+ case KEY_LEFT: // left arrow
+ case KEY_RIGHT: // right arrow
+ moveX = 0;
+ break;
+ case KEY_UP: // up arrow
+ case KEY_DOWN: // down arrow
+ moveY = 0;
+ break;
+ }
+ keysdown.splice(pos, 1); // remove key
+ if (keysdown.length > 0 || animation === null) {
+ return;
+ }
+ cancelAnimationFrame(animation);
+ animation = aniTime = null;
+ networkElem.focus();
+ }
+
+ return new D3Widget(browseState, browseEvents);
+}
\ No newline at end of file
diff --git a/src/lib/mercury/dialog-click-hook.js b/src/lib/mercury/dialog-click-hook.js
new file mode 100644
index 0000000..b8db190
--- /dev/null
+++ b/src/lib/mercury/dialog-click-hook.js
@@ -0,0 +1,19 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+ * Because of the way Mercury captures and delegates events, ev-click does not
+ * work for items inside a dialog because polymer moves the dialog's DOM around
+ * This hook is a work-around for that issue.
+ */
+module.exports = function(handler) {
+ return Object.create({
+ hook: function(elem) {
+ if (!elem.clickHandlerInstalled) {
+ elem.addEventListener('click', handler);
+ elem.clickHandlerInstalled = true;
+ }
+ }
+ });
+};
\ No newline at end of file
diff --git a/src/services/namespace/service.js b/src/services/namespace/service.js
index 4d442f1..2e58542 100644
--- a/src/services/namespace/service.js
+++ b/src/services/namespace/service.js
@@ -27,7 +27,9 @@
search: search,
util: naming,
initVanadium: getRuntime,
- clearCache: clearCache
+ clearCache: clearCache,
+ deleteMountPoint: deleteMountPoint,
+ prefixes: prefixes
};
//TODO(aghassemi) What's a good timeout? It should be shorter than this.
@@ -225,6 +227,19 @@
}
/*
+ * Deletes a mount point.
+ * @param {string} name mountpoint name to delete.
+ * @return {Promise<void>} Success or failure promise.
+ */
+function deleteMountPoint(name) {
+ return getRuntime().then(function(rt) {
+ var ctx = rt.getContext().withTimeout(RPC_TIMEOUT);
+ var ns = rt.namespace();
+ return ns.delete(ctx, name, true);
+ });
+}
+
+/*
* Given a name, provide information about its mounttable objectAddress.
* @param {string} objectName Object name to get mounttable objectAddress for.
* @return {Promise.<mercury.array<string>>} Promise of an array of
@@ -424,13 +439,17 @@
function clearByPrefix(cache, parent) {
var keys = cache.keys();
keys.forEach(function(key) {
- var isMatch =
- (key === parent) ||
- (key.lastIndexOf(naming.clean(parent) + '/') === 0);
-
- if (isMatch) {
+ if (prefixes(parent, key)) {
cache.del(key);
}
});
}
+}
+
+/*
+ * Returns true iff parentName is a parent of childName or is same as childName
+ */
+function prefixes(parentName, childName) {
+ return (parentName === childName) ||
+ (childName.indexOf(naming.clean(parentName) + '/') === 0);
}
\ No newline at end of file
diff --git a/web-component-dependencies.html b/web-component-dependencies.html
index 2fd7819..2597464 100644
--- a/web-component-dependencies.html
+++ b/web-component-dependencies.html
@@ -9,6 +9,8 @@
and the browserify transforms handles the bundling of wc dependencies at runtime.
-->
<link rel="import" href="bower_components/core-drawer-panel/core-drawer-panel.html">
+<link rel="import" href="bower_components/paper-dialog/paper-dialog.html">
+<link rel="import" href="bower_components/paper-dialog/paper-action-dialog.html">
<link rel="import" href="bower_components/core-header-panel/core-header-panel.html">
<link rel="import" href="bower_components/core-icons/av-icons.html">
<link rel="import" href="bower_components/core-icons/core-icons.html">