blob: f5f114cf4cbe1761ece34c0ceb287c40341f1b14 [file] [edit]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Namespace for the Camera app.
*/
var camera = camera || {};
/**
* Creates the Camera App main object.
* @constructor
*/
camera.Camera = function() {
/**
* @type {camera.Camera.Context}
* @private
*/
this.context_ = new camera.Camera.Context(
this.isUIAnimating_.bind(this),
this.onError_.bind(this),
this.onErrorRecovered_.bind(this));
/**
* @type {Array.<camera.ViewsStack>}
* @private
*/
this.viewsStack_ = new camera.Camera.ViewsStack();
/**
* @type {camera.Router}
* @private
*/
this.router_ = new camera.Router(
this.navigateById_.bind(this),
this.viewsStack_.pop.bind(this.viewsStack_));
/**
* @type {camera.views.Camera}
* @private
*/
this.cameraView_ = new camera.views.Camera(this.context_, this.router_);
/**
* @type {camera.views.Album}
* @private
*/
this.albumView_ = new camera.views.Album(this.context_, this.router_);
/**
* @type {camera.views.Browser}
* @private
*/
this.browserView_ = new camera.views.Browser(this.context_, this.router_);
/**
* @type {camera.views.Dialog}
* @private
*/
this.dialogView_ = new camera.views.Dialog(this.context_, this.router_);
/**
* @type {?number}
* @private
*/
this.resizingTimer_ = null;
/**
* @type {camera.util.TooltipManager}
* @private
*/
this.tooltipManager_ = new camera.util.TooltipManager();
// End of properties. Seal the object.
Object.seal(this);
// Handle key presses to make the Camera app accessible via the keyboard.
document.body.addEventListener('keydown', this.onKeyPressed_.bind(this));
// Handle window decoration buttons.
document.querySelector('#maximize-button').addEventListener('click',
this.onMaximizeClicked_.bind(this));
document.querySelector('#close-button').addEventListener('click',
this.onCloseClicked_.bind(this));
// Handle window resize.
window.addEventListener('resize', this.onWindowResize_.bind(this));
// Animate the error icon.
document.querySelector('#error .icon').addEventListener('mousedown',
this.onErrorIconClicked_.bind(this));
document.querySelector('#error .icon').addEventListener('touchstart',
this.onErrorIconClicked_.bind(this));
// Set the localized window title.
document.title = chrome.i18n.getMessage('name');
};
/**
* Creates context for the views.
*
* @param {function():boolean} isUIAnimating Checks if the UI is animating.
* @param {function(string, string, opt_string)} onError Callback to be called,
* when an error occurs. Arguments: identifier, first line, second line.
* @param {function(string)} onErrorRecovered Callback to be called,
* when the error goes away. The argument is the error id.
* @constructor
*/
camera.Camera.Context = function(isUIAnimating, onError, onErrorRecovered) {
camera.View.Context.call(this);
/**
* @type {boolean}
*/
this.resizing = false;
/**
* @type {boolean}
*/
this.hasError = false;
/**
* @type {function():boolean}
*/
this.isUIAnimating = isUIAnimating;
/**
* @type {function(string, string, string)}
*/
this.onError = onError;
/**
* @type {function(string)}
*/
this.onErrorRecovered = onErrorRecovered;
// End of properties. Seal the object.
Object.seal(this);
};
camera.Camera.Context.prototype = {
__proto__: camera.View.Context.prototype
};
/**
* Creates a stack of views.
* @constructor
*/
camera.Camera.ViewsStack = function() {
/**
* Stack with the views as well as return callbacks.
* @type {Array.<Object>}
* @private
*/
this.stack_ = [];
// No more properties. Seal the object.
Object.seal(this);
};
camera.Camera.ViewsStack.prototype = {
get current() {
return this.stack_.length ? this.stack_[this.stack_.length - 1].view : null;
}
};
/**
* Adds the view on the stack and hence makes it the current one. Optionally,
* passes the arguments to the view.
*
* @param {camera.View} view View to be pushed on top of the stack.
* @param {Object=} opt_arguments Optional arguments.
* @param {function(Object=)} opt_callback Optional result callback to be called
* when the view is closed.
*/
camera.Camera.ViewsStack.prototype.push = function(
view, opt_arguments, opt_callback) {
if (this.current)
this.current.inactivate();
this.stack_.push({
view: view,
callback: opt_callback || function(result) {}
});
view.enter(opt_arguments);
view.activate();
};
/**
* Removes the current view from the stack and hence makes the previous one
* the current one. Calls the callback passed to the previous view's navigate()
* method with the result.
*
* @param {Object=} opt_result Optional result. If not passed, then undefined
* will be passed to the callback.
*/
camera.Camera.ViewsStack.prototype.pop = function(opt_result) {
var entry = this.stack_.pop();
entry.view.inactivate();
entry.view.leave();
this.current.activate();
if (entry.callback)
entry.callback(opt_result);
};
camera.Camera.prototype = {
get currentView() {
return this.viewsStack_.current;
},
get cameraView() {
return this.cameraView_;
},
get albumView() {
return this.albumView_;
},
get browserView() {
return this.browserView_;
},
get dialogView() {
return this.dialogView_;
}
};
/**
* Starts the app by initializing views and showing the camera view.
*/
camera.Camera.prototype.start = function() {
var queue = new camera.util.Queue();
// Initialize all views.
queue.run(this.cameraView_.initialize.bind(this.cameraView_));
queue.run(this.albumView_.initialize.bind(this.albumView_));
queue.run(this.browserView_.initialize.bind(this.browserView_));
queue.run(this.dialogView_.initialize.bind(this.dialogView_));
// Display the camera view after initializing.
queue.run(function(callback) {
this.tooltipManager_.initialize();
this.viewsStack_.push(this.cameraView_);
camera.util.makeElementsUnfocusableByMouse();
camera.util.setAriaAttributes();
callback();
}.bind(this));
};
/**
* Switches the view using a router's view identifier.
*
* @param {camera.Router.ViewIdentifier} viewIdentifier View identifier.
* @param {Object=} opt_arguments Optional arguments for the view.
* @param {function(Object=)} opt_callback Optional result callback to be called
* when the view is closed.
* @private
*/
camera.Camera.prototype.navigateById_ = function(
viewIdentifier, opt_arguments, opt_callback) {
switch (viewIdentifier) {
case camera.Router.ViewIdentifier.CAMERA:
this.viewsStack_.push(this.cameraView_, opt_arguments, opt_callback);
break;
case camera.Router.ViewIdentifier.ALBUM:
this.viewsStack_.push(this.albumView_, opt_arguments, opt_callback);
break;
case camera.Router.ViewIdentifier.BROWSER:
this.viewsStack_.push(this.browserView_, opt_arguments, opt_callback);
break;
case camera.Router.ViewIdentifier.DIALOG:
this.viewsStack_.push(this.dialogView_, opt_arguments, opt_callback);
break;
}
};
/**
* Handles resizing of the window.
* @private
*/
camera.Camera.prototype.onWindowResize_ = function() {
// Suspend capturing while resizing for smoother UI.
this.context_.resizing = true;
if (this.resizingTimer_) {
clearTimeout(this.resizingTimer_);
this.resizingTimer_ = null;
}
this.resizingTimer_ = setTimeout(function() {
this.resizingTimer_ = null;
this.context_.resizing = false;
}.bind(this), 100);
if (this.currentView)
this.currentView.onResize();
};
/**
* Handles clicking on the error icon.
* @private
*/
camera.Camera.prototype.onErrorIconClicked_ = function() {
var icon = document.querySelector('#error .icon');
icon.classList.remove('animate');
setTimeout(function() {
icon.classList.add('animate');
}, 0);
};
/**
* Handles pressed keys.
* @param {Event} event Key press event.
* @private
*/
camera.Camera.prototype.onKeyPressed_ = function(event) {
if (this.context_.hasError)
return;
this.currentView.onKeyPressed(event);
};
/**
* Handles clicking on the toggle maximization button.
* @private
*/
camera.Camera.prototype.onMaximizeClicked_ = function() {
if (chrome.app.window.current().isMaximized())
chrome.app.window.current().restore();
else
chrome.app.window.current().maximize();
};
/**
* Handles clicking on the close application button.
* @private
*/
camera.Camera.prototype.onCloseClicked_ = function() {
chrome.app.window.current().close();
};
/**
* Shows an error message.
*
* @param {string} identifier Identifier of the error.
* @param {string} message Message for the error.
* @param {string=} opt_hint Optional hint for the error message.
* @private
*/
camera.Camera.prototype.onError_ = function(identifier, message, opt_hint) {
document.body.classList.add('has-error');
this.context_.hasError = true;
document.querySelector('#error-msg').textContent = message;
document.querySelector('#error-msg-hint').textContent = opt_hint || '';
};
/**
* Checks if any of UI elements are animating.
* @return {boolean} True if animating, false otherwise.
* @private
*/
camera.Camera.prototype.isUIAnimating_ = function() {
return this.tooltipManager_.animating;
};
/**
* Removes the error message when an error goes away.
* @param {string} identifier Identifier of the error.
* @private
*/
camera.Camera.prototype.onErrorRecovered_ = function(identifier) {
// TODO(mtomasz): Implement identifiers handling in case of multiple
// error messages at once.
this.context_.hasError = false;
document.body.classList.remove('has-error');
};
/**
* @type {camera.Camera} Singleton of the Camera object.
* @private
*/
camera.Camera.instance_ = null;
/**
* Returns the singleton instance of the Camera class.
* @return {camera.Camera} Camera object.
*/
camera.Camera.getInstance = function() {
if (!camera.Camera.instance_)
camera.Camera.instance_ = new camera.Camera();
return camera.Camera.instance_;
};
/**
* Creates the Camera object and starts screen capturing.
*/
document.addEventListener('DOMContentLoaded', function() {
camera.Camera.getInstance().start();
chrome.app.window.current().show();
});