OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Prototype for first-run tutorial steps. | 6 * Prototype for first-run tutorial steps. |
7 */ | 7 */ |
8 | 8 |
9 cr.define('cr.FirstRun', function() { | 9 cr.define('cr.FirstRun', function() { |
10 var Step = cr.ui.define('div'); | 10 var Step = cr.ui.define('div'); |
11 | 11 |
12 Step.prototype = { | 12 Step.prototype = { |
13 __proto__: HTMLDivElement.prototype, | 13 __proto__: HTMLDivElement.prototype, |
14 | 14 |
15 // Name of step. | 15 // Name of step. |
16 name_: null, | 16 name_: null, |
17 | 17 |
18 // Button leading to next tutorial step. | 18 // Button leading to next tutorial step. |
19 nextButton_: null, | 19 nextButton_: null, |
20 | 20 |
21 // Default control for this step. | 21 // Default control for this step. |
22 defaultControl_: null, | 22 defaultControl_: null, |
23 | 23 |
24 decorate: function() { | 24 decorate: function() { |
25 this.name_ = this.getAttribute('id'); | 25 this.name_ = this.getAttribute('id'); |
26 var controlsContainer = this.getElementsByClassName('controls')[0]; | 26 var controlsContainer = this.getElementsByClassName('controls')[0]; |
27 if (!controlsContainer) | 27 if (!controlsContainer) |
28 throw Error('Controls not found.'); | 28 throw Error('Controls not found.'); |
29 this.nextButton_ = | 29 this.nextButton_ = |
30 controlsContainer.getElementsByClassName('next-button')[0]; | 30 controlsContainer.getElementsByClassName('next-button')[0]; |
31 if (!this.nextButton_) | 31 if (!this.nextButton_) |
32 throw Error('Next button not found.'); | 32 throw Error('Next button not found.'); |
33 this.nextButton_.addEventListener('click', (function(e) { | 33 this.nextButton_.addEventListener( |
34 chrome.send('nextButtonClicked', [this.getName()]); | 34 'click', (function(e) { |
35 e.stopPropagation(); | 35 chrome.send('nextButtonClicked', [this.getName()]); |
36 }).bind(this)); | 36 e.stopPropagation(); |
| 37 }).bind(this)); |
37 this.defaultControl_ = controlsContainer.children[0]; | 38 this.defaultControl_ = controlsContainer.children[0]; |
38 }, | 39 }, |
39 | 40 |
40 /** | 41 /** |
41 * Returns name of the string. | 42 * Returns name of the string. |
42 */ | 43 */ |
43 getName: function() { | 44 getName: function() { |
44 return this.name_; | 45 return this.name_; |
45 }, | 46 }, |
46 | 47 |
47 /** | 48 /** |
48 * Hides the step. | 49 * Hides the step. |
49 * @param {boolean} animated Whether transition should be animated. | 50 * @param {boolean} animated Whether transition should be animated. |
50 * @param {function()=} opt_onHidden Called after step has been hidden. | 51 * @param {function()=} opt_onHidden Called after step has been hidden. |
51 */ | 52 */ |
52 hide: function(animated, opt_onHidden) { | 53 hide: function(animated, opt_onHidden) { |
53 var transitionDuration = | 54 var transitionDuration = |
54 animated ? cr.FirstRun.getDefaultTransitionDuration() : 0; | 55 animated ? cr.FirstRun.getDefaultTransitionDuration() : 0; |
55 changeVisibility(this, | 56 changeVisibility(this, false, transitionDuration, function() { |
56 false, | 57 this.classList.add('hidden'); |
57 transitionDuration, | 58 if (opt_onHidden) |
58 function() { | 59 opt_onHidden(); |
59 this.classList.add('hidden'); | 60 }.bind(this)); |
60 if (opt_onHidden) | |
61 opt_onHidden(); | |
62 }.bind(this)); | |
63 }, | 61 }, |
64 | 62 |
65 /** | 63 /** |
66 * Shows the step. | 64 * Shows the step. |
67 * @param {boolean} animated Whether transition should be animated. | 65 * @param {boolean} animated Whether transition should be animated. |
68 * @param {function(Step)=} opt_onShown Called after step has been shown. | 66 * @param {function(Step)=} opt_onShown Called after step has been shown. |
69 */ | 67 */ |
70 show: function(animated, opt_onShown) { | 68 show: function(animated, opt_onShown) { |
71 var transitionDuration = | 69 var transitionDuration = |
72 animated ? cr.FirstRun.getDefaultTransitionDuration() : 0; | 70 animated ? cr.FirstRun.getDefaultTransitionDuration() : 0; |
73 this.classList.remove('hidden'); | 71 this.classList.remove('hidden'); |
74 changeVisibility(this, | 72 changeVisibility(this, true, transitionDuration, function() { |
75 true, | 73 if (opt_onShown) |
76 transitionDuration, | 74 opt_onShown(this); |
77 function() { | 75 }.bind(this)); |
78 if (opt_onShown) | |
79 opt_onShown(this); | |
80 }.bind(this)); | |
81 }, | 76 }, |
82 | 77 |
83 /** | 78 /** |
84 * Sets position of the step. | 79 * Sets position of the step. |
85 * @param {object} position Parameter with optional fields |top|, | 80 * @param {object} position Parameter with optional fields |top|, |
86 * |right|, |bottom|, |left| holding corresponding offsets. | 81 * |right|, |bottom|, |left| holding corresponding offsets. |
87 */ | 82 */ |
88 setPosition: function(position) { | 83 setPosition: function(position) { |
89 var style = this.style; | 84 var style = this.style; |
90 ['top', 'right', 'bottom', 'left'].forEach(function(property) { | 85 ['top', 'right', 'bottom', 'left'].forEach(function(property) { |
(...skipping 14 matching lines...) Expand all Loading... |
105 var Bubble = cr.ui.define('div'); | 100 var Bubble = cr.ui.define('div'); |
106 | 101 |
107 // List of rules declaring bubble's arrow position depending on text direction | 102 // List of rules declaring bubble's arrow position depending on text direction |
108 // and shelf alignment. Every rule has required field |position| with list | 103 // and shelf alignment. Every rule has required field |position| with list |
109 // of classes that should be applied to arrow element if this rule choosen. | 104 // of classes that should be applied to arrow element if this rule choosen. |
110 // The rule is suitable if its |shelf| and |dir| fields are correspond | 105 // The rule is suitable if its |shelf| and |dir| fields are correspond |
111 // to current shelf alignment and text direction. Missing fields behaves like | 106 // to current shelf alignment and text direction. Missing fields behaves like |
112 // '*' wildcard. The last suitable rule in list is choosen for arrow style. | 107 // '*' wildcard. The last suitable rule in list is choosen for arrow style. |
113 var ARROW_POSITION = { | 108 var ARROW_POSITION = { |
114 'app-list': [ | 109 'app-list': [ |
115 { | 110 {position: ['points-down', 'left']}, |
116 position: ['points-down', 'left'] | 111 {dir: 'rtl', position: ['points-down', 'right']}, |
117 }, | 112 {shelf: 'left', position: ['points-left', 'top']}, |
118 { | 113 {shelf: 'right', position: ['points-right', 'top']} |
119 dir: 'rtl', | |
120 position: ['points-down', 'right'] | |
121 }, | |
122 { | |
123 shelf: 'left', | |
124 position: ['points-left', 'top'] | |
125 }, | |
126 { | |
127 shelf: 'right', | |
128 position: ['points-right', 'top'] | |
129 } | |
130 ], | 114 ], |
131 'tray': [ | 115 'tray': [ |
132 { | 116 {position: ['points-right', 'top']}, |
133 position: ['points-right', 'top'] | 117 {dir: 'rtl', shelf: 'bottom', position: ['points-left', 'top']}, |
134 }, | 118 {shelf: 'left', position: ['points-left', 'top']} |
135 { | |
136 dir: 'rtl', | |
137 shelf: 'bottom', | |
138 position: ['points-left', 'top'] | |
139 }, | |
140 { | |
141 shelf: 'left', | |
142 position: ['points-left', 'top'] | |
143 } | |
144 ], | 119 ], |
145 'help': [ | 120 'help': [ |
146 { | 121 {position: ['points-right', 'bottom']}, |
147 position: ['points-right', 'bottom'] | 122 {dir: 'rtl', shelf: 'bottom', position: ['points-left', 'bottom']}, |
148 }, | 123 {shelf: 'left', position: ['points-left', 'bottom']} |
149 { | |
150 dir: 'rtl', | |
151 shelf: 'bottom', | |
152 position: ['points-left', 'bottom'] | |
153 }, | |
154 { | |
155 shelf: 'left', | |
156 position: ['points-left', 'bottom'] | |
157 } | |
158 ] | 124 ] |
159 }; | 125 }; |
160 | 126 |
161 var DISTANCE_TO_POINTEE = 10; | 127 var DISTANCE_TO_POINTEE = 10; |
162 var MINIMAL_SCREEN_OFFSET = 10; | 128 var MINIMAL_SCREEN_OFFSET = 10; |
163 var ARROW_LENGTH = 6; // Keep synced with .arrow border-width. | 129 var ARROW_LENGTH = 6; // Keep synced with .arrow border-width. |
164 | 130 |
165 Bubble.prototype = { | 131 Bubble.prototype = { |
166 __proto__: Step.prototype, | 132 __proto__: Step.prototype, |
167 | 133 |
168 // Element displaying arrow. | 134 // Element displaying arrow. |
169 arrow_: null, | 135 arrow_: null, |
170 | 136 |
171 // Unit vector directed along the bubble arrow. | 137 // Unit vector directed along the bubble arrow. |
172 direction_: null, | 138 direction_: null, |
173 | 139 |
174 /** | 140 /** |
175 * In addition to base class 'decorate' this method creates arrow and | 141 * In addition to base class 'decorate' this method creates arrow and |
176 * sets some properties related to arrow. | 142 * sets some properties related to arrow. |
177 */ | 143 */ |
178 decorate: function() { | 144 decorate: function() { |
179 Step.prototype.decorate.call(this); | 145 Step.prototype.decorate.call(this); |
180 this.arrow_ = document.createElement('div'); | 146 this.arrow_ = document.createElement('div'); |
181 this.arrow_.classList.add('arrow'); | 147 this.arrow_.classList.add('arrow'); |
182 this.appendChild(this.arrow_); | 148 this.appendChild(this.arrow_); |
183 var inputDirection = document.documentElement.getAttribute('dir'); | 149 var inputDirection = document.documentElement.getAttribute('dir'); |
184 var shelfAlignment = document.documentElement.getAttribute('shelf'); | 150 var shelfAlignment = document.documentElement.getAttribute('shelf'); |
185 var isSuitable = function(rule) { | 151 var isSuitable = function(rule) { |
186 var inputDirectionMatch = !rule.hasOwnProperty('dir') || | 152 var inputDirectionMatch = |
187 rule.dir === inputDirection; | 153 !rule.hasOwnProperty('dir') || rule.dir === inputDirection; |
188 var shelfAlignmentMatch = !rule.hasOwnProperty('shelf') || | 154 var shelfAlignmentMatch = |
189 rule.shelf === shelfAlignment; | 155 !rule.hasOwnProperty('shelf') || rule.shelf === shelfAlignment; |
190 return inputDirectionMatch && shelfAlignmentMatch; | 156 return inputDirectionMatch && shelfAlignmentMatch; |
191 }; | 157 }; |
192 var lastSuitableRule = null; | 158 var lastSuitableRule = null; |
193 var rules = ARROW_POSITION[this.getName()]; | 159 var rules = ARROW_POSITION[this.getName()]; |
194 rules.forEach(function(rule) { | 160 rules.forEach(function(rule) { |
195 if (isSuitable(rule)) | 161 if (isSuitable(rule)) |
196 lastSuitableRule = rule; | 162 lastSuitableRule = rule; |
197 }); | 163 }); |
198 assert(lastSuitableRule); | 164 assert(lastSuitableRule); |
199 lastSuitableRule.position.forEach(function(cls) { | 165 lastSuitableRule.position.forEach(function(cls) { |
200 this.arrow_.classList.add(cls); | 166 this.arrow_.classList.add(cls); |
201 }.bind(this)); | 167 }.bind(this)); |
202 var list = this.arrow_.classList; | 168 var list = this.arrow_.classList; |
203 if (list.contains('points-up')) | 169 if (list.contains('points-up')) |
204 this.direction_ = [0, -1]; | 170 this.direction_ = [0, -1]; |
205 else if (list.contains('points-right')) | 171 else if (list.contains('points-right')) |
206 this.direction_ = [1, 0]; | 172 this.direction_ = [1, 0]; |
207 else if (list.contains('points-down')) | 173 else if (list.contains('points-down')) |
208 this.direction_ = [0, 1]; | 174 this.direction_ = [0, 1]; |
209 else // list.contains('points-left') | 175 else // list.contains('points-left') |
210 this.direction_ = [-1, 0]; | 176 this.direction_ = [-1, 0]; |
211 }, | 177 }, |
212 | 178 |
213 /** | 179 /** |
214 * Sets position of bubble in such a maner that bubble's arrow points to | 180 * Sets position of bubble in such a maner that bubble's arrow points to |
215 * given point. | 181 * given point. |
216 * @param {Array} point Bubble arrow should point to this point after | 182 * @param {Array} point Bubble arrow should point to this point after |
217 * positioning. |point| has format [x, y]. | 183 * positioning. |point| has format [x, y]. |
218 * @param {offset} number Additional offset from |point|. | 184 * @param {offset} number Additional offset from |point|. |
219 */ | 185 */ |
220 setPointsTo: function(point, offset) { | 186 setPointsTo: function(point, offset) { |
221 var shouldShowBefore = this.hidden; | 187 var shouldShowBefore = this.hidden; |
222 // "Showing" bubble in order to make offset* methods work. | 188 // "Showing" bubble in order to make offset* methods work. |
223 if (shouldShowBefore) { | 189 if (shouldShowBefore) { |
224 this.style.setProperty('opacity', '0'); | 190 this.style.setProperty('opacity', '0'); |
225 this.show(false); | 191 this.show(false); |
226 } | 192 } |
227 var arrow = [this.arrow_.offsetLeft + this.arrow_.offsetWidth / 2, | 193 var arrow = [ |
228 this.arrow_.offsetTop + this.arrow_.offsetHeight / 2]; | 194 this.arrow_.offsetLeft + this.arrow_.offsetWidth / 2, |
| 195 this.arrow_.offsetTop + this.arrow_.offsetHeight / 2 |
| 196 ]; |
229 var totalOffset = DISTANCE_TO_POINTEE + offset; | 197 var totalOffset = DISTANCE_TO_POINTEE + offset; |
230 var left = point[0] - totalOffset * this.direction_[0] - arrow[0]; | 198 var left = point[0] - totalOffset * this.direction_[0] - arrow[0]; |
231 var top = point[1] - totalOffset * this.direction_[1] - arrow[1]; | 199 var top = point[1] - totalOffset * this.direction_[1] - arrow[1]; |
232 // Force bubble to be inside screen. | 200 // Force bubble to be inside screen. |
233 if (this.arrow_.classList.contains('points-up') || | 201 if (this.arrow_.classList.contains('points-up') || |
234 this.arrow_.classList.contains('points-down')) { | 202 this.arrow_.classList.contains('points-down')) { |
235 left = Math.max(left, MINIMAL_SCREEN_OFFSET); | 203 left = Math.max(left, MINIMAL_SCREEN_OFFSET); |
236 left = Math.min(left, document.body.offsetWidth - this.offsetWidth - | 204 left = Math.min( |
237 MINIMAL_SCREEN_OFFSET); | 205 left, |
| 206 document.body.offsetWidth - this.offsetWidth - |
| 207 MINIMAL_SCREEN_OFFSET); |
238 } | 208 } |
239 if (this.arrow_.classList.contains('points-left') || | 209 if (this.arrow_.classList.contains('points-left') || |
240 this.arrow_.classList.contains('points-right')) { | 210 this.arrow_.classList.contains('points-right')) { |
241 top = Math.max(top, MINIMAL_SCREEN_OFFSET); | 211 top = Math.max(top, MINIMAL_SCREEN_OFFSET); |
242 top = Math.min(top, document.body.offsetHeight - this.offsetHeight - | 212 top = Math.min( |
243 MINIMAL_SCREEN_OFFSET); | 213 top, |
| 214 document.body.offsetHeight - this.offsetHeight - |
| 215 MINIMAL_SCREEN_OFFSET); |
244 } | 216 } |
245 this.style.setProperty('left', left + 'px'); | 217 this.style.setProperty('left', left + 'px'); |
246 this.style.setProperty('top', top + 'px'); | 218 this.style.setProperty('top', top + 'px'); |
247 if (shouldShowBefore) { | 219 if (shouldShowBefore) { |
248 this.hide(false); | 220 this.hide(false); |
249 this.style.removeProperty('opacity'); | 221 this.style.removeProperty('opacity'); |
250 } | 222 } |
251 }, | 223 }, |
252 | 224 |
253 /** | 225 /** |
254 * Sets position of bubble. Overrides Step.setPosition to adjust offsets | 226 * Sets position of bubble. Overrides Step.setPosition to adjust offsets |
255 * in case if its direction is the same as arrow's direction. | 227 * in case if its direction is the same as arrow's direction. |
256 * @param {object} position Parameter with optional fields |top|, | 228 * @param {object} position Parameter with optional fields |top|, |
257 * |right|, |bottom|, |left| holding corresponding offsets. | 229 * |right|, |bottom|, |left| holding corresponding offsets. |
258 */ | 230 */ |
259 setPosition: function(position) { | 231 setPosition: function(position) { |
260 var arrow = this.arrow_; | 232 var arrow = this.arrow_; |
261 // Increasing offset if it's from side where bubble points to. | 233 // Increasing offset if it's from side where bubble points to. |
262 [['top', 'points-up'], | 234 [['top', 'points-up'], ['right', 'points-right'], |
263 ['right', 'points-right'], | 235 ['bottom', 'points-down'], ['left', 'points-left']] |
264 ['bottom', 'points-down'], | 236 .forEach(function(mapping) { |
265 ['left', 'points-left']].forEach(function(mapping) { | 237 if (position.hasOwnProperty(mapping[0]) && |
266 if (position.hasOwnProperty(mapping[0]) && | 238 arrow.classList.contains(mapping[1])) { |
267 arrow.classList.contains(mapping[1])) { | 239 position[mapping[0]] += ARROW_LENGTH + DISTANCE_TO_POINTEE; |
268 position[mapping[0]] += ARROW_LENGTH + DISTANCE_TO_POINTEE; | 240 } |
269 } | 241 }); |
270 }); | |
271 Step.prototype.setPosition.call(this, position); | 242 Step.prototype.setPosition.call(this, position); |
272 }, | 243 }, |
273 }; | 244 }; |
274 | 245 |
275 var HelpStep = cr.ui.define('div'); | 246 var HelpStep = cr.ui.define('div'); |
276 | 247 |
277 HelpStep.prototype = { | 248 HelpStep.prototype = { |
278 __proto__: Bubble.prototype, | 249 __proto__: Bubble.prototype, |
279 | 250 |
280 decorate: function() { | 251 decorate: function() { |
(...skipping 10 matching lines...) Expand all Loading... |
291 if (el.id == 'help') | 262 if (el.id == 'help') |
292 HelpStep.decorate(el); | 263 HelpStep.decorate(el); |
293 else if (el.classList.contains('bubble')) | 264 else if (el.classList.contains('bubble')) |
294 Bubble.decorate(el); | 265 Bubble.decorate(el); |
295 else | 266 else |
296 Step.decorate(el); | 267 Step.decorate(el); |
297 }; | 268 }; |
298 | 269 |
299 return {DecorateStep: DecorateStep}; | 270 return {DecorateStep: DecorateStep}; |
300 }); | 271 }); |
OLD | NEW |