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 /** | 5 /** |
| 6 * Calls |readyTest| repeatedly until it returns true, then calls | 6 * Calls |readyTest| repeatedly until it returns true, then calls |
| 7 * |readyCallback|. | 7 * |readyCallback|. |
| 8 * @param {function():boolean} readyTest | 8 * @param {function():boolean} readyTest |
| 9 * @param {!Function} readyCallback | 9 * @param {!Function} readyCallback |
| 10 */ | 10 */ |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 35 this.scroller = this.domHost.parentNode.scroller; | 35 this.scroller = this.domHost.parentNode.scroller; |
| 36 else | 36 else |
| 37 this.scroller = document.body; // Used in unit tests. | 37 this.scroller = document.body; // Used in unit tests. |
| 38 }, | 38 }, |
| 39 | 39 |
| 40 /** | 40 /** |
| 41 * @param {!settings.Route} newRoute | 41 * @param {!settings.Route} newRoute |
| 42 * @param {!settings.Route} oldRoute | 42 * @param {!settings.Route} oldRoute |
| 43 */ | 43 */ |
| 44 currentRouteChanged: function(newRoute, oldRoute) { | 44 currentRouteChanged: function(newRoute, oldRoute) { |
| 45 var newRouteIsSubpage = newRoute && newRoute.subpage.length; | 45 // Allow the page to load before expanding the section. TODO(michaelpg): |
| 46 var oldRouteIsSubpage = oldRoute && oldRoute.subpage.length; | 46 // Time this better when refactoring settings-animated-pages. |
| 47 if (!oldRoute && newRoute.subpage.length) | |
| 48 setTimeout(this.tryTransitionToSection_.bind(this)); | |
| 49 else | |
| 50 this.tryTransitionToSection_(); | |
| 51 }, | |
| 47 | 52 |
| 48 if (!oldRoute && newRouteIsSubpage) { | 53 /** |
| 49 // Allow the page to load before expanding the section. TODO(michaelpg): | 54 * If possible, transitions to the current route's section (by expanding or |
| 50 // Time this better when refactoring settings-animated-pages. | 55 * scrolling to it). If another transition is running, finishes or cancels |
| 51 setTimeout(function() { | 56 * that one, then schedules this function again. This ensures the current |
| 52 var section = this.getSection_(newRoute.section); | 57 * section is quickly shown, without getting the page into a broken state -- |
| 53 if (section) | 58 * if currentRoute changes in between calls, just transition to the new route. |
| 54 this.expandSection_(section); | 59 * @private |
| 55 }.bind(this)); | 60 */ |
| 61 tryTransitionToSection_: function() { | |
| 62 var currentRoute = settings.getCurrentRoute(); | |
| 63 var currentSection = this.getSection_(currentRoute.section); | |
| 64 | |
| 65 // If an animation is already playing, try finishing or canceling it. | |
| 66 if (this.currentAnimation_) { | |
| 67 this.maybeStopCurrentAnimation_(); | |
| 68 // Either way, this function will be called again once the current | |
| 69 // animation ends. | |
| 56 return; | 70 return; |
| 57 } | 71 } |
| 58 | 72 |
| 59 if (newRouteIsSubpage) { | 73 var promise; |
| 60 if (!oldRouteIsSubpage || newRoute.section != oldRoute.section) { | 74 var expandedSection = /** @type {?SettingsSectionElement} */( |
| 61 var section = this.getSection_(newRoute.section); | 75 this.$$('settings-section.expanded')); |
| 62 if (section) | 76 if (expandedSection) { |
| 63 this.expandSection_(section); | 77 // If the section shouldn't be expanded, collapse it. |
| 78 if (!currentRoute.subpage.length || expandedSection != currentSection) { | |
| 79 promise = this.collapseSection_(expandedSection); | |
| 80 // Scroll to the collapsed section. TODO(michaelpg): This can look weird | |
| 81 // because the collapse we just scheduled calculated its end target | |
| 82 // based on the current scroll position. This bug existed before, and is | |
| 83 // fixed in the next patch by making the card position: absolute. | |
| 84 if (currentSection) | |
| 85 this.scrollToSection_(); | |
| 64 } | 86 } |
| 65 } else { | 87 } else if (currentSection) { |
| 66 if (oldRouteIsSubpage) { | 88 // Expand the section into a subpage or scroll to it on the main page. |
| 67 var section = this.getSection_(oldRoute.section); | 89 if (currentRoute.subpage.length) |
| 68 if (section) | 90 promise = this.expandSection_(currentSection); |
| 69 this.collapseSection_(section); | 91 else |
| 70 } | |
| 71 | |
| 72 // Scrolls to the section if this main page contains the route's section. | |
| 73 if (newRoute && newRoute.section && this.getSection_(newRoute.section)) | |
| 74 this.scrollToSection_(); | 92 this.scrollToSection_(); |
| 75 } | 93 } |
| 94 | |
| 95 // When this animation ends, another may be necessary. Call this function | |
| 96 // again after the promise resolves. | |
| 97 if (promise) | |
| 98 promise.then(this.tryTransitionToSection_.bind(this)); | |
| 99 }, | |
| 100 | |
| 101 /** | |
| 102 * If the current animation is inconsistent with the current route, stops the | |
|
Dan Beam
2016/08/09 18:15:19
stops -> stop?
michaelpg
2016/08/09 20:44:03
figure it should still be indicative, like other f
| |
| 103 * animation by finishing or canceling it so the new route can be animated to. | |
| 104 * @private | |
| 105 */ | |
| 106 maybeStopCurrentAnimation_: function() { | |
| 107 var currentRoute = settings.getCurrentRoute(); | |
| 108 var animatingSection = /** @type {?SettingsSectionElement} */( | |
| 109 this.$$('settings-section.expanding, settings-section.collapsing')); | |
|
Dan Beam
2016/08/09 18:15:19
i assume it's impossible for these both to exist a
michaelpg
2016/08/09 20:44:03
That's the idea, enforced by tryTransitionToSectio
| |
| 110 assert(animatingSection); | |
| 111 | |
| 112 if (animatingSection.classList.contains('expanding')) { | |
| 113 // Cancel the animation to go back to the main page if the animating | |
| 114 // section shouldn't be expanded. | |
| 115 if (animatingSection.section != currentRoute.section || | |
| 116 !currentRoute.subpage.length) { | |
| 117 this.currentAnimation_.cancel(); | |
| 118 } | |
| 119 // Otherwise, let the expand animation continue. | |
| 120 return; | |
| 121 } | |
| 122 | |
| 123 assert(animatingSection.classList.contains('collapsing')); | |
| 124 if (!currentRoute.subpage.length) | |
| 125 return; | |
| 126 | |
| 127 // If the collapsing section actually matches the current route's section, | |
| 128 // we can just cancel the animation to re-expand the section. | |
| 129 if (animatingSection.section == currentRoute.section) { | |
| 130 this.currentAnimation_.cancel(); | |
| 131 return; | |
| 132 } | |
| 133 | |
| 134 // The current route is a subpage, so that section needs to expand. | |
| 135 // Immediately finish the current collapse animation so that can happen. | |
| 136 this.currentAnimation_.finish(); | |
| 76 }, | 137 }, |
| 77 | 138 |
| 78 /** | 139 /** |
| 79 * Animates the card in |section|, expanding it to fill the page. | 140 * Animates the card in |section|, expanding it to fill the page. |
| 80 * @param {!SettingsSectionElement} section | 141 * @param {!SettingsSectionElement} section |
| 142 * @return {!Promise} Resolved when the transition is finished or canceled. | |
| 81 * @private | 143 * @private |
| 82 */ | 144 */ |
| 83 expandSection_: function(section) { | 145 expandSection_: function(section) { |
| 84 // If another section's card is expanding, cancel that animation first. | 146 assert(this.scroller); |
| 85 var expanding = this.$$('.expanding'); | 147 assert(section.canAnimateExpand()); |
| 86 if (expanding) { | |
| 87 if (expanding == section) | |
| 88 return; | |
| 89 | 148 |
| 90 if (this.animations['section']) { | 149 // Save the scroller position before freezing it. |
| 91 // Cancel the animation, then call startExpandSection_. | 150 this.origScrollTop_ = this.scroller.scrollTop; |
| 92 this.cancelAnimation('section', function() { | 151 this.toggleScrolling_(false); |
| 93 this.startExpandSection_(section); | |
| 94 }.bind(this)); | |
| 95 } else { | |
| 96 // The animation must have finished but its promise hasn't resolved yet. | |
| 97 // When it resolves, collapse that section's card before expanding | |
| 98 // this one. | |
| 99 setTimeout(function() { | |
| 100 this.collapseSection_( | |
| 101 /** @type {!SettingsSectionElement} */(expanding)); | |
| 102 this.finishAnimation('section', function() { | |
| 103 this.startExpandSection_(section); | |
| 104 }.bind(this)); | |
| 105 }.bind(this)); | |
| 106 } | |
| 107 | 152 |
| 108 return; | 153 // Freeze the section's height so its card can be removed from the flow. |
| 109 } | |
| 110 | |
| 111 if (this.$$('.collapsing') && this.animations['section']) { | |
| 112 // Finish the collapse animation before expanding. | |
| 113 this.finishAnimation('section', function() { | |
| 114 this.startExpandSection_(section); | |
| 115 }.bind(this)); | |
| 116 return; | |
| 117 } | |
| 118 | |
| 119 this.startExpandSection_(section); | |
| 120 }, | |
| 121 | |
| 122 /** | |
| 123 * Helper function to set up and start the expand animation. | |
| 124 * @param {!SettingsSectionElement} section | |
| 125 */ | |
| 126 startExpandSection_: function(section) { | |
| 127 if (!section.canAnimateExpand()) | |
| 128 return; | |
| 129 | |
| 130 // Freeze the scroller and save its position. | |
| 131 this.listScrollTop_ = this.scroller.scrollTop; | |
| 132 | |
| 133 var scrollerWidth = this.scroller.clientWidth; | |
| 134 this.scroller.style.overflow = 'hidden'; | |
| 135 // Adjust width to compensate for scroller. | |
| 136 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | |
| 137 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | |
| 138 | |
| 139 // Freezes the section's height so its card can be removed from the flow. | |
| 140 section.setFrozen(true); | 154 section.setFrozen(true); |
| 141 | 155 |
| 142 // Expand the section's card to fill the parent. | 156 this.currentAnimation_ = section.animateExpand(this.scroller); |
| 143 var animationPromise = this.playExpandSection_(section); | 157 var promise = this.currentAnimation_ ? |
| 158 this.currentAnimation_.finished : Promise.resolve(); | |
| 144 | 159 |
| 145 animationPromise.then(function() { | 160 var finished; |
| 161 return promise.then(function() { | |
| 146 this.scroller.scrollTop = 0; | 162 this.scroller.scrollTop = 0; |
| 147 this.toggleOtherSectionsHidden_(section.section, true); | 163 this.toggleOtherSectionsHidden_(section.section, true); |
| 164 finished = true; | |
| 148 }.bind(this), function() { | 165 }.bind(this), function() { |
| 149 // Animation was canceled; restore the section. | 166 // The animation was canceled; restore the section. |
| 150 section.setFrozen(false); | 167 section.setFrozen(false); |
| 151 }.bind(this)).then(function() { | |
| 152 this.scroller.style.overflow = ''; | |
| 153 this.scroller.style.width = ''; | |
| 154 }.bind(this)); | |
| 155 }, | |
| 156 | |
| 157 /** | |
| 158 * Expands the card in |section| to fill the page. | |
| 159 * @param {!SettingsSectionElement} section | |
| 160 * @return {!Promise} | |
| 161 * @private | |
| 162 */ | |
| 163 playExpandSection_: function(section) { | |
| 164 // We must be attached. | |
| 165 assert(this.scroller); | |
| 166 | |
| 167 var promise; | |
| 168 var animationConfig = section.animateExpand(this.scroller); | |
| 169 if (animationConfig) { | |
| 170 promise = this.animateElement('section', animationConfig.card, | |
| 171 animationConfig.keyframes, animationConfig.options); | |
| 172 } else { | |
| 173 promise = Promise.resolve(); | |
| 174 } | |
| 175 | |
| 176 var finished; | |
| 177 promise.then(function() { | |
| 178 finished = true; | |
| 179 this.style.margin = 'auto'; | |
| 180 }.bind(this), function() { | |
| 181 // The animation was canceled; catch the error and continue. | |
| 182 finished = false; | 168 finished = false; |
| 183 }).then(function() { | 169 }).then(function() { |
| 184 section.cleanUpAnimateExpand(finished); | 170 section.cleanUpAnimateExpand(finished); |
| 185 }); | 171 this.toggleScrolling_(true); |
| 186 | 172 this.currentAnimation_ = null; |
| 187 return promise; | 173 }.bind(this)); |
| 188 }, | 174 }, |
| 189 | 175 |
| 190 /** | 176 /** |
| 191 * Animates the card in |section|, collapsing it back into its section. | 177 * Animates the card in |section|, collapsing it back into its section. |
| 192 * @param {!SettingsSectionElement} section | 178 * @param {!SettingsSectionElement} section |
| 179 * @return {!Promise} Resolved when the transition is finished or canceled. | |
| 193 * @private | 180 * @private |
| 194 */ | 181 */ |
| 195 collapseSection_: function(section) { | 182 collapseSection_: function(section) { |
| 196 // If the section's card is still expanding, cancel the expand animation. | 183 assert(this.scroller); |
| 197 if (section.classList.contains('expanding')) { | 184 assert(section.canAnimateCollapse()); |
| 198 if (this.animations['section']) { | |
| 199 this.cancelAnimation('section'); | |
| 200 } else { | |
| 201 // The animation must have finished but its promise hasn't finished | |
| 202 // resolving; try again asynchronously. | |
| 203 this.async(function() { | |
| 204 this.collapseSection_(section); | |
| 205 }); | |
| 206 } | |
| 207 return; | |
| 208 } | |
| 209 | |
| 210 if (!section.canAnimateCollapse()) | |
| 211 return; | |
| 212 | 185 |
| 213 this.toggleOtherSectionsHidden_(section.section, false); | 186 this.toggleOtherSectionsHidden_(section.section, false); |
| 187 this.toggleScrolling_(false); | |
| 214 | 188 |
| 215 var scrollerWidth = this.scroller.clientWidth; | 189 this.currentAnimation_ = |
| 216 this.scroller.style.overflow = 'hidden'; | 190 section.animateCollapse(this.scroller, this.origScrollTop_); |
| 217 // Adjust width to compensate for scroller. | 191 var promise = this.currentAnimation_ ? |
| 218 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | 192 this.currentAnimation_.finished : Promise.resolve(); |
| 219 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | |
| 220 | 193 |
| 221 this.playCollapseSection_(section).then(function() { | 194 return promise.then(function() { |
| 195 section.cleanUpAnimateCollapse(true); | |
| 196 }, function() { | |
| 197 section.cleanUpAnimateCollapse(false); | |
| 198 }).then(function() { | |
| 222 section.setFrozen(false); | 199 section.setFrozen(false); |
| 223 this.scroller.style.overflow = ''; | |
| 224 this.scroller.style.width = ''; | |
| 225 section.classList.remove('collapsing'); | 200 section.classList.remove('collapsing'); |
| 201 this.toggleScrolling_(true); | |
| 202 this.currentAnimation_ = null; | |
| 226 }.bind(this)); | 203 }.bind(this)); |
| 227 }, | 204 }, |
| 228 | 205 |
| 229 /** | 206 /** |
| 230 * Collapses the card in |section| back to its normal position. | 207 * Hides or unhides the sections not being expanded. |
| 231 * @param {!SettingsSectionElement} section | 208 * @param {string} sectionName The section to keep visible. |
| 232 * @return {!Promise} | 209 * @param {boolean} hidden Whether the sections should be hidden. |
| 233 * @private | 210 * @private |
| 234 */ | 211 */ |
| 235 playCollapseSection_: function(section) { | 212 toggleOtherSectionsHidden_: function(sectionName, hidden) { |
| 236 // We must be attached. | 213 var sections = Polymer.dom(this.root).querySelectorAll( |
| 237 assert(this.scroller); | 214 'settings-section'); |
| 238 | 215 for (var section of sections) |
| 239 this.style.margin = ''; | 216 section.hidden = hidden && (section.section != sectionName); |
| 240 | |
| 241 var promise; | |
| 242 var animationConfig = | |
| 243 section.animateCollapse(this.scroller, this.listScrollTop_); | |
| 244 if (animationConfig) { | |
| 245 promise = this.animateElement('section', animationConfig.card, | |
| 246 animationConfig.keyframes, animationConfig.options); | |
| 247 } else { | |
| 248 promise = Promise.resolve(); | |
| 249 } | |
| 250 | |
| 251 promise.then(function() { | |
| 252 section.cleanUpAnimateCollapse(true); | |
| 253 }, function() { | |
| 254 section.cleanUpAnimateCollapse(false); | |
| 255 }); | |
| 256 return promise; | |
| 257 }, | 217 }, |
| 258 | 218 |
| 259 /** @private */ | 219 /** @private */ |
| 260 scrollToSection_: function() { | 220 scrollToSection_: function() { |
| 261 doWhenReady( | 221 doWhenReady( |
| 262 function() { | 222 function() { |
| 263 return this.scrollHeight > 0; | 223 return this.scrollHeight > 0; |
| 264 }.bind(this), | 224 }.bind(this), |
| 265 function() { | 225 function() { |
| 266 // If the current section changes while we are waiting for the page to | 226 // If the current section changes while we are waiting for the page to |
| 267 // be ready, scroll to the newest requested section. | 227 // be ready, scroll to the newest requested section. |
| 268 this.getSection_(settings.getCurrentRoute().section).scrollIntoView(); | 228 var section = this.getSection_(settings.getCurrentRoute().section); |
| 229 if (section) | |
| 230 section.scrollIntoView(); | |
| 269 }.bind(this)); | 231 }.bind(this)); |
| 270 }, | 232 }, |
| 271 | 233 |
| 272 /** | 234 /** |
| 273 * Hides or unhides the sections not being expanded. | |
| 274 * @param {string} sectionName The section to keep visible. | |
| 275 * @param {boolean} hidden Whether the sections should be hidden. | |
| 276 * @private | |
| 277 */ | |
| 278 toggleOtherSectionsHidden_: function(sectionName, hidden) { | |
| 279 var sections = Polymer.dom(this.root).querySelectorAll( | |
| 280 'settings-section'); | |
| 281 for (var section of sections) | |
| 282 section.hidden = hidden && (section.section != sectionName); | |
| 283 }, | |
| 284 | |
| 285 /** | |
| 286 * Helper function to get a section from the local DOM. | 235 * Helper function to get a section from the local DOM. |
| 287 * @param {string} section Section name of the element to get. | 236 * @param {string} section Section name of the element to get. |
| 288 * @return {?SettingsSectionElement} | 237 * @return {?SettingsSectionElement} |
| 289 * @private | 238 * @private |
| 290 */ | 239 */ |
| 291 getSection_: function(section) { | 240 getSection_: function(section) { |
| 241 if (!section) | |
| 242 return null; | |
| 292 return /** @type {?SettingsSectionElement} */( | 243 return /** @type {?SettingsSectionElement} */( |
| 293 this.$$('[section=' + section + ']')); | 244 this.$$('[section=' + section + ']')); |
| 294 }, | 245 }, |
| 246 | |
| 247 /** | |
| 248 * Enables or disables user scrolling, via overscroll: hidden. Room for the | |
| 249 * hidden scrollbar is added to prevent the page width from changing back and | |
| 250 * forth. | |
| 251 * @param {boolean} enabled | |
| 252 * @private | |
| 253 */ | |
| 254 toggleScrolling_: function(enabled) { | |
| 255 if (enabled) { | |
| 256 this.scroller.style.overflow = ''; | |
| 257 this.scroller.style.width = ''; | |
| 258 } else { | |
| 259 var scrollerWidth = this.scroller.clientWidth; | |
| 260 this.scroller.style.overflow = 'hidden'; | |
| 261 var scrollbarWidth = this.scroller.clientWidth - scrollerWidth; | |
| 262 this.scroller.style.width = 'calc(100% - ' + scrollbarWidth + 'px)'; | |
| 263 } | |
| 264 } | |
| 295 }; | 265 }; |
| 296 | 266 |
| 297 /** @polymerBehavior */ | 267 /** @polymerBehavior */ |
| 298 var MainPageBehavior = [ | 268 var MainPageBehavior = [ |
| 299 settings.RouteObserverBehavior, | 269 settings.RouteObserverBehavior, |
| 300 TransitionBehavior, | |
| 301 MainPageBehaviorImpl, | 270 MainPageBehaviorImpl, |
| 302 ]; | 271 ]; |
| OLD | NEW |