veyron-browser: Initial scaffold of the veyron-browse app using
Mercury.
This initial setup includes:
-Sidebar with navigation links to Browse and Help pages
-Browse page simply has two inputs and displays the value of
those inputs as they change. (ie. No actual functionality )
-Hash-based routing between the top level navigation
pages with History support.
-Build system
Change-Id: I9f7ee3b6dc13bc7685f5b14a0891353e9eacdaf1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a230a4e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+bower_components/
+public/bundle.*
+public/platform.js
+npm-debug.log
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..e33a040
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,27 @@
+{
+ "browser": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "forin": true,
+ "freeze": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": "nofunc",
+ "newcap": true,
+ "noarg": true,
+ "nonbsp": true,
+ "nonew": true,
+ "quotmark": "single",
+ "undef": true,
+ "unused": "vars",
+ "trailing": true,
+ "maxlen": 80,
+ "devel": true,
+ "expr": true,
+ "sub": true,
+ "node": true,
+ "globals": {
+ "Promise": true
+ }
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..82d5493
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,65 @@
+PATH:=$(VEYRON_ROOT)/environment/cout/node/bin:$(PATH)
+PATH:=node_modules/.bin:$(PATH)
+
+# All JS files except build.js and third party
+JS_FILES = $(shell find browser -name "*.js")
+
+# Builds everything
+all: public/bundle.js public/bundle.html public/platform.js
+
+# Creating the bundle JS file
+public/bundle.js: $(JS_FILES) node_modules
+ jshint browser # lint all browser JavaScript files
+ browserify browser/app.js -o public/bundle.js
+
+# Creating the bundle HTML file
+public/bundle.html: web-component-dependencies.html node_modules bower_components
+ vulcanize --output public/bundle.html web-component-dependencies.html --inline
+
+# Copies the web components platform file
+public/platform.js: bower_components
+ cp bower_components/platform/platform.js public/platform.js
+
+# Install what we need from NPM
+node_modules: package.json
+ npm prune
+ npm install
+ touch node_modules
+
+# Install non-JS dependencies from bower
+bower_components: bower.json node_modules
+ bower prune
+ bower install
+ touch bower_components
+
+# PHONY targets:
+
+# Uses prova to run tests in a headless chrome and then quit after all test finish
+test:
+ jshint test # lint all test JavaScript files
+ prova test/**/*.js --browser --launch chrome --headless --progress --quit
+
+# Continuously watch for changes to .js, .html or .css files.
+# Rebundles the appropriate bundles when local files change
+watch:
+ watch -n 1 make
+
+# Continuously reruns the tests as they change
+watch-test:
+ @echo "Tests being watched at: http://0.0.0.0:7559"
+ prova test/**/*.js --browser --launch chrome
+
+# Serves the needed daemons and starts a server at http://localhost:$(HTTP_PORT)
+# CTRL-C to stop
+start:
+ ./services.sh
+
+# Clean all build artifacts
+clean:
+ rm -f public/bundle.js
+ rm -f public/bundle.html
+ rm -f public/platform.js
+ rm -rf node_modules
+ rm -rf bower_components
+
+.PHONY: start clean watch test watch-test
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e5039a3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# Mounttable Browser
+Mountable browser is a browser application that lets the user view and traverse mounttables.
+
+## Building
+Before you can run Mounttable Browser, you need to build. Simply run:
+
+```sh
+make
+```
+
+## Running
+
+```sh
+make start
+```
+and navigate to http://localhost:9000
+
+to stop simply CTRL-C the console running the make start
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..df95fef
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,12 @@
+{
+ "name": "veyron-browser",
+ "dependencies": {
+ "core-drawer-panel": "Polymer/core-drawer-panel#~0.3.4",
+ "core-header-panel": "Polymer/core-header-panel#~0.3.4",
+ "core-item": "Polymer/core-item#~0.3.4",
+ "core-icons": "Polymer/core-icons#~0.3.4",
+ "core-menu": "Polymer/core-menu#~0.3.4",
+ "core-toolbar": "Polymer/core-toolbar#~0.3.4",
+ "paper-button": "Polymer/paper-button#~0.3.4"
+ }
+}
diff --git a/browser/app.js b/browser/app.js
new file mode 100644
index 0000000..b136bba
--- /dev/null
+++ b/browser/app.js
@@ -0,0 +1,65 @@
+var mercury = require('mercury');
+var onDocumentReady = require('./lib/document-ready');
+var Viewport = require('./components/viewport/index');
+var router = require('./router');
+var browse = require('./components/browse/index');
+
+onDocumentReady(function startApp() {
+
+ var browseComponent = browse();
+
+ // Top level state
+ var state = mercury.struct({
+ /*
+ * Navigation related states
+ */
+ navigation: mercury.struct({
+ /*
+ * Identifier for the currently displayed page.
+ * Mutable via route handlers
+ * @type {string}
+ */
+ pageKey: mercury.value(null)
+ }),
+ /*
+ * Veyron Namespace Browsing related states
+ */
+ browse: browseComponent.state
+ });
+
+ // To level events
+ var events = mercury.input([
+ /*
+ * Navigation related events
+ */
+ 'navigation',
+
+ /*
+ * Veyron Namespace Browsing related events
+ * Source: components/browse/events
+ */
+ 'browse'
+ ]);
+ events.navigation = mercury.input([
+ /*
+ * Indicates a navigation request to a resource
+ * Data of form:
+ * {
+ * path: 'path/to/resource'
+ * }
+ * is expected as data for the event
+ */
+ 'navigate'
+ ]);
+ events.browse = browseComponent.events;
+
+ // Start the router which will register the application routes
+ router(state, events);
+
+ // Render the app
+ var render = function(state) {
+ return Viewport.render(state, events);
+ };
+ mercury.app(document.body, state, render);
+
+});
\ No newline at end of file
diff --git a/browser/components/browse/index.js b/browser/components/browse/index.js
new file mode 100644
index 0000000..64c5136
--- /dev/null
+++ b/browser/components/browse/index.js
@@ -0,0 +1,72 @@
+var mercury = require('mercury');
+var browseNamespace = require('./update/browseNamespace');
+var h = mercury.h;
+
+module.exports = create;
+module.exports.render = render;
+
+/*
+ * Browse component provides user interfaces for browsing the Veyron namespace
+ */
+function create() {
+
+ var state = mercury.struct({
+ /*
+ * Current Veyron namespace being displayed and queried
+ * @type {string}
+ */
+ namespace: mercury.value(null),
+
+ /*
+ * Current Glob query applied to the Veyron namespace
+ * @type {string}
+ */
+ globQuery: mercury.value(null)
+ });
+
+ var events = mercury.input([
+ /*
+ * Indicates a request to browse the Veyron namespace
+ * Data of form:
+ * {
+ * namespace: '/veyron/name/space',
+ * globQuery: '*',
+ * }
+ * is expected as data for the event
+ */
+ 'browseNamespace'
+ ]);
+
+ wireUpEvents(state, events);
+
+ return {
+ state: state,
+ events: events
+ };
+}
+
+function render(browseState, browseEvents) {
+
+ // Trigger browseNamespace event when value of the inputs change
+ var changeEvent = mercury.valueEvent(browseEvents.browseNamespace);
+
+ return [
+ h('input', {
+ 'name': 'namespace',
+ 'value': browseState.namespace,
+ 'ev-change': changeEvent
+ }),
+ h('input', {
+ 'name': 'globQuery',
+ 'value': browseState.globQuery,
+ 'ev-change': changeEvent
+ }),
+ h('div', ['Current Namespace:', browseState.namespace]),
+ h('div', ['Current GlobQuery:', browseState.globQuery])
+ ];
+}
+
+// Wire up events that we know how to handle
+function wireUpEvents(state, events) {
+ events.browseNamespace(browseNamespace.bind(null, state));
+}
\ No newline at end of file
diff --git a/browser/components/browse/update/browseNamespace.js b/browser/components/browse/update/browseNamespace.js
new file mode 100644
index 0000000..e8bdfd7
--- /dev/null
+++ b/browser/components/browse/update/browseNamespace.js
@@ -0,0 +1,22 @@
+var exists = require('../../../lib/exists');
+
+module.exports = browseNamespace;
+
+/*
+ * Default event handler for the browseNamespace event.
+ * Updates the necessary states when browseNamespace is triggered
+ * Data is of the form
+ * {
+ * namespace: '/veyron/name/space',
+ * globQuery: '*',
+ * }
+ */
+function browseNamespace(browseState, data) {
+ if (exists(data.namespace) && data.namespace !== '') {
+ browseState.namespace.set(data.namespace);
+ }
+
+ if (exists(data.globQuery) && data.globQuery !== '') {
+ browseState.globQuery.set(data.globQuery);
+ }
+}
\ No newline at end of file
diff --git a/browser/components/help/index.js b/browser/components/help/index.js
new file mode 100644
index 0000000..d8f105a
--- /dev/null
+++ b/browser/components/help/index.js
@@ -0,0 +1,14 @@
+var mercury = require('mercury');
+var h = mercury.h;
+
+module.exports = create;
+module.exports.render = render;
+
+/*
+ * Help view
+ */
+function create() {}
+
+function render() {
+ return h('span', 'HELP! TODO(aghassemi)');
+}
\ No newline at end of file
diff --git a/browser/components/main-content/index.js b/browser/components/main-content/index.js
new file mode 100644
index 0000000..91de473
--- /dev/null
+++ b/browser/components/main-content/index.js
@@ -0,0 +1,32 @@
+var mercury = require('mercury');
+var Browse = require('../browse/index');
+var Help = require('../help/index');
+var h = mercury.h;
+
+module.exports = create;
+module.exports.render = render;
+
+/*
+ * MainContent part of the layout
+ */
+function create() {}
+
+function render(state, events) {
+ return [
+ h('h1', state.navigation.pageKey),
+ h('div', renderContent(state, events))
+ ];
+}
+
+function renderContent(state, events) {
+ var pageKey = state.navigation.pageKey;
+ switch (pageKey) {
+ case 'browse':
+ return Browse.render(state.browse, events.browse);
+ case 'help':
+ return Help.render();
+ default:
+ // We shouldn't get here with proper route handlers, so it's an error(bug)
+ throw new Error('Could not find page ' + pageKey);
+ }
+}
\ No newline at end of file
diff --git a/browser/components/sidebar/index.js b/browser/components/sidebar/index.js
new file mode 100644
index 0000000..1792ef8
--- /dev/null
+++ b/browser/components/sidebar/index.js
@@ -0,0 +1,53 @@
+var mercury = require('mercury');
+var h = mercury.h;
+
+module.exports = create;
+module.exports.render = render;
+
+/*
+ * Sidebar part of the layout
+ */
+function create() {}
+
+function render(navigationState, navigationEvents) {
+ return [
+ h('core-toolbar', [
+ h('h1', 'Veyron Browser')
+ ]),
+ h('core-menu', {
+ 'selected': navigationState.pageKey,
+ 'valueattr': 'itemKey'
+ },
+ renderNavigationItems(navigationEvents)
+ )
+ ];
+}
+
+var navigationItems = [{
+ key: 'browse',
+ label: 'Browse',
+ icon: 'search'
+}, {
+ key: 'help',
+ label: 'Help',
+ icon: 'help'
+}];
+
+function renderNavigationItems(navigationEvents) {
+ return navigationItems.map(function createMenuItem(navItem) {
+ return h('core-item', {
+ 'itemKey': navItem.key,
+ 'icon': navItem.icon,
+ 'label': navItem.label
+ }, [
+ h('a', {
+ 'href': '#/' + navItem.key,
+ 'ev-click': mercury.event(
+ navigationEvents.navigate, {
+ path: '/' + navItem.key
+ }
+ )
+ })
+ ]);
+ });
+}
\ No newline at end of file
diff --git a/browser/components/viewport/index.js b/browser/components/viewport/index.js
new file mode 100644
index 0000000..f223eca
--- /dev/null
+++ b/browser/components/viewport/index.js
@@ -0,0 +1,28 @@
+var mercury = require('mercury');
+var AttributeHook = require('../../lib/mercury/attribute-hook');
+var Sidebar = require('../sidebar/index');
+var MainContent = require('../main-content/index');
+var h = mercury.h;
+
+module.exports = create;
+module.exports.render = render;
+
+/*
+ * Page level layout of the application
+ */
+function create() {}
+
+function render(state, events) {
+ return h('core-drawer-panel', [
+ h('core-header-panel', {
+ 'drawer': new AttributeHook(true)
+ },
+ Sidebar.render(state.navigation, events.navigation)
+ ),
+ h('core-header-panel', {
+ 'main': new AttributeHook(true)
+ },
+ MainContent.render(state, events)
+ )
+ ]);
+}
\ No newline at end of file
diff --git a/browser/lib/document-ready.js b/browser/lib/document-ready.js
new file mode 100644
index 0000000..4974140
--- /dev/null
+++ b/browser/lib/document-ready.js
@@ -0,0 +1,8 @@
+module.exports = onDocumentReady;
+
+function onDocumentReady(cb) {
+
+ // Since we are using third-party polymer to Polyfill for web components.
+ // We wait for polymer-ready before triggering document ready.
+ document.addEventListener('polymer-ready', cb);
+}
\ No newline at end of file
diff --git a/browser/lib/exists.js b/browser/lib/exists.js
new file mode 100644
index 0000000..44581d9
--- /dev/null
+++ b/browser/lib/exists.js
@@ -0,0 +1,22 @@
+/*
+ * Given a collection of objects, returns true if all of them exist
+ * Returns false as soon as one does not exist.
+ * @param {*} [...] objects Objects to check existence of
+ * @return {bool} Whether all of the given objects exist or not
+ */
+
+module.exports = exists;
+
+function exists(objects) {
+ if (!Array.isArray(objects)) {
+ objects = [objects];
+ }
+ for (var i = 0; i < objects.length; i++) {
+ var obj = objects[i];
+ if (typeof obj === 'undefined' || obj === null) {
+ return false;
+ }
+ }
+
+ return true;
+}
\ No newline at end of file
diff --git a/browser/lib/isMounttable.js b/browser/lib/isMounttable.js
new file mode 100644
index 0000000..dfcf7ba
--- /dev/null
+++ b/browser/lib/isMounttable.js
@@ -0,0 +1,31 @@
+var resolveRace = require('promises');
+
+/**
+ * isMounttable determines if a specific address refers to a
+ * mounttable.
+ * @param {object} client the veyron client to use.
+ * @param {string} globResult result of glob to check.
+ * @return {promise} promise to a boolean indicating if it is
+ * a mounttable.
+ */
+ // TODO(bprosnitz) Remove dependency on _proxyConnection.
+ // TODO(bprosnitz) Consider adding interface name to signature and using that.
+module.exports = function(client, globResult) {
+ if (globResult.servers.length === 0) {
+ // This is on the same mounttable as the globResult.
+ return Promise.resolve(true);
+ }
+
+ var globbable = function(sig) {
+ return sig['glob'] !== undefined && sig['glob'].inArgs.length === 1;
+ };
+
+ var pconn = client._proxyConnection;
+ var promises = [];
+ for (var i = 0; i < globResult.servers.length; i++) {
+ var server = globResult.servers[i].server;
+ promises.push(pconn.getServiceSignature(server).then(globbable));
+ }
+
+ return resolveRace(promises);
+};
\ No newline at end of file
diff --git a/browser/lib/mercury/attribute-hook.js b/browser/lib/mercury/attribute-hook.js
new file mode 100644
index 0000000..885a425
--- /dev/null
+++ b/browser/lib/mercury/attribute-hook.js
@@ -0,0 +1,15 @@
+/*
+ * Allows attributes to be set using setAttribute:
+ * https://github.com/Raynos/mercury/blob/
+ * master/docs/faq.md#how-do-i-update-custom-properties
+ */
+
+module.exports = AttributeHook;
+
+function AttributeHook(value) {
+ this.value = value;
+}
+
+AttributeHook.prototype.hook = function(elem, prop) {
+ elem.setAttribute(prop, this.value);
+};
\ No newline at end of file
diff --git a/browser/lib/mountpoint.js b/browser/lib/mountpoint.js
new file mode 100644
index 0000000..1d4cbe1
--- /dev/null
+++ b/browser/lib/mountpoint.js
@@ -0,0 +1,91 @@
+//TODO(aghassemi) move to core Veyron API?
+
+module.exports = MountPoint;
+
+/**
+ * MountPoint handles manipulating and querying from
+ * a mounttable.
+ * @param {object} client A veyron client.
+ * @param {object} mountTable A veyron MountTable instance.
+ * @param {...string} addressParts Parts of the address to join
+ * @constructor
+ */
+function MountPoint(client, mountTable, addressParts) {
+ this.client = client;
+ this.mountTable = mountTable;
+ this.name = Array.prototype.slice.call(arguments, 2).join('/');
+ this._terminalNames = null;
+}
+
+/**
+ * A helper method that returns the terminal names for this
+ * MountPoint and memoizes them.
+ * @return {Promise} A promise that resolves to a list of terminal names.
+ */
+MountPoint.prototype._getTerminalNames = function() {
+ // We resolve to a terminal name manually because veyron rpc calls
+ // wont usually resolve a name if it's to a mounttable. We
+ // would like to interact with all kinds of servers.
+ if (!this._terminalNames) {
+ this._terminalNames = this.mountTable.resolveMaximally(this.name);
+ }
+ return this._terminalNames;
+};
+
+/**
+ * appendToPath appends to the mountpoint path
+ * @param {...string} toAdd strings to add to the path.
+ * @return {MountPoint} a new mount point with the path args appended
+ * to the current path.
+ */
+MountPoint.prototype.appendToPath = function(toAdd) {
+ var args = Array.prototype.slice.call(arguments);
+ if (this.name.length > 0) {
+ args.unshift(this.name);
+ }
+ return new MountPoint(this.client, this.mountTable, args.join('/'));
+};
+
+/**
+ * mount mounts a target to the current mount point.
+ * @param {string} target The target to be mounted.
+ * @return {promise} a promise that completes when it is mounted
+ */
+MountPoint.prototype.mount = function(target) {
+ var client = this.client;
+ return this._getTerminalNames().then(function(terminalNames) {
+ // TODO(mattr): We should try all the names instead of just the first.
+ // Perhpas the library should allow me to pass a list of names.
+ return client.bindTo(terminalNames[0]).then(function(mtService) {
+ return mtService.mount(target, 0);
+ });
+ });
+};
+
+/**
+ * glob makes a glob request to a server relative to the current mountpoint.
+ * @param {string} expr The glob expression e.g. A/B/*.
+ * @return {promise} a promise to a list of results
+ */
+MountPoint.prototype.glob = function(expr) {
+ var results = [];
+ var client = this.client;
+ return this._getTerminalNames().then(function(terminalNames) {
+ // TODO(mattr): We should try all the names instead of just the first.
+ // Perhpas the library should allow me to pass a list of names.
+ return client.bindTo(terminalNames[0]).then(function(mtService) {
+ var promise = mtService.glob(expr);
+ var stream = promise.stream;
+
+ stream.on('data', function(val) {
+ if (val) {
+ results.push(val);
+ }
+ });
+
+ return promise.then(function() {
+ return results;
+ });
+ });
+ });
+};
\ No newline at end of file
diff --git a/browser/lib/promises.js b/browser/lib/promises.js
new file mode 100644
index 0000000..fb21ecd
--- /dev/null
+++ b/browser/lib/promises.js
@@ -0,0 +1,30 @@
+//TODO(aghassemi) move to core Veyron API. Rename to Promise.Any
+
+exports.resolveRace = resolveRace;
+
+/**
+ * resolveRace returns a promise that resolves when the first promise
+ * resolves and rejects only after every promise has rejected.
+ * @param {promise[]} promises a list of promises.
+ * @return {promse} a promise that resolves when any of the inputs resolve, or
+ * when all of the inputs reject.
+ */
+function resolveRace(promises) {
+ var resolve, reject;
+ var promise = new Promise(function(pResolve, pReject) {
+ resolve = pResolve;
+ reject = pReject;
+ });
+ var numRejects = 0;
+ var onReject = function(reason) {
+ numRejects++;
+ if (numRejects === promises.length) {
+ reject(reason);
+ }
+ };
+
+ for (var i = 0; i < promises.length; i++) {
+ promises[i].then(resolve, onReject);
+ }
+ return promise;
+}
\ No newline at end of file
diff --git a/browser/router.js b/browser/router.js
new file mode 100644
index 0000000..36fe972
--- /dev/null
+++ b/browser/router.js
@@ -0,0 +1,63 @@
+var Routes = require('routes');
+var registerRoutes = require('./routes/register-routes');
+
+module.exports = router;
+
+/*
+ * Implements a hash(#) router.
+ *
+ * Using # instead of regular routes to eliminate any configuration dependency
+ * on the server. Otherwise servers need to be configured to return index
+ * instead of 404. Although that's reasonable, for simplicity of deployment,
+ * hash-based routing is picked.
+ */
+function router(state, events) {
+
+ // Match the path to a route and trigger the route handler for it
+ var handleRouteChange = function(data) {
+ var path = normalizePath(data.path);
+ var route = routes.match(path);
+ if (!route) {
+ //TOOD(aghassemi) redirect to 404 error view?
+ return;
+ }
+ if (!data.skipHistoryPush) {
+ window.history.pushState(null, null, '#' + path);
+ }
+ route.fn.call(null, state, events, route.params);
+ };
+
+ // Triggers a navigate event using the current location as the path
+ var navigateToCurrentLocation = function() {
+ events.navigation.navigate({
+ path: window.location.hash,
+ skipHistoryPush: true
+ });
+ };
+
+ // Create and register routes
+ var routes = new Routes();
+ registerRoutes(routes);
+
+ // Route and push to history when navigation event fires
+ events.navigation.navigate(handleRouteChange);
+
+ // Fire navigation event when hash changes
+ window.addEventListener('hashchange', navigateToCurrentLocation);
+
+ // Kick off the routing by navigating to current Url
+ navigateToCurrentLocation();
+}
+
+function normalizePath(path) {
+ // Remove #
+ if (path.indexOf('#') === 0) {
+ path = path.substr(1);
+ }
+
+ // Empty means root
+ if (path === '') {
+ path = '/';
+ }
+ return path;
+}
\ No newline at end of file
diff --git a/browser/routes/browse.js b/browser/routes/browse.js
new file mode 100644
index 0000000..063afbc
--- /dev/null
+++ b/browser/routes/browse.js
@@ -0,0 +1,15 @@
+module.exports = function(routes) {
+ routes.addRoute('/browse/:namespace?/:globquery?', handleBrowseRoute);
+};
+
+function handleBrowseRoute(state, events, params) {
+
+ // Set the page to browse
+ state.navigation.pageKey.set('browse');
+
+ // Trigger browse components browseNamespace event
+ events.browse.browseNamespace({
+ 'namespace': params.namespace,
+ 'globQuery': params.globquery
+ });
+}
\ No newline at end of file
diff --git a/browser/routes/help.js b/browser/routes/help.js
new file mode 100644
index 0000000..714092f
--- /dev/null
+++ b/browser/routes/help.js
@@ -0,0 +1,9 @@
+module.exports = function(routes) {
+ routes.addRoute('/help', handleHelpRoute);
+};
+
+function handleHelpRoute(state) {
+
+ // Set the page to help
+ state.navigation.pageKey.set('help');
+}
\ No newline at end of file
diff --git a/browser/routes/index.js b/browser/routes/index.js
new file mode 100644
index 0000000..66f412f
--- /dev/null
+++ b/browser/routes/index.js
@@ -0,0 +1,11 @@
+module.exports = function(routes) {
+ routes.addRoute('/', handleIndexRoute);
+};
+
+function handleIndexRoute(state, events) {
+
+ // Redirect to browse
+ events.navigation.navigate({
+ path: '/browse'
+ });
+}
\ No newline at end of file
diff --git a/browser/routes/register-routes.js b/browser/routes/register-routes.js
new file mode 100644
index 0000000..d0fa475
--- /dev/null
+++ b/browser/routes/register-routes.js
@@ -0,0 +1,10 @@
+module.exports = registerRoutes;
+
+/*
+ * Registers all route handlers.
+ */
+function registerRoutes(routes) {
+ require('./index')(routes);
+ require('./help')(routes);
+ require('./browse')(routes);
+}
\ No newline at end of file
diff --git a/browser/veyron-config.js b/browser/veyron-config.js
new file mode 100644
index 0000000..3719e02
--- /dev/null
+++ b/browser/veyron-config.js
@@ -0,0 +1,8 @@
+var logLevels = require('veyron').logLevels;
+var veyronConfig = {
+ 'logLevel': logLevels.INFO,
+ 'identityServer': 'http://localhost:5163/random/',
+ 'proxy': 'http://localhost:5165'
+};
+
+module.exports = veyronConfig;
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..465f9c5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "veyron-browser",
+ "description": "Veyron Browser is a browser for the Veyron namespace.",
+ "version": "0.0.1",
+ "private": true,
+ "scripts": {
+ "preinstall": "npm install --production $VEYRON_ROOT/veyron/javascript/api"
+ },
+ "devDependencies": {
+ "browserify": "^4.2.3",
+ "jshint": "^2.5.2",
+ "mercury": "^6.0.1",
+ "prova": "^1.14.0",
+ "routes": "^1.2.0",
+ "serve": "^1.4.0",
+ "veyron": "0.0.1",
+ "vulcanize": "^0.3.1"
+ }
+}
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..a7620f8
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
+ <meta name="mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="description" content="">
+ <title>Veyron Browser</title>
+ <style>
+ div[type=foo] {
+ color: red;
+ }
+ </style>
+ <script src="platform.js"></script>
+ <link rel="import" href="bundle.html">
+<head>
+<body fullbleed>
+ <script src="bundle.js"></script>
+</body>
\ No newline at end of file
diff --git a/services.sh b/services.sh
new file mode 100755
index 0000000..41500d2
--- /dev/null
+++ b/services.sh
@@ -0,0 +1,29 @@
+export PATH=$VEYRON_ROOT/veyron/go/bin:$PATH
+export PATH=node_modules/.bin:$PATH
+
+HTTP_PORT=9000
+VEYRON_MOUNTTABLE_PORT=5167
+VEYRON_MOUNTTABLE_PORT2=5168
+VEYRON_PROXY_PORT=5164
+VEYRON_PROXY_ADDR=127.0.0.1:$VEYRON_PROXY_PORT
+VEYRON_WSPR_PORT=5165
+VEYRON_IDENTITY_PORT=5163
+VEYRON_STORE_PORT=5166
+VEYRON_IDENTITY_PATH=/tmp/veyron_browser_identity
+
+trap 'kill -TERM 0' SIGINT SIGTERM EXIT
+
+identity generate veyron_browser_identity > "${VEYRON_IDENTITY_PATH}"
+
+export VEYRON_IDENTITY=$VEYRON_IDENTITY_PATH; \
+identityd --httpaddr=:$VEYRON_IDENTITY_PORT & \
+mounttabled --address=:$VEYRON_MOUNTTABLE_PORT & \
+export NAMESPACE_ROOT=/localhost:$VEYRON_MOUNTTABLE_PORT ; \
+proxyd -address=$VEYRON_PROXY_ADDR & \
+wsprd --v=3 -logtostderr=true -vproxy=$VEYRON_PROXY_ADDR --port $VEYRON_WSPR_PORT & \
+mounttabled --address=:$VEYRON_MOUNTTABLE_PORT2 --name=global & \
+sleep 1 ; \
+stored --address=:$VEYRON_STORE_PORT --name=global/$USER/store &
+serve public/. --port $HTTP_PORT --compress &
+
+wait
\ No newline at end of file
diff --git a/web-component-dependencies.html b/web-component-dependencies.html
new file mode 100644
index 0000000..494a6e5
--- /dev/null
+++ b/web-component-dependencies.html
@@ -0,0 +1,17 @@
+<!--
+TODO(aghassemi) this is not good, we are declaring a dependency on something
+outside of where we introduce that dependency.
+
+Write a browserify transform so we can know from JS code what web components
+are needed to be bundled up.
+So in js, when a component needs to use say paper-button, they just require it:
+require('wc!paper-button');
+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/core-header-panel/core-header-panel.html">
+<link rel="import" href="bower_components/core-icons/core-icons.html">
+<link rel="import" href="bower_components/core-item/core-item.html">
+<link rel="import" href="bower_components/core-menu/core-menu.html">
+<link rel="import" href="bower_components/core-toolbar/core-toolbar.html">
+<link rel="import" href="bower_components/paper-button/paper-button.html">
\ No newline at end of file