mirror of
https://github.com/atom/atom.git
synced 2026-01-23 13:58:08 -05:00
Ensure modal dialogs are presented in a coherent order
Modal dialogs can be presented while other modal dialogs are already being displayed. Previously, dialogs were always displayed in the order they were requested. But say you have two untitled buffers in a pane and you close all items… You'll display prompt dialogs for both buffers asking the user if they want to save. If the user answers yes to the first dialog, they should see the path selection dialog before they see the save prompt for the second buffer. This commit uses a stack of queues to store deferred dialogs and allow dialogs presented by the dismissal of another dialog to take precedence over other pending dialogs.
This commit is contained in:
committed by
probablycorey
parent
48c693d756
commit
f0398f2331
@@ -141,3 +141,72 @@ describe "the `atom` global", ->
|
||||
|
||||
runs ->
|
||||
expect(versionHandler.argsForCall[0][0]).toMatch /^\d+\.\d+(\.\d+)?$/
|
||||
|
||||
describe "modal native dialogs", ->
|
||||
beforeEach ->
|
||||
spyOn(atom, 'sendMessageToBrowserProcess')
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation = (buttonText) ->
|
||||
labels = @argsForCall[0][1][2...]
|
||||
callbacks = @argsForCall[0][2]
|
||||
@reset()
|
||||
callbacks[labels.indexOf(buttonText)]()
|
||||
|
||||
atom.sendMessageToBrowserProcess.simulatePathSelection = (path) ->
|
||||
callback = @argsForCall[0][2]
|
||||
@reset()
|
||||
callback(path)
|
||||
|
||||
it "only presents one native dialog at a time", ->
|
||||
confirmHandler = jasmine.createSpy("confirmHandler")
|
||||
selectPathHandler = jasmine.createSpy("selectPathHandler")
|
||||
|
||||
atom.confirm "Are you happy?", "really, truly happy?", "Yes", confirmHandler, "No"
|
||||
atom.confirm "Are you happy?", "really, truly happy?", "Yes", confirmHandler, "No"
|
||||
atom.showSaveDialog(selectPathHandler)
|
||||
atom.showSaveDialog(selectPathHandler)
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation("Yes")
|
||||
expect(confirmHandler).toHaveBeenCalled()
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation("No")
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
atom.sendMessageToBrowserProcess.simulatePathSelection('/selected/path')
|
||||
expect(selectPathHandler).toHaveBeenCalledWith('/selected/path')
|
||||
selectPathHandler.reset()
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
|
||||
it "prioritizes dialogs presented as the result of dismissing other dialogs before any previously deferred dialogs", ->
|
||||
atom.confirm "A1", "", "Next", ->
|
||||
atom.confirm "B1", "", "Next", ->
|
||||
atom.confirm "C1", "", "Next", ->
|
||||
atom.confirm "C2", "", "Next", ->
|
||||
atom.confirm "B2", "", "Next", ->
|
||||
atom.confirm "A2", "", "Next", ->
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A1"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "B1"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "C1"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "C2"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "B2"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
expect(atom.sendMessageToBrowserProcess.callCount).toBe 1
|
||||
expect(atom.sendMessageToBrowserProcess.argsForCall[0][1][0]).toBe "A2"
|
||||
atom.sendMessageToBrowserProcess.simulateConfirmation('Next')
|
||||
|
||||
@@ -83,6 +83,8 @@ afterEach ->
|
||||
window.git = null
|
||||
$('#jasmine-content').empty()
|
||||
ensureNoPathSubscriptions()
|
||||
atom.pendingModals = [[]]
|
||||
atom.presentingModal = false
|
||||
waits(0) # yield to ui thread to make screen update more frequently
|
||||
|
||||
window.loadPackage = (name, options) ->
|
||||
|
||||
@@ -16,6 +16,8 @@ _.extend atom,
|
||||
loadedPackages: []
|
||||
activatedAtomPackages: []
|
||||
atomPackageStates: {}
|
||||
presentingModal: false
|
||||
pendingModals: [[]]
|
||||
|
||||
getPathToOpen: ->
|
||||
@getWindowState('pathToOpen') ? window.location.params.pathToOpen
|
||||
@@ -102,15 +104,49 @@ _.extend atom,
|
||||
@sendMessageToBrowserProcess('newWindow', args)
|
||||
|
||||
confirm: (message, detailedMessage, buttonLabelsAndCallbacks...) ->
|
||||
args = [message, detailedMessage]
|
||||
callbacks = []
|
||||
while buttonLabelsAndCallbacks.length
|
||||
args.push(buttonLabelsAndCallbacks.shift())
|
||||
callbacks.push(buttonLabelsAndCallbacks.shift())
|
||||
@sendMessageToBrowserProcess('confirm', args, callbacks)
|
||||
wrapCallback = (callback) => => @dismissModal(callback)
|
||||
@presentModal =>
|
||||
args = [message, detailedMessage]
|
||||
callbacks = []
|
||||
while buttonLabelsAndCallbacks.length
|
||||
do =>
|
||||
buttonLabel = buttonLabelsAndCallbacks.shift()
|
||||
buttonCallback = buttonLabelsAndCallbacks.shift()
|
||||
args.push(buttonLabel)
|
||||
callbacks.push(=> @dismissModal(buttonCallback))
|
||||
@sendMessageToBrowserProcess('confirm', args, callbacks)
|
||||
|
||||
showSaveDialog: (callback) ->
|
||||
@sendMessageToBrowserProcess('showSaveDialog', [], callback)
|
||||
@presentModal =>
|
||||
@sendMessageToBrowserProcess('showSaveDialog', [], (path) => @dismissModal(callback, path))
|
||||
|
||||
presentModal: (fn) ->
|
||||
if @presentingModal
|
||||
@pushPendingModal(fn)
|
||||
else
|
||||
@presentingModal = true
|
||||
fn()
|
||||
|
||||
dismissModal: (fn, args...) ->
|
||||
@pendingModals.push([]) # prioritize any modals presented during dismiss callback
|
||||
fn?(args...)
|
||||
@presentingModal = false
|
||||
@presentModal(fn) if fn = @shiftPendingModal()
|
||||
|
||||
pushPendingModal: (fn) ->
|
||||
# pendingModals is a stack of queues. enqueue to top of stack.
|
||||
stackSize = @pendingModals.length
|
||||
@pendingModals[stackSize - 1].push(fn)
|
||||
|
||||
shiftPendingModal: ->
|
||||
# pop pendingModals stack if its top queue is empty, otherwise shift off the topmost queue
|
||||
stackSize = @pendingModals.length
|
||||
currentQueueSize = @pendingModals[stackSize - 1].length
|
||||
if stackSize > 1 and currentQueueSize == 0
|
||||
@pendingModals.pop()
|
||||
@shiftPendingModal()
|
||||
else
|
||||
@pendingModals[stackSize - 1].shift()
|
||||
|
||||
toggleDevTools: ->
|
||||
@sendMessageToBrowserProcess('toggleDevTools')
|
||||
|
||||
Reference in New Issue
Block a user