blob: 4cd87fdf9375394cc440c14fe2a2cf4d5ec5da2d [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.
/**
* Prototype for first-run tutorial steps.
*/
cr.define('cr.FirstRun', function() {
var Step = cr.ui.define('div');
Step.prototype = {
__proto__: HTMLDivElement.prototype,
// Name of step.
name_: null,
// Button leading to next tutorial step.
nextButton_: null,
// Whether screen is shown.
isShown_: false,
decorate: function() {
this.name_ = this.getAttribute('id');
this.nextButton_ = this.getElementsByClassName('next-button')[0];
if (!this.nextButton_)
throw Error('Next button not found.');
this.nextButton_.addEventListener('click', (function(e) {
chrome.send('nextButtonClicked', [this.getName()]);
e.stopPropagation();
}).bind(this));
},
/**
* Returns name of the string.
*/
getName: function() {
return this.name_;
},
/**
* Hides the step.
*/
hide: function() {
this.style.setProperty('display', 'none');
this.isShown_ = false;
},
/**
* Shows the step.
*/
show: function() {
this.style.setProperty('display', 'inline-block');
this.isShown_ = true;
},
/**
* Sets position of the step.
* @param {object} position Parameter with optional fields |top|,
* |right|, |bottom|, |left| holding corresponding offsets.
*/
setPosition: function(position) {
var style = this.style;
['top', 'right', 'bottom', 'left'].forEach(function(property) {
if (position.hasOwnProperty(property))
style.setProperty(property, position[property] + 'px');
});
}
};
var Bubble = cr.ui.define('div');
// Styles of .step which are used for arrow styling.
var ARROW_STYLES = [
'points-up',
'points-left',
'points-down',
'points-right',
'top',
'left',
'bottom',
'right'
];
var DISTANCE_TO_POINTEE = 10;
var MINIMAL_SCREEN_OFFSET = 10;
var ARROW_LENGTH = 6; // Keep synced with .arrow border-width.
Bubble.prototype = {
__proto__: Step.prototype,
// Element displaying arrow.
arrow_: null,
// Unit vector directed along the bubble arrow.
direction_: null,
/**
* In addition to base class 'decorate' this method creates arrow and
* sets some properties related to arrow.
*/
decorate: function() {
Step.prototype.decorate.call(this);
this.arrow_ = document.createElement('div');
this.arrow_.classList.add('arrow');
this.appendChild(this.arrow_);
ARROW_STYLES.forEach(function(style) {
if (!this.classList.contains(style))
return;
// Changing right to left in RTL case.
if (document.documentElement.getAttribute('dir') == 'rtl') {
style = style.replace(/right|left/, function(match) {
return (match == 'right') ? 'left' : 'right';
});
}
this.arrow_.classList.add(style);
}.bind(this));
var list = this.arrow_.classList;
if (list.contains('points-up'))
this.direction_ = [0, -1];
else if (list.contains('points-right'))
this.direction_ = [1, 0];
else if (list.contains('points-down'))
this.direction_ = [0, 1];
else // list.contains('points-left')
this.direction_ = [-1, 0];
},
/**
* Sets position of bubble in such a maner that bubble's arrow points to
* given point.
* @param {Array} point Bubble arrow should point to this point after
* positioning. |point| has format [x, y].
* @param {offset} number Additional offset from |point|.
*/
setPointsTo: function(point, offset) {
var shouldShowBefore = !this.isShown_;
// "Showing" bubble in order to make offset* methods work.
if (shouldShowBefore) {
this.style.setProperty('opacity', '0');
this.show();
}
var arrow = [this.arrow_.offsetLeft + this.arrow_.offsetWidth / 2,
this.arrow_.offsetTop + this.arrow_.offsetHeight / 2];
var totalOffset = DISTANCE_TO_POINTEE + offset;
var left = point[0] - totalOffset * this.direction_[0] - arrow[0];
var top = point[1] - totalOffset * this.direction_[1] - arrow[1];
// Force bubble to be inside screen.
if (this.arrow_.classList.contains('points-up') ||
this.arrow_.classList.contains('points-down')) {
left = Math.max(left, MINIMAL_SCREEN_OFFSET);
left = Math.min(left, document.body.offsetWidth - this.offsetWidth -
MINIMAL_SCREEN_OFFSET);
}
if (this.arrow_.classList.contains('points-left') ||
this.arrow_.classList.contains('points-right')) {
top = Math.max(top, MINIMAL_SCREEN_OFFSET);
top = Math.min(top, document.body.offsetHeight - this.offsetHeight -
MINIMAL_SCREEN_OFFSET);
}
this.style.setProperty('left', left + 'px');
this.style.setProperty('top', top + 'px');
if (shouldShowBefore) {
this.hide();
this.style.setProperty('opacity', '1');
}
},
/**
* Sets position of bubble. Overrides Step.setPosition to adjust offsets
* in case if its direction is the same as arrow's direction.
* @param {object} position Parameter with optional fields |top|,
* |right|, |bottom|, |left| holding corresponding offsets.
*/
setPosition: function(position) {
var arrow = this.arrow_;
// Increasing offset if it's from side where bubble points to.
[['top', 'points-up'],
['right', 'points-right'],
['bottom', 'points-down'],
['left', 'points-left']].forEach(function(mapping) {
if (position.hasOwnProperty(mapping[0]) &&
arrow.classList.contains(mapping[1])) {
position[mapping[0]] += ARROW_LENGTH + DISTANCE_TO_POINTEE;
}
});
Step.prototype.setPosition.call(this, position);
}
};
var HelpStep = cr.ui.define('div');
HelpStep.prototype = {
__proto__: Bubble.prototype,
decorate: function() {
Bubble.prototype.decorate.call(this);
var helpButton = this.getElementsByClassName('help-button')[0];
helpButton.addEventListener('click', function(e) {
chrome.send('helpButtonClicked');
e.stopPropagation();
});
},
};
var DecorateStep = function(el) {
if (el.id == 'help')
HelpStep.decorate(el);
else if (el.classList.contains('bubble'))
Bubble.decorate(el);
else
Step.decorate(el);
};
return {DecorateStep: DecorateStep};
});