From a545628e04b5bc9e6bde62b18a49590934390e43 Mon Sep 17 00:00:00 2001 From: Andrew Miner Date: Wed, 13 May 2015 09:12:44 -0700 Subject: [PATCH] Bug: Prevent invalid UI states in markdown editor When rapidly transitioning between various states, the markdown editor could wind up with a weird mixture of UI elements showing as some of transitions were dropped because they followed too quickly on the heels of another transition. This change fixes the problem by changing how the BaseController's `hide` and `show` functions work. They now set a "target" state on the element and, during each step of the animation, check whether the target has changed. If so, the transition begins to work in reverse to return to the prior state. --- src/coffee/controllers/base_controller.coffee | 105 +++++++++++------- .../markdown_section_controller.coffee | 13 +-- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/coffee/controllers/base_controller.coffee b/src/coffee/controllers/base_controller.coffee index 54876e7c0..b02828654 100644 --- a/src/coffee/controllers/base_controller.coffee +++ b/src/coffee/controllers/base_controller.coffee @@ -41,25 +41,8 @@ module.exports = class BaseController extends backbone.View hide: ($el)-> $el ?= @$el - - if not $el.hasClass 'hideable' - $el.addClass 'hideable' - _.defer => @hide $el - return - - if not ($el.hasClass('hiding') or $el.hasClass('hidden')) - logger.verbose => "#{this} is hiding \"#{$el.selector}\"" - $el.addClass 'hiding' - @trigger Event.animate.hide.start, this, $el - @_onAnimationComplete $el, Duration.normal, => @hide $el - return - - if $el.hasClass('hiding') and not $el.hasClass('hidden') - $el.removeClass 'hiding' - $el.addClass 'hidden' - logger.verbose => "#{this} is finished hiding #{$el.selector}" - - @trigger Event.animate.hide.finish, this, $el + $el.data 'target-visibility', 'hidden' + _.delay (=> @_adjustTargetVisibility $el), Duration.snap refresh: -> logger.verbose => "#{this} refreshing" @@ -89,27 +72,8 @@ module.exports = class BaseController extends backbone.View show: ($el)-> $el ?= @$el - - if not $el.hasClass 'hideable' - $el.addClass 'hideable' - _.defer => @show $el - return - - if $el.hasClass 'hidden' - logger.verbose => "#{this} is showing \"#{$el.selector}\"" - $el.removeClass 'hidden' - $el.addClass 'hiding' - @trigger Event.animate.show.start, this, $el - _.defer => @show $el - return - - if $el.hasClass 'hiding' - $el.removeClass 'hiding' - @_onAnimationComplete $el, Duration.normal, => @show $el - return - - logger.verbose => "#{this} is finished showing #{$el.selector}" - @trigger Event.animate.show.finish, this, $el + $el.data 'target-visibility', 'visible' + _.delay (=> @_adjustTargetVisibility $el), Duration.snap unrender: -> @undelegateEvents() @@ -205,6 +169,67 @@ module.exports = class BaseController extends backbone.View # Private Methods ############################################################################## + _adjustTargetVisibility: ($el)-> + priorTarget = $el.data 'prior-target' + + target = $el.data 'target-visibility' + target ?= 'visible' + $el.data 'prior-target', target + + return if priorTarget is target and $el.data 'animating' + + current = 'visible' + current = 'hiding' if $el.hasClass 'hiding' + current = 'hidden' if $el.hasClass 'hidden' + + prior = $el.data 'prior-visibility' + $el.data 'prior-visibility', current + + if target is current and current isnt prior + $el.removeData 'prior-visibility' + $el.removeData 'prior-target' + if target is 'visible' + @trigger Event.animate.show.finish, this, $el + logger.verbose => "#{this} is finished showing #{$el.selector}" + else if target is 'hidden' + @trigger Event.animate.hide.finish, this, $el + logger.verbose => "#{this} is finished hiding #{$el.selector}" + + return if current is target + + if not $el.hasClass 'hideable' + $el.addClass 'hideable' + _.defer => @_adjustTargetVisibility $el + + if target is 'hidden' + if current is 'visible' + logger.verbose => "#{this} is hiding \"#{$el.selector}\"" + $el.addClass 'hiding' + @trigger Event.animate.hide.start, this, $el + + $el.data 'animating', true + @_onAnimationComplete $el, Duration.normal, => + $el.removeData 'animating' + @_adjustTargetVisibility $el + else if current is 'hiding' + $el.removeClass 'hiding' + $el.addClass 'hidden' + _.defer => @_adjustTargetVisibility $el + else if target is 'visible' + if current is 'hidden' + logger.verbose => "#{this} is showing \"#{$el.selector}\"" + $el.removeClass 'hidden' + $el.addClass 'hiding' + @trigger Event.animate.show.start, this, $el + _.defer => @_adjustTargetVisibility $el + else if current is 'hiding' + $el.removeClass 'hiding' + + $el.data 'animating', true + @_onAnimationComplete $el, Duration.normal, => + $el.removeData 'animating' + @_adjustTargetVisibility $el + _loadTemplate: (templateName)-> if templateName? @_template = views[templateName] diff --git a/src/coffee/controllers/markdown_section_controller.coffee b/src/coffee/controllers/markdown_section_controller.coffee index 166d8f20b..2b7e51017 100644 --- a/src/coffee/controllers/markdown_section_controller.coffee +++ b/src/coffee/controllers/markdown_section_controller.coffee @@ -82,6 +82,7 @@ module.exports = class MarkdownSectionController extends BaseController onSaveClicked: (event)-> event.preventDefault() + @model = @$textarea.val() @state = State.waiting @_endEditing() .then => @@ -94,8 +95,7 @@ module.exports = class MarkdownSectionController extends BaseController @state = State.viewing onTextChanged: (event)-> - event.preventDefault() - @model = @$textarea.val() + @tryRefresh() # Property Methods ############################################################################# @@ -139,10 +139,9 @@ module.exports = class MarkdownSectionController extends BaseController super onWillChangeModel: (oldModel, newModel)-> - result = super oldModel, newModel + _.defer => @_resetToDefaultState() @$textarea.val newModel - - return result + super oldModel, newModel # Backbone.View Overrides ###################################################################### @@ -173,14 +172,14 @@ module.exports = class MarkdownSectionController extends BaseController return result _updateSizer: -> - text = @model + text = @$textarea.val() if @model? text = text.replace /\n/g, '
' @$sizer.html text _updatePreview: -> - text = @model + text = @$textarea.val() if @model? text = @_convertWikiLinks text text = @_convertImageLinks text