blob: 10ba74bb3e5f71a6a3e88661d9ec6d661df25391 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview First run UI.
*/
<include src="step.js">
// Transitions durations.
/** @const */ var DEFAULT_TRANSITION_DURATION_MS = 400;
/** @const */ var BG_TRANSITION_DURATION_MS = 800;
/**
* Changes visibility of element with animated transition.
* @param {Element} element Element which visibility should be changed.
* @param {boolean} visible Whether element should be visible after transition.
* @param {number=} opt_transitionDuration Time length of transition in
* milliseconds. Default value is DEFAULT_TRANSITION_DURATION_MS.
* @param {function()=} opt_onFinished Called after transition has finished.
*/
function changeVisibility(
element, visible, opt_transitionDuration, opt_onFinished) {
var classes = element.classList;
// If target visibility is the same as current element visibility.
if (classes.contains('transparent') === !visible) {
if (opt_onFinished)
opt_onFinished();
return;
}
var transitionDuration = (opt_transitionDuration === undefined) ?
cr.FirstRun.getDefaultTransitionDuration() : opt_transitionDuration;
var style = element.style;
var oldDurationValue = style.getPropertyValue('transition-duration');
style.setProperty('transition-duration', transitionDuration + 'ms');
var transition = visible ? 'show-animated' : 'hide-animated';
classes.add(transition);
classes.toggle('transparent');
element.addEventListener('webkitTransitionEnd', function f() {
element.removeEventListener('webkitTransitionEnd', f);
classes.remove(transition);
if (oldDurationValue)
style.setProperty('transition-duration', oldDurationValue);
else
style.removeProperty('transition-duration');
if (opt_onFinished)
opt_onFinished();
});
ensureTransitionEndEvent(element, transitionDuration);
}
cr.define('cr.FirstRun', function() {
return {
// Whether animated transitions are enabled.
transitionsEnabled_: false,
// SVG element representing UI background.
background_: null,
// Container for background.
backgroundContainer_: null,
// Mask element describing transparent "holes" in background.
mask_: null,
// Pattern used for creating rectangular holes.
rectangularHolePattern_: null,
// Pattern used for creating round holes.
roundHolePattern_: null,
// Dictionary keeping all available tutorial steps by their names.
steps_: {},
// Element representing step currently shown for user.
currentStep_: null,
/**
* Initializes internal structures and preparing steps.
*/
initialize: function() {
disableTextSelectAndDrag();
this.transitionsEnabled_ = loadTimeData.getBoolean('transitionsEnabled');
this.background_ = $('background');
this.backgroundContainer_ = $('background-container');
this.mask_ = $('mask');
this.rectangularHolePattern_ = $('rectangular-hole-pattern');
this.rectangularHolePattern_.removeAttribute('id');
this.roundHolePattern_ = $('round-hole-pattern');
this.roundHolePattern_.removeAttribute('id');
var stepElements = document.getElementsByClassName('step');
for (var i = 0; i < stepElements.length; ++i) {
var step = stepElements[i];
cr.FirstRun.DecorateStep(step);
this.steps_[step.getName()] = step;
}
this.setBackgroundVisible(true, function() {
chrome.send('initialized');
});
},
/**
* Hides all elements and background.
*/
finalize: function() {
// At first we hide holes (job 1) and current step (job 2) simultaneously,
// then background.
var jobsLeft = 2;
var onJobDone = function() {
--jobsLeft;
if (jobsLeft)
return;
this.setBackgroundVisible(false, function() {
chrome.send('finalized');
});
}.bind(this);
this.doHideCurrentStep_(function(name) {
if (name)
chrome.send('stepHidden', [name]);
onJobDone();
});
this.removeHoles(onJobDone);
},
/**
* Adds transparent rectangular hole to background.
* @param {number} x X coordinate of top-left corner of hole.
* @param {number} y Y coordinate of top-left corner of hole.
* @param {number} widht Width of hole.
* @param {number} height Height of hole.
*/
addRectangularHole: function(x, y, width, height) {
var hole = this.rectangularHolePattern_.cloneNode();
hole.setAttribute('x', x);
hole.setAttribute('y', y);
hole.setAttribute('width', width);
hole.setAttribute('height', height);
this.mask_.appendChild(hole);
setTimeout(function() {
changeVisibility(hole, true);
}, 0);
},
/**
* Adds transparent round hole to background.
* @param {number} x X coordinate of circle center.
* @param {number} y Y coordinate of circle center.
* @param {number} radius Radius of circle.
*/
addRoundHole: function(x, y, radius) {
var hole = this.roundHolePattern_.cloneNode();
hole.setAttribute('cx', x);
hole.setAttribute('cy', y);
hole.setAttribute('r', radius);
this.mask_.appendChild(hole);
setTimeout(function() {
changeVisibility(hole, true);
}, 0);
},
/**
* Removes all holes previously added by |addHole|.
* @param {function=} opt_onHolesRemoved Called after all holes have been
* hidden.
*/
removeHoles: function(opt_onHolesRemoved) {
var mask = this.mask_;
var holes = Array.prototype.slice.call(
mask.getElementsByClassName('hole'));
var holesLeft = holes.length;
if (!holesLeft) {
if (opt_onHolesRemoved)
opt_onHolesRemoved();
return;
}
holes.forEach(function(hole) {
changeVisibility(hole, false, this.getDefaultTransitionDuration(),
function() {
mask.removeChild(hole);
--holesLeft;
if (!holesLeft && opt_onHolesRemoved)
opt_onHolesRemoved();
});
}.bind(this));
},
/**
* Hides currently active step and notifies chrome after step has been
* hidden.
*/
hideCurrentStep: function() {
assert(this.currentStep_);
this.doHideCurrentStep_(function(name) {
chrome.send('stepHidden', [name]);
});
},
/**
* Hides currently active step.
* @param {function(string)=} opt_onStepHidden Called after step has been
* hidden.
*/
doHideCurrentStep_: function(opt_onStepHidden) {
if (!this.currentStep_) {
if (opt_onStepHidden)
opt_onStepHidden();
return;
}
var name = this.currentStep_.getName();
this.currentStep_.hide(true, function() {
this.currentStep_ = null;
if (opt_onStepHidden)
opt_onStepHidden(name);
}.bind(this));
},
/**
* Shows step with given name in given position.
* @param {string} name Name of step.
* @param {object} position Optional parameter with optional fields |top|,
* |right|, |bottom|, |left| used for step positioning.
* @param {Array} pointWithOffset Optional parameter for positioning
* bubble. Contains [x, y, offset], where (x, y) - point to which bubble
* points, offset - distance between arrow and point.
*/
showStep: function(name, position, pointWithOffset) {
assert(!this.currentStep_);
if (!this.steps_.hasOwnProperty(name))
throw Error('Step "' + name + '" not found.');
var step = this.steps_[name];
if (position)
step.setPosition(position);
if (pointWithOffset)
step.setPointsTo(pointWithOffset.slice(0, 2), pointWithOffset[2]);
step.show(true, function(step) {
step.focusDefaultControl();
this.currentStep_ = step;
chrome.send('stepShown', [name]);
}.bind(this));
},
/**
* Sets visibility of the background.
* @param {boolean} visibility Whether background should be visible.
* @param {function()=} opt_onCompletion Called after visibility has
* changed.
*/
setBackgroundVisible: function(visible, opt_onCompletion) {
changeVisibility(this.backgroundContainer_, visible,
this.getBackgroundTransitionDuration(), opt_onCompletion);
},
/**
* Returns default duration of animated transitions, in ms.
*/
getDefaultTransitionDuration: function() {
return this.transitionsEnabled_ ? DEFAULT_TRANSITION_DURATION_MS : 0;
},
/**
* Returns duration of transitions of background shield, in ms.
*/
getBackgroundTransitionDuration: function() {
return this.transitionsEnabled_ ? BG_TRANSITION_DURATION_MS : 0;
}
};
});
/**
* Initializes UI.
*/
window.onload = function() {
cr.FirstRun.initialize();
};