blob: 3bd8ef364a8fce48a5b1e12bc9f44fc5af32c888 [file] [log] [blame]
// 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 uuid = require('uuid');
var mercury = require('mercury');
var vanadium = require('vanadium');
var addDelegatedEvents = require('./lib/mercury/add-delegated-events');
var onboarding = require('./onboarding');
var router = require('./router');
var registerItemPlugins = require('./item-plugins/register-plugins');
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');
var errorRoute = require('./routes/error');
var browseRoute = require('./routes/browse');
var log = require('./lib/log')('app');
var browseComponent = browse();
var errorComponent = error();
var debugComponent = debug();
var helpComponent = help();
var viewportComponent = viewport();
var userAccountComponent = userAccount();
// 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('')
}),
/*
* Vanadium Namespace Browsing related state
*/
browse: browseComponent.state,
/*
* Vanadium Namespace Help related state
*/
help: helpComponent.state,
/*
* State of the viewport component
*/
viewport: viewportComponent.state,
/*
* Boolean indicating that app has been initialized.
* Used to show/hide splash screen.
* @type {boolean}
*/
initialized: mercury.value(false),
/*
* State of the error component
*/
error: errorComponent.state,
/*
* State for user account component
*/
userAccount: userAccountComponent.state,
/*
* Internal debugging state
*/
debug: debugComponent.state,
/*
* Boolean indicating whether we are in demo mode.
* In demo mode, a sample-world is created and user is redirected to it as
* the starting namespace.
* @type {boolean}
*/
demo: mercury.value(false),
});
// To level events
var events = mercury.input([
/*
* Navigation related events
*/
'navigation',
/*
* Vanadium Namespace Browsing related events
*/
'browse',
/*
* Vanadium Namespace Help related events
*/
'help',
/*
* Events of the viewport component
*/
'viewport'
]);
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',
/*
* 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;
events.viewport = viewportComponent.events;
// Wire Events
wireEvents();
// Register the plugins
registerItemPlugins();
// Load the user's saved state, followed by other dependencies.
loadUserState().then(function() {
// Start the router which will register the application routes
router(state, events);
// Initialize Vanadium
initVanadium();
});
// Debugging related exports
exportDebugging();
// Render the app
var render = function(state) {
return viewport.render(state, events);
};
mercury.app(document.body, state, render);
// Add additional events that mercury's delegator should listenTo.
addDelegatedEvents(['core-overlay-open-completed',
'down', 'up', 'tap', 'openchange', 'activate', 'delete-item'
]);
function wireEvents() {
// TODO(aghassemi): Make these events global.
// Hook up external browse events.
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) {
var msg = err.toString();
if (err.message) {
msg = err.message;
}
state.error.message.set(msg);
events.navigation.navigate({
path: errorRoute.createUrl(),
skipHistoryPush: true
});
}
/*
* Given a toast object, let the viewport render it.
* Toasts are given a unique key to ensure Mercury draws 1 toast per event.
*/
function onToast(toast) {
toast.key = uuid.v4();
state.viewport.toasts.push(toast);
}
/*
* Export some debugging methods at global level
*/
function exportDebugging() {
window.log = require('./lib/log');
window.enableContinuousRendering =
debug.enableContinuousRendering.bind(null, state.debug);
}
/*
* Load any of the user's application state.
* Note: This promise will always resolve.
*/
function loadUserState() {
var loads = [];
// Set the initial namespace for the user. This guarantees that regardless
// of starting route, that the user continues where they last left off.
loads.push(stateService.loadNamespace().then(function(namespace) {
state.browse.namespace.set(namespace);
}));
// Fetch the most recently used side panel width.
loads.push(stateService.loadSidePanelWidth().then(function(width) {
state.browse.sidePanelWidth.set(width);
}));
// Fetch the most recently used browse view type.
loads.push(stateService.loadBrowseViewType().then(function(view) {
state.browse.views.viewType.set(view);
}));
return Promise.all(loads).catch(function() {});
}
/*
* Initialized vanadium and sets appropriate messages on the splash screen
*/
function initVanadium() {
viewport.setSplashMessage('Initializing Vanadium...');
namespaceService.initVanadium().then(function(vruntime) {
vruntime.once('crash', onVanadiumCrash);
// Onboarding Hook for new users (async). Currently does not block.
onboarding(vruntime, state);
window.addEventListener('beforeunload', function() {
log.debug('closing runtime');
vruntime.close();
});
if (state.demo()) {
return intializeDemo();
} else {
return initialized();
}
/*
* Called when Vanadium initialization is complete
*/
function initialized() {
viewport.setSplashMessage('Initialized');
state.initialized.set(true);
}
/*
* Initialized the demo mode
*/
function intializeDemo() {
viewport.setSplashMessage('Initializing Sample World Demo...');
var sampleWorldDirectory = '';
return sampleWorld.getRootedName().then(function(name) {
sampleWorldDirectory = name;
return sampleWorld.create(sampleWorldDirectory);
}).then(function() {
initialized();
// Navigate to the home directory
events.navigation.navigate({
path: browseRoute.createUrl(state.browse, {
namespace: sampleWorldDirectory,
viewType: 'tree'
})
});
});
}
}).catch(function(err) {
if (err instanceof vanadium.verror.ExtensionNotInstalledError) {
vanadium.extension.promptUserToInstallExtension();
} else {
var isError = true;
viewport.setSplashMessage(err.toString(), isError);
log.error(err);
}
});
}
/*
* Handler if Vanadium runtime crashes
*/
function onVanadiumCrash(crashErr) {
events.browse.error(crashErr);
}