Home | History | Annotate | Download | only in first_run
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview First run UI.
      7  */
      8 
      9 <include src="step.js">
     10 
     11 // Transitions durations.
     12 /** @const  */ var DEFAULT_TRANSITION_DURATION_MS = 400;
     13 /** @const  */ var BG_TRANSITION_DURATION_MS = 800;
     14 
     15 /**
     16  * Changes visibility of element with animated transition.
     17  * @param {Element} element Element which visibility should be changed.
     18  * @param {boolean} visible Whether element should be visible after transition.
     19  * @param {number=} opt_transitionDuration Time length of transition in
     20  *     milliseconds. Default value is DEFAULT_TRANSITION_DURATION_MS.
     21  * @param {function()=} opt_onFinished Called after transition has finished.
     22  */
     23 function changeVisibility(
     24     element, visible, opt_transitionDuration, opt_onFinished) {
     25   var classes = element.classList;
     26   // If target visibility is the same as current element visibility.
     27   if (classes.contains('transparent') === !visible) {
     28     if (opt_onFinished)
     29       opt_onFinished();
     30     return;
     31   }
     32   var transitionDuration = (opt_transitionDuration === undefined) ?
     33     cr.FirstRun.getDefaultTransitionDuration() : opt_transitionDuration;
     34   var style = element.style;
     35   var oldDurationValue = style.getPropertyValue('transition-duration');
     36   style.setProperty('transition-duration', transitionDuration + 'ms');
     37   var transition = visible ? 'show-animated' : 'hide-animated';
     38   classes.add(transition);
     39   classes.toggle('transparent');
     40   element.addEventListener('webkitTransitionEnd', function f() {
     41     element.removeEventListener('webkitTransitionEnd', f);
     42     classes.remove(transition);
     43     if (oldDurationValue)
     44       style.setProperty('transition-duration', oldDurationValue);
     45     else
     46       style.removeProperty('transition-duration');
     47     if (opt_onFinished)
     48       opt_onFinished();
     49   });
     50   ensureTransitionEndEvent(element, transitionDuration);
     51 }
     52 
     53 cr.define('cr.FirstRun', function() {
     54   return {
     55     // Whether animated transitions are enabled.
     56     transitionsEnabled_: false,
     57 
     58     // SVG element representing UI background.
     59     background_: null,
     60 
     61     // Container for background.
     62     backgroundContainer_: null,
     63 
     64     // Mask element describing transparent "holes" in background.
     65     mask_: null,
     66 
     67     // Pattern used for creating rectangular holes.
     68     rectangularHolePattern_: null,
     69 
     70     // Pattern used for creating round holes.
     71     roundHolePattern_: null,
     72 
     73     // Dictionary keeping all available tutorial steps by their names.
     74     steps_: {},
     75 
     76     // Element representing step currently shown for user.
     77     currentStep_: null,
     78 
     79     /**
     80      * Initializes internal structures and preparing steps.
     81      */
     82     initialize: function() {
     83       disableTextSelectAndDrag();
     84       this.transitionsEnabled_ = loadTimeData.getBoolean('transitionsEnabled');
     85       this.background_ = $('background');
     86       this.backgroundContainer_ = $('background-container');
     87       this.mask_ = $('mask');
     88       this.rectangularHolePattern_ = $('rectangular-hole-pattern');
     89       this.rectangularHolePattern_.removeAttribute('id');
     90       this.roundHolePattern_ = $('round-hole-pattern');
     91       this.roundHolePattern_.removeAttribute('id');
     92       var stepElements = document.getElementsByClassName('step');
     93       for (var i = 0; i < stepElements.length; ++i) {
     94         var step = stepElements[i];
     95         cr.FirstRun.DecorateStep(step);
     96         this.steps_[step.getName()] = step;
     97       }
     98       this.setBackgroundVisible(true, function() {
     99         chrome.send('initialized');
    100       });
    101     },
    102 
    103     /**
    104      * Hides all elements and background.
    105      */
    106     finalize: function() {
    107       // At first we hide holes (job 1) and current step (job 2) simultaneously,
    108       // then background.
    109       var jobsLeft = 2;
    110       var onJobDone = function() {
    111         --jobsLeft;
    112         if (jobsLeft)
    113           return;
    114         this.setBackgroundVisible(false, function() {
    115           chrome.send('finalized');
    116         });
    117       }.bind(this);
    118       this.doHideCurrentStep_(function(name) {
    119         if (name)
    120           chrome.send('stepHidden', [name]);
    121         onJobDone();
    122       });
    123       this.removeHoles(onJobDone);
    124     },
    125 
    126     /**
    127      * Adds transparent rectangular hole to background.
    128      * @param {number} x X coordinate of top-left corner of hole.
    129      * @param {number} y Y coordinate of top-left corner of hole.
    130      * @param {number} widht Width of hole.
    131      * @param {number} height Height of hole.
    132      */
    133     addRectangularHole: function(x, y, width, height) {
    134       var hole = this.rectangularHolePattern_.cloneNode();
    135       hole.setAttribute('x', x);
    136       hole.setAttribute('y', y);
    137       hole.setAttribute('width', width);
    138       hole.setAttribute('height', height);
    139       this.mask_.appendChild(hole);
    140       setTimeout(function() {
    141         changeVisibility(hole, true);
    142       }, 0);
    143     },
    144 
    145     /**
    146      * Adds transparent round hole to background.
    147      * @param {number} x X coordinate of circle center.
    148      * @param {number} y Y coordinate of circle center.
    149      * @param {number} radius Radius of circle.
    150      */
    151     addRoundHole: function(x, y, radius) {
    152       var hole = this.roundHolePattern_.cloneNode();
    153       hole.setAttribute('cx', x);
    154       hole.setAttribute('cy', y);
    155       hole.setAttribute('r', radius);
    156       this.mask_.appendChild(hole);
    157       setTimeout(function() {
    158         changeVisibility(hole, true);
    159       }, 0);
    160     },
    161 
    162     /**
    163      * Removes all holes previously added by |addHole|.
    164      * @param {function=} opt_onHolesRemoved Called after all holes have been
    165      *     hidden.
    166      */
    167     removeHoles: function(opt_onHolesRemoved) {
    168       var mask = this.mask_;
    169       var holes = Array.prototype.slice.call(
    170           mask.getElementsByClassName('hole'));
    171       var holesLeft = holes.length;
    172       if (!holesLeft) {
    173         if (opt_onHolesRemoved)
    174           opt_onHolesRemoved();
    175         return;
    176       }
    177       holes.forEach(function(hole) {
    178         changeVisibility(hole, false, this.getDefaultTransitionDuration(),
    179             function() {
    180               mask.removeChild(hole);
    181               --holesLeft;
    182               if (!holesLeft && opt_onHolesRemoved)
    183                 opt_onHolesRemoved();
    184             });
    185       }.bind(this));
    186     },
    187 
    188     /**
    189      * Hides currently active step and notifies chrome after step has been
    190      * hidden.
    191      */
    192     hideCurrentStep: function() {
    193       assert(this.currentStep_);
    194       this.doHideCurrentStep_(function(name) {
    195         chrome.send('stepHidden', [name]);
    196       });
    197     },
    198 
    199     /**
    200      * Hides currently active step.
    201      * @param {function(string)=} opt_onStepHidden Called after step has been
    202      *     hidden.
    203      */
    204     doHideCurrentStep_: function(opt_onStepHidden) {
    205       if (!this.currentStep_) {
    206         if (opt_onStepHidden)
    207           opt_onStepHidden();
    208         return;
    209       }
    210       var name = this.currentStep_.getName();
    211       this.currentStep_.hide(true, function() {
    212         this.currentStep_ = null;
    213         if (opt_onStepHidden)
    214           opt_onStepHidden(name);
    215       }.bind(this));
    216     },
    217 
    218     /**
    219      * Shows step with given name in given position.
    220      * @param {string} name Name of step.
    221      * @param {object} position Optional parameter with optional fields |top|,
    222      *     |right|, |bottom|, |left| used for step positioning.
    223      * @param {Array} pointWithOffset Optional parameter for positioning
    224      *     bubble. Contains [x, y, offset], where (x, y) - point to which bubble
    225      *     points, offset - distance between arrow and point.
    226      */
    227     showStep: function(name, position, pointWithOffset) {
    228       assert(!this.currentStep_);
    229       if (!this.steps_.hasOwnProperty(name))
    230         throw Error('Step "' + name + '" not found.');
    231       var step = this.steps_[name];
    232       if (position)
    233         step.setPosition(position);
    234       if (pointWithOffset)
    235         step.setPointsTo(pointWithOffset.slice(0, 2), pointWithOffset[2]);
    236       step.show(true, function(step) {
    237         step.focusDefaultControl();
    238         this.currentStep_ = step;
    239         chrome.send('stepShown', [name]);
    240       }.bind(this));
    241     },
    242 
    243     /**
    244      * Sets visibility of the background.
    245      * @param {boolean} visibility Whether background should be visible.
    246      * @param {function()=} opt_onCompletion Called after visibility has
    247      *     changed.
    248      */
    249     setBackgroundVisible: function(visible, opt_onCompletion) {
    250       changeVisibility(this.backgroundContainer_, visible,
    251           this.getBackgroundTransitionDuration(), opt_onCompletion);
    252     },
    253 
    254     /**
    255      * Returns default duration of animated transitions, in ms.
    256      */
    257     getDefaultTransitionDuration: function() {
    258       return this.transitionsEnabled_ ? DEFAULT_TRANSITION_DURATION_MS : 0;
    259     },
    260 
    261     /**
    262      * Returns duration of transitions of background shield, in ms.
    263      */
    264     getBackgroundTransitionDuration: function() {
    265       return this.transitionsEnabled_ ? BG_TRANSITION_DURATION_MS : 0;
    266     }
    267   };
    268 });
    269 
    270 /**
    271  * Initializes UI.
    272  */
    273 window.onload = function() {
    274   cr.FirstRun.initialize();
    275 };
    276