Skip to content

Commit

Permalink
dashboard: add modal open and close events (#1664)
Browse files Browse the repository at this point in the history
* add modal-open and modal-closed events, call hideAllPanels() when modal is closed

* Document dashboard:modal-open and dashboard:modal-closed events
  • Loading branch information
arturi authored Jun 17, 2019
1 parent 4a98bdc commit 07da784
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 129 deletions.
260 changes: 131 additions & 129 deletions packages/@uppy/dashboard/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,28 +143,28 @@ module.exports = class Dashboard extends Plugin {
this.removeTarget = this.removeTarget.bind(this)
this.hideAllPanels = this.hideAllPanels.bind(this)
this.showPanel = this.showPanel.bind(this)
this.handlePopState = this.handlePopState.bind(this)
this.toggleFileCard = this.toggleFileCard.bind(this)
this.toggleAddFilesPanel = this.toggleAddFilesPanel.bind(this)

this.initEvents = this.initEvents.bind(this)
this.handlePopState = this.handlePopState.bind(this)
this.handleKeyDownInModal = this.handleKeyDownInModal.bind(this)
this.handleKeyDownInInline = this.handleKeyDownInInline.bind(this)
this.handleFileAdded = this.handleFileAdded.bind(this)
this.handleComplete = this.handleComplete.bind(this)
this.handleClickOutside = this.handleClickOutside.bind(this)
this.toggleFileCard = this.toggleFileCard.bind(this)
this.toggleAddFilesPanel = this.toggleAddFilesPanel.bind(this)
this.handlePaste = this.handlePaste.bind(this)
this.handlePasteOnBody = this.handlePasteOnBody.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
this.render = this.render.bind(this)
this.install = this.install.bind(this)

this.handleDragOver = this.handleDragOver.bind(this)
this.handleDragLeave = this.handleDragLeave.bind(this)
this.handleDrop = this.handleDrop.bind(this)

this.superFocusOnEachUpdate = this.superFocusOnEachUpdate.bind(this)
this.recordIfFocusedOnUppyRecently = this.recordIfFocusedOnUppyRecently.bind(this)

this.render = this.render.bind(this)
this.install = this.install.bind(this)

this.superFocus = createSuperFocus()
this.ifFocusedOnUppyRecently = false

Expand Down Expand Up @@ -234,41 +234,6 @@ module.exports = class Dashboard extends Plugin {
})
}

requestCloseModal () {
if (this.opts.onRequestCloseModal) {
return this.opts.onRequestCloseModal()
}
return this.closeModal()
}

updateBrowserHistory () {
// Ensure history state does not already contain our modal name to avoid double-pushing
if (!history.state || !history.state[this.modalName]) {
// Push to history so that the page is not lost on browser back button press
history.pushState({
...history.state,
[this.modalName]: true
}, '')
}

// Listen for back button presses
window.addEventListener('popstate', this.handlePopState, false)
}

handlePopState (event) {
// Close the modal if the history state no longer contains our modal name
if (this.isModalOpen() && (!event.state || !event.state[this.modalName])) {
this.closeModal({ manualClose: false })
}

// When the browser back button is pressed and uppy is now the latest entry in the history but the modal is closed, fix the history by removing the uppy history entry
// This occurs when another entry is added into the history state while the modal is open, and then the modal gets manually closed
// Solves PR #575 (https://github.com/transloadit/uppy/pull/575)
if (!this.isModalOpen() && event.state && event.state[this.modalName]) {
history.go(-1)
}
}

openModal () {
const { promise, resolve } = createPromise()
// save scroll position
Expand Down Expand Up @@ -303,6 +268,8 @@ module.exports = class Dashboard extends Plugin {
// handle ESC and TAB keys in modal dialog
document.addEventListener('keydown', this.handleKeyDownInModal)

this.uppy.emit('dashboard:modal-open')

return promise
}

Expand Down Expand Up @@ -364,47 +331,34 @@ module.exports = class Dashboard extends Plugin {
}
}

this.uppy.emit('dashboard:modal-closed')

return promise
}

isModalOpen () {
return !this.getPluginState().isHidden || false
}

handleKeyDownInModal (event) {
// close modal on esc key press
if (event.keyCode === ESC_KEY) this.requestCloseModal(event)
// trap focus on tab key press
if (event.keyCode === TAB_KEY) trapFocus.forModal(event, this.getPluginState().activeOverlayType, this.el)
}

