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