diff --git a/dashboard-ui/bower_components/emby-webcomponents/.bower.json b/dashboard-ui/bower_components/emby-webcomponents/.bower.json
index a5e409c1d0..baeb7e95b3 100644
--- a/dashboard-ui/bower_components/emby-webcomponents/.bower.json
+++ b/dashboard-ui/bower_components/emby-webcomponents/.bower.json
@@ -16,12 +16,12 @@
},
"devDependencies": {},
"ignore": [],
- "version": "1.1.48",
- "_release": "1.1.48",
+ "version": "1.1.49",
+ "_release": "1.1.49",
"_resolution": {
"type": "version",
- "tag": "1.1.48",
- "commit": "1301ee3681a29577ba045c0ce20fd0e914cee168"
+ "tag": "1.1.49",
+ "commit": "6fe9a4ae4aa727695fbd17a40486065ce46c0892"
},
"_source": "git://github.com/MediaBrowser/emby-webcomponents.git",
"_target": "~1.1.5",
diff --git a/dashboard-ui/bower_components/emby-webcomponents/router.js b/dashboard-ui/bower_components/emby-webcomponents/router.js
new file mode 100644
index 0000000000..aa87e64344
--- /dev/null
+++ b/dashboard-ui/bower_components/emby-webcomponents/router.js
@@ -0,0 +1,527 @@
+define(['loading', 'viewManager', 'skinManager', 'pluginManager', 'backdrop', 'browser'], function (loading, viewManager, skinManager, pluginManager, backdrop, browser) {
+
+ var connectionManager;
+
+ function isStartup(ctx) {
+ var path = ctx.pathname;
+
+ if (path.indexOf('welcome') != -1) {
+ return true;
+ }
+
+ if (path.indexOf('connectlogin') != -1) {
+ return true;
+ }
+
+ if (path.indexOf('login') != -1) {
+ return true;
+ }
+
+ if (path.indexOf('manuallogin') != -1) {
+ return true;
+ }
+
+ if (path.indexOf('manualserver') != -1) {
+ return true;
+ }
+
+ if (path.indexOf('selectserver') != -1) {
+ return true;
+ }
+
+ if (path.indexOf('localpin') != -1) {
+ return true;
+ }
+
+ return false;
+ }
+
+ function allowAnonymous(ctx) {
+
+ return isStartup(ctx);
+ }
+
+ function redirectToLogin() {
+
+ backdrop.clear();
+
+ loading.show();
+
+ connectionManager.connect().then(function (result) {
+ handleConnectionResult(result, loading);
+ });
+ }
+
+ function handleConnectionResult(result, loading) {
+
+ switch (result.State) {
+
+ case MediaBrowser.ConnectionState.SignedIn:
+ {
+ loading.hide();
+ skinManager.loadUserSkin();
+ }
+ break;
+ case MediaBrowser.ConnectionState.ServerSignIn:
+ {
+ result.ApiClient.getPublicUsers().then(function (users) {
+
+ if (users.length) {
+ show('/startup/login.html?serverid=' + result.Servers[0].Id);
+ } else {
+ goToLocalLogin(result.ApiClient, result.Servers[0].Id);
+ }
+ });
+ }
+ break;
+ case MediaBrowser.ConnectionState.ServerSelection:
+ {
+ show('/startup/selectserver.html');
+ }
+ break;
+ case MediaBrowser.ConnectionState.ConnectSignIn:
+ {
+ show('/startup/welcome.html');
+ }
+ break;
+ case MediaBrowser.ConnectionState.ServerUpdateNeeded:
+ {
+ require(['alert'], function (alert) {
+ alert(Globalize.translate('core#ServerUpdateNeeded', 'https://emby.media')).then(function () {
+ show('/startup/selectserver.html');
+ });
+ });
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ function goToLocalLogin(apiClient, serverId) {
+
+ show('/startup/manuallogin.html?serverid=' + serverId);
+ }
+
+ var cacheParam = new Date().getTime();
+ function loadContentUrl(ctx, next, route, request) {
+
+ var url = route.contentPath || route.path;
+
+ if (url.toLowerCase().indexOf('http') != 0 && url.indexOf('file:') != 0) {
+ url = baseUrl() + '/' + url;
+ }
+
+ url += url.indexOf('?') == -1 ? '?' : '&';
+ url += 'v=' + cacheParam;
+
+ var xhr = new XMLHttpRequest();
+ xhr.onload = xhr.onerror = function () {
+ if (this.status < 400) {
+ loadContent(ctx, route, this.response, request);
+ } else {
+ next();
+ }
+ };
+ xhr.onerror = next;
+ xhr.open('GET', url, true);
+ xhr.send();
+ }
+
+ function handleRoute(ctx, next, route) {
+
+ authenticate(ctx, route, function () {
+ initRoute(ctx, next, route);
+ });
+ }
+
+ function initRoute(ctx, next, route) {
+
+ var onInitComplete = function (controllerFactory) {
+ sendRouteToViewManager(ctx, next, route, controllerFactory);
+ };
+
+ require(route.dependencies || [], function () {
+
+ if (route.controller) {
+ require([route.controller], onInitComplete);
+ } else {
+ onInitComplete();
+ }
+ });
+ }
+
+ function cancelCurrentLoadRequest() {
+ var currentRequest = currentViewLoadRequest;
+ if (currentRequest) {
+ currentRequest.cancel = true;
+ }
+ }
+
+ var currentViewLoadRequest;
+ function sendRouteToViewManager(ctx, next, route, controllerFactory) {
+
+ cancelCurrentLoadRequest();
+
+ var isBackNav = ctx.isBack;
+
+ var currentRequest = {
+ url: baseUrl() + ctx.path,
+ transition: route.transition,
+ isBack: isBackNav,
+ state: ctx.state,
+ type: route.type,
+ controllerFactory: controllerFactory,
+ options: {
+ supportsThemeMedia: route.supportsThemeMedia || false
+ }
+ };
+ currentViewLoadRequest = currentRequest;
+
+ var onNewViewNeeded = function () {
+ if (typeof route.path === 'string') {
+
+ loadContentUrl(ctx, next, route, currentRequest);
+
+ } else {
+ // ? TODO
+ next();
+ }
+ };
+
+ if (!isBackNav) {
+ // Don't force a new view for home due to the back menu
+ if (route.type != 'home') {
+ onNewViewNeeded();
+ return;
+ }
+ }
+
+ viewManager.tryRestoreView(currentRequest).then(function () {
+
+ // done
+ currentRouteInfo = {
+ route: route,
+ path: ctx.path
+ };
+
+ }, onNewViewNeeded);
+ }
+
+ var firstConnectionResult;
+ function start() {
+
+ loading.show();
+
+ require(['connectionManager'], function (connectionManagerInstance) {
+
+ connectionManager = connectionManagerInstance;
+
+ connectionManager.connect().then(function (result) {
+
+ firstConnectionResult = result;
+
+ loading.hide();
+
+ page({
+ click: false,
+ hashbang: true,
+ enableHistory: enableHistory()
+ });
+ });
+ });
+ }
+
+ function enableHistory() {
+
+ if (browser.xboxOne) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function enableNativeHistory() {
+ return page.enableNativeHistory();
+ }
+
+ function authenticate(ctx, route, callback) {
+
+ var firstResult = firstConnectionResult;
+ if (firstResult) {
+
+ firstConnectionResult = null;
+
+ if (firstResult.State != MediaBrowser.ConnectionState.SignedIn) {
+
+ handleConnectionResult(firstResult, loading);
+ return;
+ }
+ }
+
+ var server = connectionManager.currentLoggedInServer();
+ var pathname = ctx.pathname.toLowerCase();
+
+ console.log('Emby.Page - processing path request ' + pathname);
+
+ if (server) {
+
+ console.log('Emby.Page - user is authenticated');
+
+ if (ctx.isBack && (route.isDefaultRoute /*|| isStartup(ctx)*/)) {
+ handleBackToDefault();
+ }
+ else if (route.isDefaultRoute) {
+ console.log('Emby.Page - loading skin home page');
+ skinManager.loadUserSkin();
+ } else {
+ console.log('Emby.Page - next()');
+ callback();
+ }
+ return;
+ }
+
+ console.log('Emby.Page - user is not authenticated');
+
+ if (!allowAnonymous(ctx)) {
+
+ console.log('Emby.Page - route does not allow anonymous access, redirecting to login');
+ redirectToLogin();
+ }
+ else {
+
+ console.log('Emby.Page - proceeding to ' + pathname);
+ callback();
+ }
+ }
+
+ var isHandlingBackToDefault;
+ function handleBackToDefault() {
+
+ skinManager.loadUserSkin();
+
+ if (isHandlingBackToDefault) {
+ return;
+ }
+
+ isHandlingBackToDefault = true;
+
+ // This must result in a call to either
+ // skinManager.loadUserSkin();
+ // Logout
+ // Or exit app
+
+ skinManager.getCurrentSkin().showBackMenu().then(function () {
+
+ isHandlingBackToDefault = false;
+ });
+ }
+
+ function loadContent(ctx, route, html, request) {
+
+ html = Globalize.translateDocument(html, route.dictionary);
+ request.view = html;
+
+ viewManager.loadView(request);
+
+ currentRouteInfo = {
+ route: route,
+ path: ctx.path
+ };
+ //next();
+
+ ctx.handled = true;
+ }
+
+ var baseRoute = window.location.href.split('?')[0].replace('/index.html', '');
+ // support hashbang
+ baseRoute = baseRoute.split('#')[0];
+ if (baseRoute.lastIndexOf('/') == baseRoute.length - 1) {
+ baseRoute = baseRoute.substring(0, baseRoute.length - 1);
+ }
+ function baseUrl() {
+ return baseRoute;
+ }
+
+ function getHandler(route) {
+ return function (ctx, next) {
+ handleRoute(ctx, next, route);
+ };
+ }
+
+ function getWindowLocationSearch(win) {
+
+ var currentPath = currentRouteInfo ? (currentRouteInfo.path || '') : '';
+
+ var index = currentPath.indexOf('?');
+ var search = '';
+
+ if (index != -1) {
+ search = currentPath.substring(index);
+ }
+
+ return search || '';
+ }
+
+ function param(name, url) {
+ name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
+ var regexS = "[\\?&]" + name + "=([^]*)";
+ var regex = new RegExp(regexS, "i");
+
+ var results = regex.exec(url || getWindowLocationSearch());
+ if (results == null)
+ return "";
+ else
+ return decodeURIComponent(results[1].replace(/\+/g, " "));
+ }
+
+ function back() {
+
+ page.back();
+ }
+ function canGoBack() {
+
+ var curr = current();
+
+ if (!curr) {
+ return false;
+ }
+
+ if (curr.type == 'home') {
+ return false;
+ }
+ return page.canGoBack();
+ }
+ function show(path, options) {
+
+ return new Promise(function (resolve, reject) {
+
+ var baseRoute = baseUrl();
+ path = path.replace(baseRoute, '');
+
+ if (currentRouteInfo && currentRouteInfo.path == path) {
+
+ // can't use this with home right now due to the back menu
+ if (currentRouteInfo.route.type != 'home') {
+ resolve();
+ return;
+ }
+ }
+
+ page.show(path, options);
+ setTimeout(resolve, 500);
+ });
+ }
+
+ var currentRouteInfo;
+ function current() {
+ return currentRouteInfo ? currentRouteInfo.route : null;
+ }
+
+ function goHome() {
+
+ var skin = skinManager.getCurrentSkin();
+
+ var homeRoute = skin.getRoutes().filter(function (r) {
+ return r.type == 'home';
+ })[0];
+
+ return show(pluginManager.mapRoute(skin, homeRoute));
+ }
+
+ function showItem(item) {
+
+ if (typeof (item) === 'string') {
+ Emby.Models.item(item).then(showItem);
+
+ } else {
+ skinManager.getCurrentSkin().showItem(item);
+ }
+ }
+
+ function setTitle(title) {
+ skinManager.getCurrentSkin().setTitle(title);
+ }
+
+ function gotoSettings() {
+ show('/settings/settings.html');
+ }
+
+ function selectServer() {
+ show('/startup/selectserver.html');
+ }
+
+ function showVideoOsd() {
+ var skin = skinManager.getCurrentSkin();
+
+ var homeRoute = skin.getRoutes().filter(function (r) {
+ return r.type == 'video-osd';
+ })[0];
+
+ return show(pluginManager.mapRoute(skin, homeRoute));
+ }
+
+ var allRoutes = [];
+
+ function addRoute(path, newRoute) {
+
+ page(path, getHandler(newRoute));
+ allRoutes.push(newRoute);
+ }
+
+ function getRoutes() {
+ return allRoutes;
+ }
+
+ function setTransparency(level) {
+
+ if (level == 'full' || level == Emby.TransparencyLevel.Full) {
+ backdrop.clear(true);
+ document.documentElement.classList.add('transparentDocument');
+ }
+ else if (level == 'backdrop' || level == Emby.TransparencyLevel.Backdrop) {
+ backdrop.externalBackdrop(true);
+ document.documentElement.classList.add('transparentDocument');
+ } else {
+ backdrop.externalBackdrop(false);
+ document.documentElement.classList.remove('transparentDocument');
+ }
+ }
+
+ function pushState(state, title, url) {
+
+ state.navigate = false;
+
+ page.pushState(state, title, url);
+ }
+
+ return {
+ addRoute: addRoute,
+ param: param,
+ back: back,
+ show: show,
+ start: start,
+ baseUrl: baseUrl,
+ canGoBack: canGoBack,
+ current: current,
+ redirectToLogin: redirectToLogin,
+ goHome: goHome,
+ gotoSettings: gotoSettings,
+ showItem: showItem,
+ setTitle: setTitle,
+ selectServer: selectServer,
+ showVideoOsd: showVideoOsd,
+ setTransparency: setTransparency,
+ getRoutes: getRoutes,
+
+ pushState: pushState,
+
+ TransparencyLevel: {
+ None: 0,
+ Backdrop: 1,
+ Full: 2
+ },
+ enableNativeHistory: enableNativeHistory
+ };
+
+});
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/iron-icon/.bower.json b/dashboard-ui/bower_components/iron-icon/.bower.json
index f0167baf13..9784e3a3b7 100644
--- a/dashboard-ui/bower_components/iron-icon/.bower.json
+++ b/dashboard-ui/bower_components/iron-icon/.bower.json
@@ -32,14 +32,14 @@
"web-component-tester": "^4.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
},
- "homepage": "https://github.com/polymerelements/iron-icon",
+ "homepage": "https://github.com/PolymerElements/iron-icon",
"_release": "1.0.8",
"_resolution": {
"type": "version",
"tag": "v1.0.8",
"commit": "f36b38928849ef3853db727faa8c9ef104d611eb"
},
- "_source": "git://github.com/polymerelements/iron-icon.git",
+ "_source": "git://github.com/PolymerElements/iron-icon.git",
"_target": "^1.0.0",
- "_originalSource": "polymerelements/iron-icon"
+ "_originalSource": "PolymerElements/iron-icon"
}
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/iron-selector/.bower.json b/dashboard-ui/bower_components/iron-selector/.bower.json
index 66d7d54b0f..c6b481c2cb 100644
--- a/dashboard-ui/bower_components/iron-selector/.bower.json
+++ b/dashboard-ui/bower_components/iron-selector/.bower.json
@@ -36,7 +36,7 @@
"tag": "v1.3.0",
"commit": "1662093611cda3fd29125cdab94a61d3d88093da"
},
- "_source": "git://github.com/PolymerElements/iron-selector.git",
+ "_source": "git://github.com/polymerelements/iron-selector.git",
"_target": "^1.0.0",
- "_originalSource": "PolymerElements/iron-selector"
+ "_originalSource": "polymerelements/iron-selector"
}
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/page.js/.bower.json b/dashboard-ui/bower_components/page.js/.bower.json
new file mode 100644
index 0000000000..1f3872eaba
--- /dev/null
+++ b/dashboard-ui/bower_components/page.js/.bower.json
@@ -0,0 +1,40 @@
+{
+ "name": "page",
+ "description": "Tiny client-side router",
+ "keywords": [
+ "page",
+ "route",
+ "router",
+ "routes",
+ "pushState"
+ ],
+ "main": "page.js",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/visionmedia/page.js"
+ },
+ "ignore": [
+ ".gitignore",
+ ".npmignore",
+ ".travis.yml",
+ "component.json",
+ "examples",
+ "History.md",
+ "Makefile",
+ "package.json",
+ "Readme.md",
+ "test"
+ ],
+ "license": "MIT",
+ "homepage": "https://github.com/visionmedia/page.js",
+ "version": "1.6.4",
+ "_release": "1.6.4",
+ "_resolution": {
+ "type": "version",
+ "tag": "1.6.4",
+ "commit": "d11509f1f0fed0309391d995919c25dce84b8abd"
+ },
+ "_source": "git://github.com/visionmedia/page.js.git",
+ "_target": "~1.6.3",
+ "_originalSource": "page.js"
+}
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/page.js/.jsbeautifyrc b/dashboard-ui/bower_components/page.js/.jsbeautifyrc
new file mode 100644
index 0000000000..5067554a76
--- /dev/null
+++ b/dashboard-ui/bower_components/page.js/.jsbeautifyrc
@@ -0,0 +1,5 @@
+{
+ "indent_size": 2,
+ "indent_char": " ",
+ "indent_with_tabs": false
+}
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/page.js/.jshintrc b/dashboard-ui/bower_components/page.js/.jshintrc
new file mode 100644
index 0000000000..cc5fa19a97
--- /dev/null
+++ b/dashboard-ui/bower_components/page.js/.jshintrc
@@ -0,0 +1,23 @@
+{
+ "browser": true,
+ "node":true,
+ "expr": true,
+ "laxcomma": true,
+ "-W079": true,
+ "-W014": true,
+ "curly": false,
+ "eqeqeq": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "undef": true,
+ "unused": false,
+ "strict": true,
+ "trailing": false,
+ "smarttabs": true,
+ "latedef": false,
+ "indent": 2
+}
diff --git a/dashboard-ui/bower_components/page.js/bower.json b/dashboard-ui/bower_components/page.js/bower.json
new file mode 100644
index 0000000000..faf30fd876
--- /dev/null
+++ b/dashboard-ui/bower_components/page.js/bower.json
@@ -0,0 +1,23 @@
+{
+ "name": "page",
+ "description": "Tiny client-side router",
+ "keywords": ["page", "route", "router", "routes", "pushState"],
+ "main": "page.js",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/visionmedia/page.js"
+ },
+ "ignore": [
+ ".gitignore",
+ ".npmignore",
+ ".travis.yml",
+ "component.json",
+ "examples",
+ "History.md",
+ "Makefile",
+ "package.json",
+ "Readme.md",
+ "test"
+ ],
+ "license": "MIT"
+}
diff --git a/dashboard-ui/bower_components/page.js/index.js b/dashboard-ui/bower_components/page.js/index.js
new file mode 100644
index 0000000000..d37374f236
--- /dev/null
+++ b/dashboard-ui/bower_components/page.js/index.js
@@ -0,0 +1,619 @@
+ /* globals require, module */
+
+ 'use strict';
+
+ /**
+ * Module dependencies.
+ */
+
+ var pathtoRegexp = require('path-to-regexp');
+
+ /**
+ * Module exports.
+ */
+
+ module.exports = page;
+
+ /**
+ * Detect click event
+ */
+ var clickEvent = ('undefined' !== typeof document) && document.ontouchstart ? 'touchstart' : 'click';
+
+ /**
+ * To work properly with the URL
+ * history.location generated polyfill in https://github.com/devote/HTML5-History-API
+ */
+
+ var location = ('undefined' !== typeof window) && (window.history.location || window.location);
+
+ /**
+ * Perform initial dispatch.
+ */
+
+ var dispatch = true;
+
+
+ /**
+ * Decode URL components (query string, pathname, hash).
+ * Accommodates both regular percent encoding and x-www-form-urlencoded format.
+ */
+ var decodeURLComponents = true;
+
+ /**
+ * Base path.
+ */
+
+ var base = '';
+
+ /**
+ * Running flag.
+ */
+
+ var running;
+
+ /**
+ * HashBang option
+ */
+
+ var hashbang = false;
+
+ /**
+ * Previous context, for capturing
+ * page exit events.
+ */
+
+ var prevContext;
+
+ /**
+ * Register `path` with callback `fn()`,
+ * or route `path`, or redirection,
+ * or `page.start()`.
+ *
+ * page(fn);
+ * page('*', fn);
+ * page('/user/:id', load, user);
+ * page('/user/' + user.id, { some: 'thing' });
+ * page('/user/' + user.id);
+ * page('/from', '/to')
+ * page();
+ *
+ * @param {String|Function} path
+ * @param {Function} fn...
+ * @api public
+ */
+
+ function page(path, fn) {
+ //
+ if ('function' === typeof path) {
+ return page('*', path);
+ }
+
+ // route to
+ if ('function' === typeof fn) {
+ var route = new Route(path);
+ for (var i = 1; i < arguments.length; ++i) {
+ page.callbacks.push(route.middleware(arguments[i]));
+ }
+ // show with [state]
+ } else if ('string' === typeof path) {
+ page['string' === typeof fn ? 'redirect' : 'show'](path, fn);
+ // start [options]
+ } else {
+ page.start(path);
+ }
+ }
+
+ /**
+ * Callback functions.
+ */
+
+ page.callbacks = [];
+ page.exits = [];
+
+ /**
+ * Current path being processed
+ * @type {String}
+ */
+ page.current = '';
+
+ /**
+ * Number of pages navigated to.
+ * @type {number}
+ *
+ * page.len == 0;
+ * page('/login');
+ * page.len == 1;
+ */
+
+ page.len = 0;
+
+ /**
+ * Get or set basepath to `path`.
+ *
+ * @param {String} path
+ * @api public
+ */
+
+ page.base = function(path) {
+ if (0 === arguments.length) return base;
+ base = path;
+ };
+
+ /**
+ * Bind with the given `options`.
+ *
+ * Options:
+ *
+ * - `click` bind to click events [true]
+ * - `popstate` bind to popstate [true]
+ * - `dispatch` perform initial dispatch [true]
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+ page.start = function(options) {
+ options = options || {};
+ if (running) return;
+ running = true;
+ if (false === options.dispatch) dispatch = false;
+ if (false === options.decodeURLComponents) decodeURLComponents = false;
+ if (false !== options.popstate) window.addEventListener('popstate', onpopstate, false);
+ if (false !== options.click) {
+ document.addEventListener(clickEvent, onclick, false);
+ }
+ if (true === options.hashbang) hashbang = true;
+ if (!dispatch) return;
+ var url = (hashbang && ~location.hash.indexOf('#!')) ? location.hash.substr(2) + location.search : location.pathname + location.search + location.hash;
+ page.replace(url, null, true, dispatch);
+ };
+
+ /**
+ * Unbind click and popstate event handlers.
+ *
+ * @api public
+ */
+
+ page.stop = function() {
+ if (!running) return;
+ page.current = '';
+ page.len = 0;
+ running = false;
+ document.removeEventListener(clickEvent, onclick, false);
+ window.removeEventListener('popstate', onpopstate, false);
+ };
+
+ /**
+ * Show `path` with optional `state` object.
+ *
+ * @param {String} path
+ * @param {Object} state
+ * @param {Boolean} dispatch
+ * @return {Context}
+ * @api public
+ */
+
+ page.show = function(path, state, dispatch, push) {
+ var ctx = new Context(path, state);
+ page.current = ctx.path;
+ if (false !== dispatch) page.dispatch(ctx);
+ if (false !== ctx.handled && false !== push) ctx.pushState();
+ return ctx;
+ };
+
+ /**
+ * Goes back in the history
+ * Back should always let the current route push state and then go back.
+ *
+ * @param {String} path - fallback path to go back if no more history exists, if undefined defaults to page.base
+ * @param {Object} [state]
+ * @api public
+ */
+
+ page.back = function(path, state) {
+ if (page.len > 0) {
+ // this may need more testing to see if all browsers
+ // wait for the next tick to go back in history
+ history.back();
+ page.len--;
+ } else if (path) {
+ setTimeout(function() {
+ page.show(path, state);
+ });
+ }else{
+ setTimeout(function() {
+ page.show(base, state);
+ });
+ }
+ };
+
+
+ /**
+ * Register route to redirect from one path to other
+ * or just redirect to another route
+ *
+ * @param {String} from - if param 'to' is undefined redirects to 'from'
+ * @param {String} [to]
+ * @api public
+ */
+ page.redirect = function(from, to) {
+ // Define route from a path to another
+ if ('string' === typeof from && 'string' === typeof to) {
+ page(from, function(e) {
+ setTimeout(function() {
+ page.replace(to);
+ }, 0);
+ });
+ }
+
+ // Wait for the push state and replace it with another
+ if ('string' === typeof from && 'undefined' === typeof to) {
+ setTimeout(function() {
+ page.replace(from);
+ }, 0);
+ }
+ };
+
+ /**
+ * Replace `path` with optional `state` object.
+ *
+ * @param {String} path
+ * @param {Object} state
+ * @return {Context}
+ * @api public
+ */
+
+
+ page.replace = function(path, state, init, dispatch) {
+ var ctx = new Context(path, state);
+ page.current = ctx.path;
+ ctx.init = init;
+ ctx.save(); // save before dispatching, which may redirect
+ if (false !== dispatch) page.dispatch(ctx);
+ return ctx;
+ };
+
+ /**
+ * Dispatch the given `ctx`.
+ *
+ * @param {Object} ctx
+ * @api private
+ */
+
+ page.dispatch = function(ctx) {
+ var prev = prevContext,
+ i = 0,
+ j = 0;
+
+ prevContext = ctx;
+
+ function nextExit() {
+ var fn = page.exits[j++];
+ if (!fn) return nextEnter();
+ fn(prev, nextExit);
+ }
+
+ function nextEnter() {
+ var fn = page.callbacks[i++];
+
+ if (ctx.path !== page.current) {
+ ctx.handled = false;
+ return;
+ }
+ if (!fn) return unhandled(ctx);
+ fn(ctx, nextEnter);
+ }
+
+ if (prev) {
+ nextExit();
+ } else {
+ nextEnter();
+ }
+ };
+
+ /**
+ * Unhandled `ctx`. When it's not the initial
+ * popstate then redirect. If you wish to handle
+ * 404s on your own use `page('*', callback)`.
+ *
+ * @param {Context} ctx
+ * @api private
+ */
+
+ function unhandled(ctx) {
+ if (ctx.handled) return;
+ var current;
+
+ if (hashbang) {
+ current = base + location.hash.replace('#!', '');
+ } else {
+ current = location.pathname + location.search;
+ }
+
+ if (current === ctx.canonicalPath) return;
+ page.stop();
+ ctx.handled = false;
+ location.href = ctx.canonicalPath;
+ }
+
+ /**
+ * Register an exit route on `path` with
+ * callback `fn()`, which will be called
+ * on the previous context when a new
+ * page is visited.
+ */
+ page.exit = function(path, fn) {
+ if (typeof path === 'function') {
+ return page.exit('*', path);
+ }
+
+ var route = new Route(path);
+ for (var i = 1; i < arguments.length; ++i) {
+ page.exits.push(route.middleware(arguments[i]));
+ }
+ };
+
+ /**
+ * Remove URL encoding from the given `str`.
+ * Accommodates whitespace in both x-www-form-urlencoded
+ * and regular percent-encoded form.
+ *
+ * @param {str} URL component to decode
+ */
+ function decodeURLEncodedURIComponent(val) {
+ if (typeof val !== 'string') { return val; }
+ return decodeURLComponents ? decodeURIComponent(val.replace(/\+/g, ' ')) : val;
+ }
+
+ /**
+ * Initialize a new "request" `Context`
+ * with the given `path` and optional initial `state`.
+ *
+ * @param {String} path
+ * @param {Object} state
+ * @api public
+ */
+
+ function Context(path, state) {
+ if ('/' === path[0] && 0 !== path.indexOf(base)) path = base + (hashbang ? '#!' : '') + path;
+ var i = path.indexOf('?');
+
+ this.canonicalPath = path;
+ this.path = path.replace(base, '') || '/';
+ if (hashbang) this.path = this.path.replace('#!', '') || '/';
+
+ this.title = document.title;
+ this.state = state || {};
+ this.state.path = path;
+ this.querystring = ~i ? decodeURLEncodedURIComponent(path.slice(i + 1)) : '';
+ this.pathname = decodeURLEncodedURIComponent(~i ? path.slice(0, i) : path);
+ this.params = {};
+
+ // fragment
+ this.hash = '';
+ if (!hashbang) {
+ if (!~this.path.indexOf('#')) return;
+ var parts = this.path.split('#');
+ this.path = parts[0];
+ this.hash = decodeURLEncodedURIComponent(parts[1]) || '';
+ this.querystring = this.querystring.split('#')[0];
+ }
+ }
+
+ /**
+ * Expose `Context`.
+ */
+
+ page.Context = Context;
+
+ /**
+ * Push state.
+ *
+ * @api private
+ */
+
+ Context.prototype.pushState = function() {
+ page.len++;
+ history.pushState(this.state, this.title, hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
+ };
+
+ /**
+ * Save the context state.
+ *
+ * @api public
+ */
+
+ Context.prototype.save = function() {
+ history.replaceState(this.state, this.title, hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
+ };
+
+ /**
+ * Initialize `Route` with the given HTTP `path`,
+ * and an array of `callbacks` and `options`.
+ *
+ * Options:
+ *
+ * - `sensitive` enable case-sensitive routes
+ * - `strict` enable strict matching for trailing slashes
+ *
+ * @param {String} path
+ * @param {Object} options.
+ * @api private
+ */
+
+ function Route(path, options) {
+ options = options || {};
+ this.path = (path === '*') ? '(.*)' : path;
+ this.method = 'GET';
+ this.regexp = pathtoRegexp(this.path,
+ this.keys = [],
+ options.sensitive,
+ options.strict);
+ }
+
+ /**
+ * Expose `Route`.
+ */
+
+ page.Route = Route;
+
+ /**
+ * Return route middleware with
+ * the given callback `fn()`.
+ *
+ * @param {Function} fn
+ * @return {Function}
+ * @api public
+ */
+
+ Route.prototype.middleware = function(fn) {
+ var self = this;
+ return function(ctx, next) {
+ if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
+ next();
+ };
+ };
+
+ /**
+ * Check if this route matches `path`, if so
+ * populate `params`.
+ *
+ * @param {String} path
+ * @param {Object} params
+ * @return {Boolean}
+ * @api private
+ */
+
+ Route.prototype.match = function(path, params) {
+ var keys = this.keys,
+ qsIndex = path.indexOf('?'),
+ pathname = ~qsIndex ? path.slice(0, qsIndex) : path,
+ m = this.regexp.exec(decodeURIComponent(pathname));
+
+ if (!m) return false;
+
+ for (var i = 1, len = m.length; i < len; ++i) {
+ var key = keys[i - 1];
+ var val = decodeURLEncodedURIComponent(m[i]);
+ if (val !== undefined || !(hasOwnProperty.call(params, key.name))) {
+ params[key.name] = val;
+ }
+ }
+
+ return true;
+ };
+
+
+ /**
+ * Handle "populate" events.
+ */
+
+ var onpopstate = (function () {
+ var loaded = false;
+ if ('undefined' === typeof window) {
+ return;
+ }
+ if (document.readyState === 'complete') {
+ loaded = true;
+ } else {
+ window.addEventListener('load', function() {
+ setTimeout(function() {
+ loaded = true;
+ }, 0);
+ });
+ }
+ return function onpopstate(e) {
+ if (!loaded) return;
+ if (e.state) {
+ var path = e.state.path;
+ page.replace(path, e.state);
+ } else {
+ page.show(location.pathname + location.hash, undefined, undefined, false);
+ }
+ };
+ })();
+ /**
+ * Handle "click" events.
+ */
+
+ function onclick(e) {
+
+ if (1 !== which(e)) return;
+
+ if (e.metaKey || e.ctrlKey || e.shiftKey) return;
+ if (e.defaultPrevented) return;
+
+
+
+ // ensure link
+ var el = e.target;
+ while (el && 'A' !== el.nodeName) el = el.parentNode;
+ if (!el || 'A' !== el.nodeName) return;
+
+
+
+ // Ignore if tag has
+ // 1. "download" attribute
+ // 2. rel="external" attribute
+ if (el.hasAttribute('download') || el.getAttribute('rel') === 'external') return;
+
+ // ensure non-hash for the same path
+ var link = el.getAttribute('href');
+ if (!hashbang && el.pathname === location.pathname && (el.hash || '#' === link)) return;
+
+
+
+ // Check for mailto: in the href
+ if (link && link.indexOf('mailto:') > -1) return;
+
+ // check target
+ if (el.target) return;
+
+ // x-origin
+ if (!sameOrigin(el.href)) return;
+
+
+
+ // rebuild path
+ var path = el.pathname + el.search + (el.hash || '');
+
+ // strip leading "/[drive letter]:" on NW.js on Windows
+ if (typeof process !== 'undefined' && path.match(/^\/[a-zA-Z]:\//)) {
+ path = path.replace(/^\/[a-zA-Z]:\//, '/');
+ }
+
+ // same page
+ var orig = path;
+
+ if (path.indexOf(base) === 0) {
+ path = path.substr(base.length);
+ }
+
+ if (hashbang) path = path.replace('#!', '');
+
+ if (base && orig === path) return;
+
+ e.preventDefault();
+ page.show(orig);
+ }
+
+ /**
+ * Event button.
+ */
+
+ function which(e) {
+ e = e || window.event;
+ return null === e.which ? e.button : e.which;
+ }
+
+ /**
+ * Check if `href` is the same origin.
+ */
+
+ function sameOrigin(href) {
+ var origin = location.protocol + '//' + location.hostname;
+ if (location.port) origin += ':' + location.port;
+ return (href && (0 === href.indexOf(origin)));
+ }
+
+ page.sameOrigin = sameOrigin;
diff --git a/dashboard-ui/bower_components/page.js/page.js b/dashboard-ui/bower_components/page.js/page.js
new file mode 100644
index 0000000000..51f2190078
--- /dev/null
+++ b/dashboard-ui/bower_components/page.js/page.js
@@ -0,0 +1,1184 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.page=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
+ if ('function' === typeof path) {
+ return page('*', path);
+ }
+
+ // route to
+ if ('function' === typeof fn) {
+ var route = new Route(path);
+ for (var i = 1; i < arguments.length; ++i) {
+ page.callbacks.push(route.middleware(arguments[i]));
+ }
+ // show with [state]
+ } else if ('string' === typeof path) {
+ page['string' === typeof fn ? 'redirect' : 'show'](path, fn);
+ // start [options]
+ } else {
+ page.start(path);
+ }
+ }
+
+ /**
+ * Callback functions.
+ */
+
+ page.callbacks = [];
+ page.exits = [];
+
+ /**
+ * Current path being processed
+ * @type {String}
+ */
+ page.current = '';
+
+ /**
+ * Number of pages navigated to.
+ * @type {number}
+ *
+ * page.len == 0;
+ * page('/login');
+ * page.len == 1;
+ */
+
+ page.len = 0;
+
+ /**
+ * Get or set basepath to `path`.
+ *
+ * @param {String} path
+ * @api public
+ */
+
+ page.base = function(path) {
+ if (0 === arguments.length) return base;
+ base = path;
+ };
+
+ /**
+ * Bind with the given `options`.
+ *
+ * Options:
+ *
+ * - `click` bind to click events [true]
+ * - `popstate` bind to popstate [true]
+ * - `dispatch` perform initial dispatch [true]
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+ page.start = function(options) {
+ options = options || {};
+ if (running) return;
+ running = true;
+ if (false === options.dispatch) dispatch = false;
+ if (false === options.decodeURLComponents) decodeURLComponents = false;
+ if (false !== options.popstate) window.addEventListener('popstate', onpopstate, false);
+ if (false !== options.click) {
+ document.addEventListener(clickEvent, onclick, false);
+ }
+ if (options.enableHistory != null) enableHistory = options.enableHistory;
+ if (true === options.hashbang) hashbang = true;
+ if (!dispatch) return;
+ var url = (hashbang && ~location.hash.indexOf('#!')) ? location.hash.substr(2) + location.search : location.pathname + location.search + location.hash;
+ page.replace(url, null, true, dispatch);
+ };
+
+ /**
+ * Unbind click and popstate event handlers.
+ *
+ * @api public
+ */
+
+ page.stop = function() {
+ if (!running) return;
+ page.current = '';
+ page.len = 0;
+ running = false;
+ document.removeEventListener(clickEvent, onclick, false);
+ window.removeEventListener('popstate', onpopstate, false);
+ };
+
+ /**
+ * Show `path` with optional `state` object.
+ *
+ * @param {String} path
+ * @param {Object} state
+ * @param {Boolean} dispatch
+ * @return {Context}
+ * @api public
+ */
+
+ page.show = function(path, state, dispatch, push, isBack) {
+ var ctx = new Context(path, state);
+ ctx.isBack = isBack;
+ page.current = ctx.path;
+ if (false !== dispatch) page.dispatch(ctx);
+ if (false !== ctx.handled && false !== push) ctx.pushState();
+ return ctx;
+ };
+
+ /**
+ * Goes back in the history
+ * Back should always let the current route push state and then go back.
+ *
+ * @param {String} path - fallback path to go back if no more history exists, if undefined defaults to page.base
+ * @param {Object} [state]
+ * @api public
+ */
+
+ page.back = function (path, state) {
+
+ if (enableHistory) {
+ // Keep it simple and mimic browser back
+ history.back();
+ return;
+ }
+
+ if (page.len > 0) {
+ // this may need more testing to see if all browsers
+ // wait for the next tick to go back in history
+ if (enableHistory) {
+ history.back();
+ } else {
+
+ if (backStack.length > 2) {
+ backStack.length--;
+ var previousState = backStack[backStack.length - 1];
+ page.show(previousState.path, previousState.state, true, false, true);
+ }
+ }
+ page.len--;
+ } else if (path) {
+ setTimeout(function() {
+ page.show(path, state);
+ });
+ }else{
+ setTimeout(function() {
+ page.show(base, state);
+ });
+ }
+ };
+
+ page.enableNativeHistory = function () {
+ return enableHistory;
+ };
+
+ page.canGoBack = function () {
+ if (enableHistory) {
+ return history.length > 1;
+ }
+ return (page.len || 0) > 0;
+ };
+
+ /**
+ * Register route to redirect from one path to other
+ * or just redirect to another route
+ *
+ * @param {String} from - if param 'to' is undefined redirects to 'from'
+ * @param {String} [to]
+ * @api public
+ */
+ page.redirect = function(from, to) {
+ // Define route from a path to another
+ if ('string' === typeof from && 'string' === typeof to) {
+ page(from, function(e) {
+ setTimeout(function() {
+ page.replace(to);
+ }, 0);
+ });
+ }
+
+ // Wait for the push state and replace it with another
+ if ('string' === typeof from && 'undefined' === typeof to) {
+ setTimeout(function() {
+ page.replace(from);
+ }, 0);
+ }
+ };
+
+ /**
+ * Replace `path` with optional `state` object.
+ *
+ * @param {String} path
+ * @param {Object} state
+ * @return {Context}
+ * @api public
+ */
+
+
+ page.replace = function(path, state, init, dispatch, isBack) {
+ var ctx = new Context(path, state);
+ ctx.isBack = isBack;
+ page.current = ctx.path;
+ ctx.init = init;
+ ctx.save(); // save before dispatching, which may redirect
+ if (false !== dispatch) page.dispatch(ctx);
+ return ctx;
+ };
+
+ /**
+ * Dispatch the given `ctx`.
+ *
+ * @param {Object} ctx
+ * @api private
+ */
+
+ page.dispatch = function(ctx) {
+ var prev = prevContext,
+ i = 0,
+ j = 0;
+
+ prevContext = ctx;
+
+ function nextExit() {
+ var fn = page.exits[j++];
+ if (!fn) return nextEnter();
+ fn(prev, nextExit);
+ }
+
+ function nextEnter() {
+ var fn = page.callbacks[i++];
+
+ if (ctx.path !== page.current) {
+ ctx.handled = false;
+ return;
+ }
+ if (!fn) return unhandled(ctx);
+ fn(ctx, nextEnter);
+ }
+
+ if (prev) {
+ nextExit();
+ } else {
+ nextEnter();
+ }
+ };
+
+ /**
+ * Unhandled `ctx`. When it's not the initial
+ * popstate then redirect. If you wish to handle
+ * 404s on your own use `page('*', callback)`.
+ *
+ * @param {Context} ctx
+ * @api private
+ */
+
+ function unhandled(ctx) {
+ if (ctx.handled) return;
+ var current;
+
+ if (hashbang) {
+ current = base + location.hash.replace('#!', '');
+ } else {
+ current = location.pathname + location.search;
+ }
+
+ if (current === ctx.canonicalPath) return;
+ page.stop();
+ ctx.handled = false;
+ location.href = ctx.canonicalPath;
+ }
+
+ /**
+ * Register an exit route on `path` with
+ * callback `fn()`, which will be called
+ * on the previous context when a new
+ * page is visited.
+ */
+ page.exit = function(path, fn) {
+ if (typeof path === 'function') {
+ return page.exit('*', path);
+ }
+
+ var route = new Route(path);
+ for (var i = 1; i < arguments.length; ++i) {
+ page.exits.push(route.middleware(arguments[i]));
+ }
+ };
+
+ /**
+ * Remove URL encoding from the given `str`.
+ * Accommodates whitespace in both x-www-form-urlencoded
+ * and regular percent-encoded form.
+ *
+ * @param {str} URL component to decode
+ */
+ function decodeURLEncodedURIComponent(val) {
+ if (typeof val !== 'string') { return val; }
+ return decodeURLComponents ? decodeURIComponent(val.replace(/\+/g, ' ')) : val;
+ }
+
+ /**
+ * Initialize a new "request" `Context`
+ * with the given `path` and optional initial `state`.
+ *
+ * @param {String} path
+ * @param {Object} state
+ * @api public
+ */
+
+ function Context(path, state) {
+ if ('/' === path[0] && 0 !== path.indexOf(base)) path = base + (hashbang ? '#!' : '') + path;
+ var i = path.indexOf('?');
+
+ this.canonicalPath = path;
+ this.path = path.replace(base, '') || '/';
+ if (hashbang) this.path = this.path.replace('#!', '') || '/';
+
+ this.title = document.title;
+ this.state = state || {};
+ this.state.path = path;
+ this.querystring = ~i ? decodeURLEncodedURIComponent(path.slice(i + 1)) : '';
+ this.pathname = decodeURLEncodedURIComponent(~i ? path.slice(0, i) : path);
+ this.params = {};
+
+ // fragment
+ this.hash = '';
+ if (!hashbang) {
+ if (!~this.path.indexOf('#')) return;
+ var parts = this.path.split('#');
+ this.path = parts[0];
+ this.hash = decodeURLEncodedURIComponent(parts[1]) || '';
+ this.querystring = this.querystring.split('#')[0];
+ }
+ }
+
+ /**
+ * Expose `Context`.
+ */
+
+ page.Context = Context;
+ var backStack = [];
+
+ /**
+ * Push state.
+ *
+ * @api private
+ */
+
+ Context.prototype.pushState = function() {
+ page.len++;
+
+ if (enableHistory) {
+ history.pushState(this.state, this.title, hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
+ } else {
+ backStack.push({
+ state: this.state,
+ title: this.title,
+ url: (hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath),
+ path: this.path
+ });
+ }
+ };
+
+ /**
+ * Save the context state.
+ *
+ * @api public
+ */
+
+ Context.prototype.save = function () {
+
+ if (enableHistory) {
+ history.replaceState(this.state, this.title, hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath);
+ } else {
+ backStack[page.len || 0] = {
+ state: this.state,
+ title: this.title,
+ url: (hashbang && this.path !== '/' ? '#!' + this.path : this.canonicalPath),
+ path: this.path
+ };
+ }
+ };
+
+ /**
+ * Initialize `Route` with the given HTTP `path`,
+ * and an array of `callbacks` and `options`.
+ *
+ * Options:
+ *
+ * - `sensitive` enable case-sensitive routes
+ * - `strict` enable strict matching for trailing slashes
+ *
+ * @param {String} path
+ * @param {Object} options.
+ * @api private
+ */
+
+ function Route(path, options) {
+ options = options || {};
+ this.path = (path === '*') ? '(.*)' : path;
+ this.method = 'GET';
+ this.regexp = pathtoRegexp(this.path,
+ this.keys = [],
+ options.sensitive,
+ options.strict);
+ }
+
+ /**
+ * Expose `Route`.
+ */
+
+ page.Route = Route;
+
+ /**
+ * Return route middleware with
+ * the given callback `fn()`.
+ *
+ * @param {Function} fn
+ * @return {Function}
+ * @api public
+ */
+
+ Route.prototype.middleware = function(fn) {
+ var self = this;
+ return function(ctx, next) {
+ if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
+ next();
+ };
+ };
+
+ /**
+ * Check if this route matches `path`, if so
+ * populate `params`.
+ *
+ * @param {String} path
+ * @param {Object} params
+ * @return {Boolean}
+ * @api private
+ */
+
+ Route.prototype.match = function(path, params) {
+ var keys = this.keys,
+ qsIndex = path.indexOf('?'),
+ pathname = ~qsIndex ? path.slice(0, qsIndex) : path,
+ m = this.regexp.exec(decodeURIComponent(pathname));
+
+ if (!m) return false;
+
+ for (var i = 1, len = m.length; i < len; ++i) {
+ var key = keys[i - 1];
+ var val = decodeURLEncodedURIComponent(m[i]);
+ if (val !== undefined || !(hasOwnProperty.call(params, key.name))) {
+ params[key.name] = val;
+ }
+ }
+
+ return true;
+ };
+
+
+ var previousPopState = {};
+
+ function ignorePopState(event) {
+
+ var state = event.state || {};
+
+ if (previousPopState.navigate === false) {
+ // Ignore
+ previousPopState = state;
+ return true;
+ }
+
+ previousPopState = state;
+ return false;
+ }
+
+ page.pushState = function (state, title, url) {
+ history.pushState(state, title, url);
+ previousPopState = state;
+ };
+
+ /**
+ * Handle "populate" events.
+ */
+
+ var onpopstate = (function () {
+ var loaded = false;
+ if ('undefined' === typeof window) {
+ return;
+ }
+ if (document.readyState === 'complete') {
+ loaded = true;
+ } else {
+ window.addEventListener('load', function() {
+ setTimeout(function() {
+ loaded = true;
+ }, 0);
+ });
+ }
+ return function onpopstate(e) {
+ if (!loaded) return;
+ if (ignorePopState(e)) return;
+ if (e.state) {
+ var path = e.state.path;
+ page.replace(path, e.state, null, null, true);
+ } else {
+ page.show(location.pathname + location.hash, undefined, undefined, false, true);
+ }
+ };
+ })();
+ /**
+ * Handle "click" events.
+ */
+
+ function onclick(e) {
+
+ if (1 !== which(e)) return;
+
+ if (e.metaKey || e.ctrlKey || e.shiftKey) return;
+ if (e.defaultPrevented) return;
+
+
+
+ // ensure link
+ var el = e.target;
+ while (el && 'A' !== el.nodeName) el = el.parentNode;
+ if (!el || 'A' !== el.nodeName) return;
+
+
+
+ // Ignore if tag has
+ // 1. "download" attribute
+ // 2. rel="external" attribute
+ if (el.hasAttribute('download') || el.getAttribute('rel') === 'external') return;
+
+ // ensure non-hash for the same path
+ var link = el.getAttribute('href');
+ if (!hashbang && el.pathname === location.pathname && (el.hash || '#' === link)) return;
+
+
+
+ // Check for mailto: in the href
+ if (link && link.indexOf('mailto:') > -1) return;
+
+ // check target
+ if (el.target) return;
+
+ // x-origin
+ if (!sameOrigin(el.href)) return;
+
+
+
+ // rebuild path
+ var path = el.pathname + el.search + (el.hash || '');
+
+ // strip leading "/[drive letter]:" on NW.js on Windows
+ if (typeof process !== 'undefined' && path.match(/^\/[a-zA-Z]:\//)) {
+ path = path.replace(/^\/[a-zA-Z]:\//, '/');
+ }
+
+ // same page
+ var orig = path;
+
+ if (path.indexOf(base) === 0) {
+ path = path.substr(base.length);
+ }
+
+ if (hashbang) path = path.replace('#!', '');
+
+ if (base && orig === path) return;
+
+ e.preventDefault();
+ page.show(orig);
+ }
+
+ /**
+ * Event button.
+ */
+
+ function which(e) {
+ e = e || window.event;
+ return null === e.which ? e.button : e.which;
+ }
+
+ /**
+ * Check if `href` is the same origin.
+ */
+
+ function sameOrigin(href) {
+ var origin = location.protocol + '//' + location.hostname;
+ if (location.port) origin += ':' + location.port;
+ return (href && (0 === href.indexOf(origin)));
+ }
+
+ page.sameOrigin = sameOrigin;
+
+}).call(this,require('_process'))
+},{"_process":2,"path-to-regexp":3}],2:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+
+process.nextTick = (function () {
+ var canSetImmediate = typeof window !== 'undefined'
+ && window.setImmediate;
+ var canMutationObserver = typeof window !== 'undefined'
+ && window.MutationObserver;
+ var canPost = typeof window !== 'undefined'
+ && window.postMessage && window.addEventListener
+ ;
+
+ if (canSetImmediate) {
+ return function (f) { return window.setImmediate(f) };
+ }
+
+ var queue = [];
+
+ if (canMutationObserver) {
+ var hiddenDiv = document.createElement("div");
+ var observer = new MutationObserver(function () {
+ var queueList = queue.slice();
+ queue.length = 0;
+ queueList.forEach(function (fn) {
+ fn();
+ });
+ });
+
+ observer.observe(hiddenDiv, { attributes: true });
+
+ return function nextTick(fn) {
+ if (!queue.length) {
+ hiddenDiv.setAttribute('yes', 'no');
+ }
+ queue.push(fn);
+ };
+ }
+
+ if (canPost) {
+ window.addEventListener('message', function (ev) {
+ var source = ev.source;
+ if ((source === window || source === null) && ev.data === 'process-tick') {
+ ev.stopPropagation();
+ if (queue.length > 0) {
+ var fn = queue.shift();
+ fn();
+ }
+ }
+ }, true);
+
+ return function nextTick(fn) {
+ queue.push(fn);
+ window.postMessage('process-tick', '*');
+ };
+ }
+
+ return function nextTick(fn) {
+ setTimeout(fn, 0);
+ };
+})();
+
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+// TODO(shtylman)
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+
+},{}],3:[function(require,module,exports){
+var isarray = require('isarray')
+
+/**
+ * Expose `pathToRegexp`.
+ */
+module.exports = pathToRegexp
+module.exports.parse = parse
+module.exports.compile = compile
+module.exports.tokensToFunction = tokensToFunction
+module.exports.tokensToRegExp = tokensToRegExp
+
+/**
+ * The main path matching regexp utility.
+ *
+ * @type {RegExp}
+ */
+var PATH_REGEXP = new RegExp([
+ // Match escaped characters that would otherwise appear in future matches.
+ // This allows the user to escape special characters that won't transform.
+ '(\\\\.)',
+ // Match Express-style parameters and un-named parameters with a prefix
+ // and optional suffixes. Matches appear as:
+ //
+ // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
+ // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
+ // "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
+ '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))'
+].join('|'), 'g')
+
+/**
+ * Parse a string for the raw tokens.
+ *
+ * @param {String} str
+ * @return {Array}
+ */
+function parse (str) {
+ var tokens = []
+ var key = 0
+ var index = 0
+ var path = ''
+ var res
+
+ while ((res = PATH_REGEXP.exec(str)) != null) {
+ var m = res[0]
+ var escaped = res[1]
+ var offset = res.index
+ path += str.slice(index, offset)
+ index = offset + m.length
+
+ // Ignore already escaped sequences.
+ if (escaped) {
+ path += escaped[1]
+ continue
+ }
+
+ // Push the current path onto the tokens.
+ if (path) {
+ tokens.push(path)
+ path = ''
+ }
+
+ var prefix = res[2]
+ var name = res[3]
+ var capture = res[4]
+ var group = res[5]
+ var suffix = res[6]
+ var asterisk = res[7]
+
+ var repeat = suffix === '+' || suffix === '*'
+ var optional = suffix === '?' || suffix === '*'
+ var delimiter = prefix || '/'
+ var pattern = capture || group || (asterisk ? '.*' : '[^' + delimiter + ']+?')
+
+ tokens.push({
+ name: name || key++,
+ prefix: prefix || '',
+ delimiter: delimiter,
+ optional: optional,
+ repeat: repeat,
+ pattern: escapeGroup(pattern)
+ })
+ }
+
+ // Match any characters still remaining.
+ if (index < str.length) {
+ path += str.substr(index)
+ }
+
+ // If the path exists, push it onto the end.
+ if (path) {
+ tokens.push(path)
+ }
+
+ return tokens
+}
+
+/**
+ * Compile a string to a template function for the path.
+ *
+ * @param {String} str
+ * @return {Function}
+ */
+function compile (str) {
+ return tokensToFunction(parse(str))
+}
+
+/**
+ * Expose a method for transforming tokens into the path function.
+ */
+function tokensToFunction (tokens) {
+ // Compile all the tokens into regexps.
+ var matches = new Array(tokens.length)
+
+ // Compile all the patterns before compilation.
+ for (var i = 0; i < tokens.length; i++) {
+ if (typeof tokens[i] === 'object') {
+ matches[i] = new RegExp('^' + tokens[i].pattern + '$')
+ }
+ }
+
+ return function (obj) {
+ var path = ''
+ var data = obj || {}
+
+ for (var i = 0; i < tokens.length; i++) {
+ var token = tokens[i]
+
+ if (typeof token === 'string') {
+ path += token
+
+ continue
+ }
+
+ var value = data[token.name]
+ var segment
+
+ if (value == null) {
+ if (token.optional) {
+ continue
+ } else {
+ throw new TypeError('Expected "' + token.name + '" to be defined')
+ }
+ }
+
+ if (isarray(value)) {
+ if (!token.repeat) {
+ throw new TypeError('Expected "' + token.name + '" to not repeat, but received "' + value + '"')
+ }
+
+ if (value.length === 0) {
+ if (token.optional) {
+ continue
+ } else {
+ throw new TypeError('Expected "' + token.name + '" to not be empty')
+ }
+ }
+
+ for (var j = 0; j < value.length; j++) {
+ segment = encodeURIComponent(value[j])
+
+ if (!matches[i].test(segment)) {
+ throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
+ }
+
+ path += (j === 0 ? token.prefix : token.delimiter) + segment
+ }
+
+ continue
+ }
+
+ segment = encodeURIComponent(value)
+
+ if (!matches[i].test(segment)) {
+ throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
+ }
+
+ path += token.prefix + segment
+ }
+
+ return path
+ }
+}
+
+/**
+ * Escape a regular expression string.
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function escapeString (str) {
+ return str.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1')
+}
+
+/**
+ * Escape the capturing group by escaping special characters and meaning.
+ *
+ * @param {String} group
+ * @return {String}
+ */
+function escapeGroup (group) {
+ return group.replace(/([=!:$\/()])/g, '\\$1')
+}
+
+/**
+ * Attach the keys as a property of the regexp.
+ *
+ * @param {RegExp} re
+ * @param {Array} keys
+ * @return {RegExp}
+ */
+function attachKeys (re, keys) {
+ re.keys = keys
+ return re
+}
+
+/**
+ * Get the flags for a regexp from the options.
+ *
+ * @param {Object} options
+ * @return {String}
+ */
+function flags (options) {
+ return options.sensitive ? '' : 'i'
+}
+
+/**
+ * Pull out keys from a regexp.
+ *
+ * @param {RegExp} path
+ * @param {Array} keys
+ * @return {RegExp}
+ */
+function regexpToRegexp (path, keys) {
+ // Use a negative lookahead to match only capturing groups.
+ var groups = path.source.match(/\((?!\?)/g)
+
+ if (groups) {
+ for (var i = 0; i < groups.length; i++) {
+ keys.push({
+ name: i,
+ prefix: null,
+ delimiter: null,
+ optional: false,
+ repeat: false,
+ pattern: null
+ })
+ }
+ }
+
+ return attachKeys(path, keys)
+}
+
+/**
+ * Transform an array into a regexp.
+ *
+ * @param {Array} path
+ * @param {Array} keys
+ * @param {Object} options
+ * @return {RegExp}
+ */
+function arrayToRegexp (path, keys, options) {
+ var parts = []
+
+ for (var i = 0; i < path.length; i++) {
+ parts.push(pathToRegexp(path[i], keys, options).source)
+ }
+
+ var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))
+
+ return attachKeys(regexp, keys)
+}
+
+/**
+ * Create a path regexp from string input.
+ *
+ * @param {String} path
+ * @param {Array} keys
+ * @param {Object} options
+ * @return {RegExp}
+ */
+function stringToRegexp (path, keys, options) {
+ var tokens = parse(path)
+ var re = tokensToRegExp(tokens, options)
+
+ // Attach keys back to the regexp.
+ for (var i = 0; i < tokens.length; i++) {
+ if (typeof tokens[i] !== 'string') {
+ keys.push(tokens[i])
+ }
+ }
+
+ return attachKeys(re, keys)
+}
+
+/**
+ * Expose a function for taking tokens and returning a RegExp.
+ *
+ * @param {Array} tokens
+ * @param {Array} keys
+ * @param {Object} options
+ * @return {RegExp}
+ */
+function tokensToRegExp (tokens, options) {
+ options = options || {}
+
+ var strict = options.strict
+ var end = options.end !== false
+ var route = ''
+ var lastToken = tokens[tokens.length - 1]
+ var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken)
+
+ // Iterate over the tokens and create our regexp string.
+ for (var i = 0; i < tokens.length; i++) {
+ var token = tokens[i]
+
+ if (typeof token === 'string') {
+ route += escapeString(token)
+ } else {
+ var prefix = escapeString(token.prefix)
+ var capture = token.pattern
+
+ if (token.repeat) {
+ capture += '(?:' + prefix + capture + ')*'
+ }
+
+ if (token.optional) {
+ if (prefix) {
+ capture = '(?:' + prefix + '(' + capture + '))?'
+ } else {
+ capture = '(' + capture + ')?'
+ }
+ } else {
+ capture = prefix + '(' + capture + ')'
+ }
+
+ route += capture
+ }
+ }
+
+ // In non-strict mode we allow a slash at the end of match. If the path to
+ // match already ends with a slash, we remove it for consistency. The slash
+ // is valid at the end of a path match, not in the middle. This is important
+ // in non-ending mode, where "/test/" shouldn't match "/test//route".
+ if (!strict) {
+ route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?'
+ }
+
+ if (end) {
+ route += '$'
+ } else {
+ // In non-ending mode, we need the capturing groups to match as much as
+ // possible by using a positive lookahead to the end or next path segment.
+ route += strict && endsWithSlash ? '' : '(?=\\/|$)'
+ }
+
+ return new RegExp('^' + route, flags(options))
+}
+
+/**
+ * Normalize the given path string, returning a regular expression.
+ *
+ * An empty array can be passed in for the keys, which will hold the
+ * placeholder key descriptions. For example, using `/user/:id`, `keys` will
+ * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
+ *
+ * @param {(String|RegExp|Array)} path
+ * @param {Array} [keys]
+ * @param {Object} [options]
+ * @return {RegExp}
+ */
+function pathToRegexp (path, keys, options) {
+ keys = keys || []
+
+ if (!isarray(keys)) {
+ options = keys
+ keys = []
+ } else if (!options) {
+ options = {}
+ }
+
+ if (path instanceof RegExp) {
+ return regexpToRegexp(path, keys, options)
+ }
+
+ if (isarray(path)) {
+ return arrayToRegexp(path, keys, options)
+ }
+
+ return stringToRegexp(path, keys, options)
+}
+
+},{"isarray":4}],4:[function(require,module,exports){
+module.exports = Array.isArray || function (arr) {
+ return Object.prototype.toString.call(arr) == '[object Array]';
+};
+
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/paper-behaviors/.bower.json b/dashboard-ui/bower_components/paper-behaviors/.bower.json
index a6b333d335..2b04bf9f8a 100644
--- a/dashboard-ui/bower_components/paper-behaviors/.bower.json
+++ b/dashboard-ui/bower_components/paper-behaviors/.bower.json
@@ -45,7 +45,7 @@
"tag": "v1.0.11",
"commit": "e3c1ab0c72905b58fb4d9adc2921ea73b5c085a5"
},
- "_source": "git://github.com/polymerelements/paper-behaviors.git",
+ "_source": "git://github.com/PolymerElements/paper-behaviors.git",
"_target": "^1.0.0",
- "_originalSource": "polymerelements/paper-behaviors"
+ "_originalSource": "PolymerElements/paper-behaviors"
}
\ No newline at end of file
diff --git a/dashboard-ui/bower_components/paper-ripple/.bower.json b/dashboard-ui/bower_components/paper-ripple/.bower.json
index 2f654d71c6..157225ee71 100644
--- a/dashboard-ui/bower_components/paper-ripple/.bower.json
+++ b/dashboard-ui/bower_components/paper-ripple/.bower.json
@@ -32,14 +32,14 @@
"iron-test-helpers": "PolymerElements/iron-test-helpers#^1.0.0"
},
"ignore": [],
- "homepage": "https://github.com/polymerelements/paper-ripple",
+ "homepage": "https://github.com/PolymerElements/paper-ripple",
"_release": "1.0.5",
"_resolution": {
"type": "version",
"tag": "v1.0.5",
"commit": "d72e7a9a8ab518b901ed18dde492df3b87a93be5"
},
- "_source": "git://github.com/polymerelements/paper-ripple.git",
+ "_source": "git://github.com/PolymerElements/paper-ripple.git",
"_target": "^1.0.0",
- "_originalSource": "polymerelements/paper-ripple"
+ "_originalSource": "PolymerElements/paper-ripple"
}
\ No newline at end of file