Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 // Fast out, slow in. | |
| 6 var EASING_FUNCTION = 'cubic-bezier(0.4, 0, 0.2, 1)'; | |
| 7 var EXPAND_DURATION = 350; | |
| 8 | |
| 9 /** | 5 /** |
| 10 * Calls |readyTest| repeatedly until it returns true, then calls | 6 * Calls |readyTest| repeatedly until it returns true, then calls |
| 11 * |readyCallback|. | 7 * |readyCallback|. |
| 12 * @param {function():boolean} readyTest | 8 * @param {function():boolean} readyTest |
| 13 * @param {!Function} readyCallback | 9 * @param {!Function} readyCallback |
| 14 */ | 10 */ |
| 15 function doWhenReady(readyTest, readyCallback) { | 11 function doWhenReady(readyTest, readyCallback) { |
| 16 // TODO(dschuyler): Determine whether this hack can be removed. | 12 // TODO(dschuyler): Determine whether this hack can be removed. |
| 17 // See also: https://github.com/Polymer/polymer/issues/3629 | 13 // See also: https://github.com/Polymer/polymer/issues/3629 |
| 18 var intervalId = setInterval(function() { | 14 var intervalId = setInterval(function() { |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 29 * section should be expanded at any given time. | 25 * section should be expanded at any given time. |
| 30 * @polymerBehavior Polymer.MainPageBehavior | 26 * @polymerBehavior Polymer.MainPageBehavior |
| 31 */ | 27 */ |
| 32 var MainPageBehaviorImpl = { | 28 var MainPageBehaviorImpl = { |
| 33 /** | 29 /** |
| 34 * @type {string} Selector to get the sections. Derived elements | 30 * @type {string} Selector to get the sections. Derived elements |
| 35 * must override. | 31 * must override. |
| 36 */ | 32 */ |
| 37 sectionSelector: '', | 33 sectionSelector: '', |
| 38 | 34 |
| 39 /** @type {?Element} The scrolling container. */ | |
| 40 scroller: null, | |
| 41 | |
| 42 /** @override */ | 35 /** @override */ |
| 43 attached: function() { | 36 attached: function() { |
| 37 /** @type {!HTMLElement} The scrolling container. */ | |
| 44 this.scroller = this.domHost && this.domHost.parentNode.$.mainContainer; | 38 this.scroller = this.domHost && this.domHost.parentNode.$.mainContainer; |
| 45 }, | 39 }, |
| 46 | 40 |
| 47 /** | 41 /** |
| 48 * Hides or unhides the sections not being expanded. | 42 * Hides or unhides the sections not being expanded. |
| 49 * @param {string} sectionName The section to keep visible. | 43 * @param {string} sectionName The section to keep visible. |
| 50 * @param {boolean} hidden Whether the sections should be hidden. | 44 * @param {boolean} hidden Whether the sections should be hidden. |
| 51 * @private | 45 * @private |
| 52 */ | 46 */ |
| 53 toggleOtherSectionsHidden_: function(sectionName, hidden) { | 47 toggleOtherSectionsHidden_: function(sectionName, hidden) { |
| 54 var sections = Polymer.dom(this.root).querySelectorAll( | 48 var sections = Polymer.dom(this.root).querySelectorAll( |
| 55 this.sectionSelector + ':not([section=' + sectionName + '])'); | 49 this.sectionSelector + ':not([section=' + sectionName + '])'); |
| 56 for (var section of sections) | 50 for (var section of sections) |
| 57 section.hidden = hidden; | 51 section.hidden = hidden; |
| 58 }, | 52 }, |
| 59 | 53 |
| 60 /** | 54 /** |
| 61 * Animates the card in |section|, expanding it to fill the page. | 55 * Animates the card in |section|, expanding it to fill the page. |
| 62 * @param {!SettingsSectionElement} section | 56 * @param {!SettingsSectionElement} section |
| 63 */ | 57 */ |
| 64 expandSection: function(section) { | 58 expandSection: function(section) { |
| 65 // If another section's card is expanding, cancel that animation first. | 59 // TODO(michaelpg): Manage transition lifetime better. |
| 66 var expanding = this.$$('.expanding'); | 60 // crbug.com/624145 |
| 67 if (expanding) { | 61 if (this.openSectionTransition_) { |
| 68 if (expanding == section) | 62 if (this.openSectionTransition_.section == section) |
| 69 return; | 63 return; |
| 70 | 64 |
| 71 if (this.animations['section']) { | 65 // Cancel the other section expanding. |
| 72 // Cancel the animation, then call startExpandSection_. | 66 this.openSectionTransition_.cancel(); |
| 73 this.cancelAnimation('section', function() { | 67 // After everything, schedule this function again to open the section. |
| 74 this.startExpandSection_(section); | 68 this.openSectionTransition_.finished |
| 75 }.bind(this)); | 69 .then(function() { |
| 76 } else { | 70 // The animation already finished, so close that section. |
| 77 // The animation must have finished but its promise hasn't resolved yet. | 71 this.collapseSection(this.openSectionTransition_.section); |
| 78 // When it resolves, collapse that section's card before expanding | 72 }).catch(function() {}) |
| 79 // this one. | 73 .then(this.expandSection.bind(this, section)); |
| 80 setTimeout(function() { | 74 return; |
| 81 this.collapseSection( | 75 } |
| 82 /** @type {!SettingsSectionElement} */(expanding)); | 76 if (this.closeSectionTransition_) { |
| 83 this.finishAnimation('section', function() { | 77 // Finish collapsing the section, then expand this section. |
| 84 this.startExpandSection_(section); | 78 this.closeSectionTransition_.finished |
| 85 }.bind(this)); | 79 .then(this.expandSection.bind(this, section)); |
|
Dan Beam
2016/07/12 02:01:54
i don't see lines like this as a big improvement w
michaelpg
2016/07/20 22:10:57
Acknowledged. Breaking here after .then( instead.
| |
| 86 }.bind(this)); | |
| 87 } | |
| 88 | |
| 89 return; | 80 return; |
| 90 } | 81 } |
| 91 | 82 |
| 92 if (this.$$('.collapsing') && this.animations['section']) { | 83 this.openSectionTransition_ = |
| 93 // Finish the collapse animation before expanding. | 84 new settings.OpenSectionTransition(section, this.scroller); |
| 94 this.finishAnimation('section', function() { | |
| 95 this.startExpandSection_(section); | |
| 96 }.bind(this)); | |
| 97 return; | |
| 98 } | |
| 99 | |
| 100 this.startExpandSection_(section); | |
| 101 }, | |
| 102 | |
| 103 /** | |
| 104 * Helper function to set up and start the expand animation. | |
| 105 * @param {!SettingsSectionElement} section | |
| 106 */ | |
| 107 startExpandSection_: function(section) { | |
| 108 if (section.classList.contains('expanded')) | |
| 109 return; | |
| 110 | 85 |
| 111 // Freeze the scroller and save its position. | 86 // Freeze the scroller and save its position. |
| 112 this.listScrollTop_ = this.scroller.scrollTop; | 87 this.scroller.listScrollTop_ = this.scroller.scrollTop; |
| 113 | |
| 114 var scrollerWidth = this.scroller.clientWidth; | 88 var scrollerWidth = this.scroller.clientWidth; |
| 115 this.scroller.style.overflow = 'hidden'; | 89 this.scroller.style.overflow = 'hidden'; |
| 90 | |
| 116 // Adjust width to compensate for scroller. | 91 // Adjust width to compensate for scroller. |
| 117 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 92 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; |
| 118 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | 93 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; |
| 119 | 94 |
| 120 // Freezes the section's height so its card can be removed from the flow. | 95 this.openSectionTransition_.play() |
| 121 this.freezeSection_(section); | 96 .then(function() { |
| 122 | 97 this.toggleOtherSectionsHidden_(section.section, true); |
| 123 // Expand the section's card to fill the parent. | 98 this.scroller.scrollTop = 0; |
| 124 var animationPromise = this.playExpandSection_(section); | 99 this.classList.add('showing-subpage'); |
| 125 | 100 this.fire('subpage-expand'); |
| 126 animationPromise.then(function() { | 101 }.bind(this)) |
| 127 this.scroller.scrollTop = 0; | 102 .catch(function() { |
| 128 this.toggleOtherSectionsHidden_(section.section, true); | 103 this.scroller.scrollTop = this.scroller.listScrollTop_; |
| 129 }.bind(this), function() { | 104 }.bind(this)) |
| 130 // Animation was canceled; restore the section. | 105 .then(function() { |
| 131 this.unfreezeSection_(section); | 106 this.scroller.style.width = ''; |
| 132 }.bind(this)).then(function() { | 107 this.scroller.style.overflow = ''; |
| 133 this.scroller.style.overflow = ''; | 108 this.openSectionTransition_ = null; |
| 134 this.scroller.style.width = ''; | 109 }.bind(this)); |
| 135 }.bind(this)); | |
| 136 }, | 110 }, |
| 137 | 111 |
| 138 /** | 112 /** |
| 139 * Animates the card in |section|, collapsing it back into its section. | 113 * Animates the card in |section|, collapsing it back into its section. |
| 140 * @param {!SettingsSectionElement} section | 114 * @param {!SettingsSectionElement} section |
| 141 */ | 115 */ |
| 142 collapseSection: function(section) { | 116 collapseSection: function(section) { |
| 143 // If the section's card is still expanding, cancel the expand animation. | 117 if (this.closeSectionTransition_) { |
| 144 if (section.classList.contains('expanding')) { | 118 assert(this.closeSectionTransition_.section == section); |
| 145 if (this.animations['section']) { | 119 this.closeSectionTransition_.cancel(); |
| 146 this.cancelAnimation('section'); | |
| 147 } else { | |
| 148 // The animation must have finished but its promise hasn't finished | |
| 149 // resolving; try again asynchronously. | |
| 150 this.async(function() { | |
| 151 this.collapseSection(section); | |
| 152 }); | |
| 153 } | |
| 154 return; | 120 return; |
| 155 } | 121 } |
| 156 | 122 |
| 157 if (!section.classList.contains('expanded')) | 123 if (this.openSectionTransition_) { |
| 124 assert(this.openSectionTransition_.section == section); | |
| 125 this.openSectionTransition_.cancel(); | |
| 158 return; | 126 return; |
| 127 } | |
| 159 | 128 |
| 160 this.toggleOtherSectionsHidden_(section.section, false); | 129 assert(section.classList.contains('expanded')); |
| 161 | 130 |
| 162 var scrollerWidth = this.scroller.clientWidth; | 131 var scrollerWidth = this.scroller.clientWidth; |
| 163 this.scroller.style.overflow = 'hidden'; | 132 this.scroller.style.overflow = 'hidden'; |
| 133 | |
| 164 // Adjust width to compensate for scroller. | 134 // Adjust width to compensate for scroller. |
| 135 // TODO(michaelpg): Minimize horizontal motion when scrollbar changes for | |
| 136 // the common cases. | |
| 165 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 137 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; |
| 166 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | 138 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; |
| 167 | 139 |
| 168 this.playCollapseSection_(section).then(function() { | 140 // Allow time for the dom-ifs in settings-main to re-render. |
| 169 this.unfreezeSection_(section); | 141 // TODO(michaelpg): Use a readiness signal (e.g., from the router) rather |
| 170 this.scroller.style.overflow = ''; | 142 // than firing events for settings-main and running this function async. |
| 171 this.scroller.style.width = ''; | 143 this.fire('subpage-collapsing'); |
| 172 section.classList.remove('collapsing'); | 144 this.async(function() { |
| 173 }.bind(this)); | 145 // Set up the close transition first to take the section out of the flow |
| 174 }, | 146 // before showing everything. |
| 147 this.closeSectionTransition_ = | |
| 148 new settings.CloseSectionTransition(section, this.scroller); | |
| 149 this.closeSectionTransition_.setUp(); | |
| 175 | 150 |
| 176 /** | 151 this.toggleOtherSectionsHidden_(section.section, false); |
| 177 * Freezes a section's height so its card can be removed from the flow without | 152 this.classList.remove('showing-subpage'); |
| 178 * affecting the layout of the surrounding sections. | 153 this.scroller.scrollTop = this.scroller.listScrollTop_; |
| 179 * @param {!SettingsSectionElement} section | |
| 180 * @private | |
| 181 */ | |
| 182 freezeSection_: function(section) { | |
| 183 var card = section.$.card; | |
| 184 section.style.height = section.clientHeight + 'px'; | |
| 185 | 154 |
| 186 var cardHeight = card.offsetHeight; | 155 this.closeSectionTransition_.play() |
| 187 var cardWidth = card.offsetWidth; | 156 .catch(function() { |
| 188 // If the section is not displayed yet (e.g., navigated directly to a | 157 this.fire('subpage-expand'); |
| 189 // sub-page), cardHeight and cardWidth are 0, so do not set the height or | 158 }.bind(this)) |
| 190 // width explicitly. | 159 .then(function() { |
| 191 // TODO(michaelpg): Improve this logic when refactoring | 160 this.scroller.style.overflow = ''; |
| 192 // settings-animated-pages. | 161 this.scroller.style.width = ''; |
| 193 if (cardHeight && cardWidth) { | 162 this.closeSectionTransition_ = null; |
| 194 // TODO(michaelpg): Temporary hack to store the height the section should | 163 }.bind(this)); |
| 195 // collapse to when it closes. | |
| 196 card.origHeight_ = cardHeight; | |
| 197 | |
| 198 card.style.height = cardHeight + 'px'; | |
| 199 card.style.width = cardWidth + 'px'; | |
| 200 } else { | |
| 201 // Set an invalid value so we don't try to use it later. | |
| 202 card.origHeight_ = NaN; | |
| 203 } | |
| 204 | |
| 205 // Place the section's card at its current position but removed from the | |
| 206 // flow. | |
| 207 card.style.top = card.getBoundingClientRect().top + 'px'; | |
| 208 section.classList.add('frozen'); | |
| 209 }, | |
| 210 | |
| 211 /** | |
| 212 * After freezeSection_, restores the section to its normal height. | |
| 213 * @param {!SettingsSectionElement} section | |
| 214 * @private | |
| 215 */ | |
| 216 unfreezeSection_: function(section) { | |
| 217 if (!section.classList.contains('frozen')) | |
| 218 return; | |
| 219 var card = section.$.card; | |
| 220 section.classList.remove('frozen'); | |
| 221 card.style.top = ''; | |
| 222 card.style.height = ''; | |
| 223 card.style.width = ''; | |
| 224 section.style.height = ''; | |
| 225 }, | |
| 226 | |
| 227 /** | |
| 228 * Expands the card in |section| to fill the page. | |
| 229 * @param {!SettingsSectionElement} section | |
| 230 * @return {!Promise} | |
| 231 * @private | |
| 232 */ | |
| 233 playExpandSection_: function(section) { | |
| 234 var card = section.$.card; | |
| 235 | |
| 236 // The card should start at the top of the page. | |
| 237 var targetTop = this.scroller.getBoundingClientRect().top; | |
| 238 | |
| 239 section.classList.add('expanding'); | |
| 240 | |
| 241 // Expand the card, using minHeight. (The card must span the container's | |
| 242 // client height, so it must be at least 100% in case the card is too short. | |
| 243 // If the card is already taller than the container's client height, we | |
| 244 // don't want to shrink the card to 100% or the content will overflow, so | |
| 245 // we can't use height, and animating height wouldn't look right anyway.) | |
| 246 var keyframes = [{ | |
| 247 top: card.style.top, | |
| 248 minHeight: card.style.height, | |
| 249 easing: EASING_FUNCTION, | |
| 250 }, { | |
| 251 top: targetTop + 'px', | |
| 252 minHeight: 'calc(100% - ' + targetTop + 'px)', | |
| 253 }]; | |
| 254 var options = /** @type {!KeyframeEffectOptions} */({ | |
| 255 duration: EXPAND_DURATION | |
| 256 }); | 164 }); |
| 257 // TODO(michaelpg): Change elevation of sections. | |
| 258 var promise; | |
| 259 if (keyframes[0].top && keyframes[0].minHeight) | |
| 260 promise = this.animateElement('section', card, keyframes, options); | |
| 261 else | |
| 262 promise = Promise.resolve(); | |
| 263 | |
| 264 promise.then(function() { | |
| 265 section.classList.add('expanded'); | |
| 266 card.style.top = ''; | |
| 267 this.style.margin = 'auto'; | |
| 268 section.$.header.hidden = true; | |
| 269 section.style.height = ''; | |
| 270 }.bind(this), function() { | |
| 271 // The animation was canceled; catch the error and continue. | |
| 272 }).then(function() { | |
| 273 // Whether finished or canceled, clean up the animation. | |
| 274 section.classList.remove('expanding'); | |
| 275 card.style.height = ''; | |
| 276 card.style.width = ''; | |
| 277 }); | |
| 278 | |
| 279 return promise; | |
| 280 }, | |
| 281 | |
| 282 /** | |
| 283 * Collapses the card in |section| back to its normal position. | |
| 284 * @param {!SettingsSectionElement} section | |
| 285 * @return {!Promise} | |
| 286 * @private | |
| 287 */ | |
| 288 playCollapseSection_: function(section) { | |
| 289 var card = section.$.card; | |
| 290 | |
| 291 this.style.margin = ''; | |
| 292 section.$.header.hidden = false; | |
| 293 | |
| 294 var startingTop = this.scroller.getBoundingClientRect().top; | |
| 295 | |
| 296 var cardHeightStart = card.clientHeight; | |
| 297 var cardWidthStart = card.clientWidth; | |
| 298 | |
| 299 section.classList.add('collapsing'); | |
| 300 section.classList.remove('expanding', 'expanded'); | |
| 301 | |
| 302 // If we navigated here directly, we don't know the original height of the | |
| 303 // section, so we skip the animation. | |
| 304 // TODO(michaelpg): remove this condition once sliding is implemented. | |
| 305 if (isNaN(card.origHeight_)) | |
| 306 return Promise.resolve(); | |
| 307 | |
| 308 // Restore the section to its proper height to make room for the card. | |
| 309 section.style.height = section.clientHeight + card.origHeight_ + 'px'; | |
| 310 | |
| 311 // TODO(michaelpg): this should be in collapseSection(), but we need to wait | |
| 312 // until the full page height is available (setting the section height). | |
| 313 this.scroller.scrollTop = this.listScrollTop_; | |
| 314 | |
| 315 // The card is unpositioned, so use its position as the ending state, | |
| 316 // but account for scroll. | |
| 317 var targetTop = card.getBoundingClientRect().top - this.scroller.scrollTop; | |
| 318 | |
| 319 // Account for the section header. | |
| 320 var headerStyle = getComputedStyle(section.$.header); | |
| 321 targetTop += section.$.header.offsetHeight + | |
| 322 parseInt(headerStyle.marginBottom, 10) + | |
| 323 parseInt(headerStyle.marginTop, 10); | |
| 324 | |
| 325 var keyframes = [{ | |
| 326 top: startingTop + 'px', | |
| 327 minHeight: cardHeightStart + 'px', | |
| 328 easing: EASING_FUNCTION, | |
| 329 }, { | |
| 330 top: targetTop + 'px', | |
| 331 minHeight: card.origHeight_ + 'px', | |
| 332 }]; | |
| 333 var options = /** @type {!KeyframeEffectOptions} */({ | |
| 334 duration: EXPAND_DURATION | |
| 335 }); | |
| 336 | |
| 337 card.style.width = cardWidthStart + 'px'; | |
| 338 var promise = this.animateElement('section', card, keyframes, options); | |
| 339 promise.then(function() { | |
| 340 card.style.width = ''; | |
| 341 }); | |
| 342 return promise; | |
| 343 }, | 165 }, |
| 344 }; | 166 }; |
| 345 | 167 |
| 346 | 168 |
| 347 /** @polymerBehavior */ | 169 /** @polymerBehavior */ |
| 348 var MainPageBehavior = [ | 170 var MainPageBehavior = [ |
| 349 TransitionBehavior, | |
| 350 MainPageBehaviorImpl | 171 MainPageBehaviorImpl |
| 351 ]; | 172 ]; |
| 352 | 173 |
| 353 | 174 |
| 354 /** | 175 /** |
| 355 * TODO(michaelpg): integrate slide animations. | 176 * TODO(michaelpg): integrate slide animations. |
| 356 * @polymerBehavior RoutableBehavior | 177 * @polymerBehavior RoutableBehavior |
| 357 */ | 178 */ |
| 358 var RoutableBehaviorImpl = { | 179 var RoutableBehaviorImpl = { |
| 359 properties: { | 180 properties: { |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 420 this.$$('[section=' + section + ']')); | 241 this.$$('[section=' + section + ']')); |
| 421 }, | 242 }, |
| 422 }; | 243 }; |
| 423 | 244 |
| 424 | 245 |
| 425 /** @polymerBehavior */ | 246 /** @polymerBehavior */ |
| 426 var RoutableBehavior = [ | 247 var RoutableBehavior = [ |
| 427 MainPageBehavior, | 248 MainPageBehavior, |
| 428 RoutableBehaviorImpl | 249 RoutableBehaviorImpl |
| 429 ]; | 250 ]; |
| OLD | NEW |