handleClickOutside () {
if (this.opts.closeModalOnClickOutside) this.requestCloseModal()
requestCloseModal () {
if (this.opts.onRequestCloseModal) {
return this.opts.onRequestCloseModal()
}
return this.closeModal()
}

handlePaste (event) {
// 1. Let any acquirer plugin (Url/Webcam/etc.) handle pastes to the root
this.uppy.iteratePlugins((plugin) => {
if (plugin.type === 'acquirer') {
// Every Plugin with .type acquirer can define handleRootPaste(event)
plugin.handleRootPaste && plugin.handleRootPaste(event)
}
})

// 2. Add all dropped files
const files = toArray(event.clipboardData.files)
files.forEach((file) => {
this.uppy.log('[Dashboard] File pasted')
this.addFile(file)
toggleFileCard (fileId) {
this.setPluginState({
fileCardFor: fileId || null,
activeOverlayType: fileId ? 'FileCard' : null
})
}

handleInputChange (event) {
event.preventDefault()
const files = toArray(event.target.files)
files.forEach((file) =>
this.addFile(file)
)
toggleAddFilesPanel (show) {
this.setPluginState({
showAddFilesPanel: show,
activeOverlayType: show ? 'AddFiles' : null
})
}

addFile (file) {
Expand Down Expand Up @@ -467,6 +421,83 @@ module.exports = class Dashboard extends Plugin {
clearTimeout(this.makeDashboardInsidesVisibleAnywayTimeout)
}

// Records whether we have been interacting with uppy right now, which is then used to determine whether state updates should trigger a refocusing.
recordIfFocusedOnUppyRecently (event) {
if (this.el.contains(event.target)) {
this.ifFocusedOnUppyRecently = true
} else {
this.ifFocusedOnUppyRecently = false
// ___Why run this.superFocus.cancel here when it already runs in superFocusOnEachUpdate?
// Because superFocus is debounced, when we move from Uppy to some other element on the page,
// previously run superFocus sometimes hits and moves focus back to Uppy.
this.superFocus.cancel()
}
}

updateBrowserHistory () {
// Ensure history state does not already contain our modal name to avoid double-pushing
if (!history.state || !history.state[this.modalName]) {
// Push to history so that the page is not lost on browser back button press
history.pushState({
...history.state,
[this.modalName]: true
}, '')
}

// Listen for back button presses
window.addEventListener('popstate', this.handlePopState, false)
}

handlePopState (event) {
// Close the modal if the history state no longer contains our modal name
if (this.isModalOpen() && (!event.state || !event.state[this.modalName])) {
this.closeModal({ manualClose: false })
}

// When the browser back button is pressed and uppy is now the latest entry in the history but the modal is closed, fix the history by removing the uppy history entry
// This occurs when another entry is added into the history state while the modal is open, and then the modal gets manually closed
// Solves PR #575 (https://github.com/transloadit/uppy/pull/575)
if (!this.isModalOpen() && event.state && event.state[this.modalName]) {
history.go(-1)
}
}

handleKeyDownInModal (event) {
// close modal on esc key press
if (event.keyCode === ESC_KEY) this.requestCloseModal(event)
// trap focus on tab key press
if (event.keyCode === TAB_KEY) trapFocus.forModal(event, this.getPluginState().activeOverlayType, this.el)
}

handleClickOutside () {
if (this.opts.closeModalOnClickOutside) this.requestCloseModal()
}

handlePaste (event) {
// 1. Let any acquirer plugin (Url/Webcam/etc.) handle pastes to the root
this.uppy.iteratePlugins((plugin) => {
if (plugin.type === 'acquirer') {
// Every Plugin with .type acquirer can define handleRootPaste(event)
plugin.handleRootPaste && plugin.handleRootPaste(event)
}
})

// 2. Add all dropped files
const files = toArray(event.clipboardData.files)
files.forEach((file) => {
this.uppy.log('[Dashboard] File pasted')
this.addFile(file)
})
}

handleInputChange (event) {
event.preventDefault()
const files = toArray(event.target.files)
files.forEach((file) =>
this.addFile(file)
)
}

handleDragOver (event) {
event.preventDefault()
event.stopPropagation()
Expand Down Expand Up @@ -516,6 +547,31 @@ module.exports = class Dashboard extends Plugin {
})
}

handleKeyDownInInline (event) {
// Trap focus on tab key press.
if (event.keyCode === TAB_KEY) trapFocus.forInline(event, this.getPluginState().activeOverlayType, this.el)
}

// ___Why do we listen to the 'paste' event on a document instead of onPaste={props.handlePaste} prop, or this.el.addEventListener('paste')?
// Because (at least) Chrome doesn't handle paste if focus is on some button, e.g. 'My Device'.
// => Therefore, the best option is to listen to all 'paste' events, and only react to them when we are focused on our particular Uppy instance.
// ___Why do we still need onPaste={props.handlePaste} for the DashboardUi?
// Because if we click on the 'Drop files here' caption e.g., `document.activeElement` will be 'body'. Which means our standard determination of whether we're pasting into our Uppy instance won't work.
// => Therefore, we need a traditional onPaste={props.handlePaste} handler too.
handlePasteOnBody (event) {
const isFocusInOverlay = this.el.contains(document.activeElement)
if (isFocusInOverlay) {
this.handlePaste(event)
}
}

handleComplete ({ failed, uploadID }) {
if (this.opts.closeAfterFinish && failed.length === 0) {
// All uploads are done
this.requestCloseModal()
}
}

initEvents () {
// Modal open button
const showModalTrigger = findAllDOMElements(this.opts.trigger)
Expand All @@ -531,7 +587,8 @@ module.exports = class Dashboard extends Plugin {
document.addEventListener('paste', this.handlePasteOnBody)

this.uppy.on('plugin-remove', this.removeTarget)
this.uppy.on('file-added', this.handleFileAdded)
this.uppy.on('file-added', this.hideAllPanels)
this.uppy.on('dashboard:modal-closed', this.hideAllPanels)
this.uppy.on('complete', this.handleComplete)

// ___Why fire on capture?
Expand All @@ -544,48 +601,6 @@ module.exports = class Dashboard extends Plugin {
}
}

handleKeyDownInInline (event) {
// Trap focus on tab key press.
if (event.keyCode === TAB_KEY) trapFocus.forInline(event, this.getPluginState().activeOverlayType, this.el)
}

// Records whether we have been interacting with uppy right now, which is then used to determine whether state updates should trigger a refocusing.
recordIfFocusedOnUppyRecently (event) {
if (this.el.contains(event.target)) {
this.ifFocusedOnUppyRecently = true
} else {
this.ifFocusedOnUppyRecently = false
// ___Why run this.superFocus.cancel here when it already runs in superFocusOnEachUpdate?
// Because superFocus is debounced, when we move from Uppy to some other element on the page,
// previously run superFocus sometimes hits and moves focus back to Uppy.
this.superFocus.cancel()
}
}

// ___Why do we listen to the 'paste' event on a document instead of onPaste={props.handlePaste} prop, or this.el.addEventListener('paste')?
// Because (at least) Chrome doesn't handle paste if focus is on some button, e.g. 'My Device'.
// => Therefore, the best option is to listen to all 'paste' events, and only react to them when we are focused on our particular Uppy instance.
// ___Why do we still need onPaste={props.handlePaste} for the DashboardUi?
// Because if we click on the 'Drop files here' caption e.g., `document.activeElement` will be 'body'. Which means our standard determination of whether we're pasting into our Uppy instance won't work.
// => Therefore, we need a traditional onPaste={props.handlePaste} handler too.
handlePasteOnBody (event) {
const isFocusInOverlay = this.el.contains(document.activeElement)
if (isFocusInOverlay) {
this.handlePaste(event)
}
}

handleFileAdded () {
this.hideAllPanels()
}

handleComplete ({ failed, uploadID }) {
if (this.opts.closeAfterFinish && failed.length === 0) {
// All uploads are done
this.requestCloseModal()
}
}

removeEvents () {
const showModalTrigger = findAllDOMElements(this.opts.trigger)
if (!this.opts.inline && showModalTrigger) {
Expand All @@ -597,7 +612,8 @@ module.exports = class Dashboard extends Plugin {

window.removeEventListener('popstate', this.handlePopState, false)
this.uppy.off('plugin-remove', this.removeTarget)
this.uppy.off('file-added', this.handleFileAdded)
this.uppy.off('file-added', this.hideAllPanels)
this.uppy.off('dashboard:modal-closed', this.hideAllPanels)
this.uppy.off('complete', this.handleComplete)

document.removeEventListener('focus', this.recordIfFocusedOnUppyRecently)
Expand All @@ -608,20 +624,6 @@ module.exports = class Dashboard extends Plugin {
}
}

toggleFileCard (fileId) {
this.setPluginState({
fileCardFor: fileId || null,
activeOverlayType: fileId ? 'FileCard' : null
})
}

toggleAddFilesPanel (show) {
this.setPluginState({
showAddFilesPanel: show,
activeOverlayType: show ? 'AddFiles' : null
})
}

superFocusOnEachUpdate () {
const isFocusInUppy = this.el.contains(document.activeElement)
// When focus is lost on the page (== focus is on body for most browsers, or focus is null for IE11)
Expand Down
16 changes: 16 additions & 0 deletions website/src/docs/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,19 @@ if ( dashboard.isModalOpen() ) {
dashboard.closeModal()
}
```

## Events

### `dashboard:modal-open`

Fired when the Dashboard modal is open.

```js
uppy.on('dashboard:modal-open', () => {
console.log('Modal is open')
})
```

### `dashboard:modal-closed`

Fired when the Dashboard modal is closed.

0 comments on commit 07da784

Please sign in to comment.