simplify and refactor dialogs using event bus

This commit is contained in:
Nolan Lawson 2018-04-08 16:56:20 -07:00
parent bcc7fb47ef
commit eb8cd5f83d
26 changed files with 237 additions and 153 deletions

View file

@ -1,20 +1,27 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown
:closed
:title :title
background="var(--main-bg)" background="var(--main-bg)"
on:destroyDialog="destroy()"
> >
<GenericDialogList :items on:click="onClick(event)"/> <GenericDialogList :items on:click="onClick(event)"/>
</ModalDialog> </ModalDialog>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { store } from '../../_store/store' import { store } from '../../../_store/store'
import GenericDialogList from './GenericDialogList.html' import GenericDialogList from './GenericDialogList.html'
import { importDialogs } from '../../_utils/asyncModules' import { importDialogs } from '../../../_utils/asyncModules'
import { createDialogId } from '../helpers/createDialogId'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
export default { export default {
oncreate,
store: () => store,
data: () => ({
id: createDialogId()
}),
computed: { computed: {
items: (account) => ( items: (account) => (
[ [
@ -26,15 +33,9 @@ export default {
] ]
) )
}, },
components: {
ModalDialog,
GenericDialogList
},
store: () => store,
methods: { methods: {
async show() { show,
this.set({shown: true}) close,
},
async onClick() { async onClick() {
let account = this.get('account') let account = this.get('account')
this.store.setComposeData('dialog', { this.store.setComposeData('dialog', {
@ -42,8 +43,12 @@ export default {
}) })
let dialogs = await importDialogs() let dialogs = await importDialogs()
dialogs.showComposeDialog() dialogs.showComposeDialog()
this.set({closed: true}) this.close()
} }
} },
components: {
ModalDialog,
GenericDialogList
},
} }
</script> </script>

View file

@ -1,41 +1,37 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown
:closed
:title :title
background="var(--main-bg)" background="var(--main-bg)"
on:destroyDialog="destroy()"
> >
<ComposeBox realm="dialog" size="slim" autoFocus="true" /> <ComposeBox realm="dialog" size="slim" autoFocus="true" />
</ModalDialog> </ModalDialog>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import ComposeBox from '../compose/ComposeBox.html' import ComposeBox from '../../compose/ComposeBox.html'
import { on } from '../../_utils/eventBus' import { on } from '../../../_utils/eventBus'
import { show } from '../helpers/showDialog'
import { oncreate as onCreateDialog } from '../helpers/onCreateDialog'
import { close } from '../helpers/closeDialog'
export default { export default {
oncreate() { oncreate() {
on('postedStatus', this, this.onPostedStatus) on('postedStatus', this, this.onPostedStatus)
}, onCreateDialog.call(this)
components: {
ModalDialog,
ComposeBox
}, },
methods: { methods: {
async show() { show,
this.set({shown: true}) close,
},
onPostedStatus(realm) { onPostedStatus(realm) {
if (realm !== 'dialog') { if (realm !== 'dialog') {
return return
} }
try { this.close()
this.set({closed: true})
} catch (e) {
// TODO: this seems to error sometimes, not sure why
console.error(e)
}
} }
},
components: {
ModalDialog,
ComposeBox
} }
} }
</script> </script>

View file

@ -1,9 +1,7 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown background="var(--main-bg)"
:closed background="var(--main-bg)"
on:close="onClose()"
on:destroyDialog="destroy()"
> >
<form class="confirmation-dialog-form"> <form class="confirmation-dialog-form">
<p> <p>
@ -36,16 +34,23 @@
</style> </style>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { on } from '../../../_utils/eventBus'
import { oncreate as onCreateDialog } from '../helpers/onCreateDialog'
export default { export default {
components: { oncreate() {
ModalDialog on('destroyDialog', this, this.onDestroyDialog)
onCreateDialog.call(this)
}, },
methods: { methods: {
show() { show,
this.set({shown: true}) close,
}, onDestroyDialog(id) {
onClose() { if (id !== this.get('id')) {
return
}
if (this.get('positiveResult')) { if (this.get('positiveResult')) {
if (this.get('onPositive')) { if (this.get('onPositive')) {
this.get('onPositive')() this.get('onPositive')()
@ -58,11 +63,14 @@
}, },
onPositive() { onPositive() {
this.set({positiveResult: true}) this.set({positiveResult: true})
this.set({closed: true}) this.close()
}, },
onNegative() { onNegative() {
this.set({closed: true}) this.close()
} }
},
components: {
ModalDialog
} }
} }
</script> </script>

View file

@ -1,10 +1,8 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown
:closed
:title :title
background="var(--main-bg)" background="var(--main-bg)"
on:destroyDialog="destroy()"
> >
<div class="custom-emoji-container"> <div class="custom-emoji-container">
{{#if emojis.length}} {{#if emojis.length}}
@ -60,10 +58,14 @@
</style> </style>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { store } from '../../_store/store' import { store } from '../../../_store/store'
import { insertEmoji } from '../../_actions/emoji' import { insertEmoji } from '../../../_actions/emoji'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
export default { export default {
oncreate,
components: { components: {
ModalDialog ModalDialog
}, },
@ -77,12 +79,11 @@
} }
}, },
methods: { methods: {
async show() { show,
this.set({shown: true}) close,
},
onClickEmoji(emoji) { onClickEmoji(emoji) {
insertEmoji(this.get('realm'), emoji) insertEmoji(this.get('realm'), emoji)
this.set({closed: true}) this.close()
} }
} }
} }

View file

@ -1,9 +1,9 @@
<ModalDialog :label <ModalDialog
:shown :id
background="var(--muted-modal-bg)" :label
muted="true" background="var(--muted-modal-bg)"
className="image-modal-dialog" muted="true"
on:destroyDialog="destroy()" className="image-modal-dialog"
> >
{{#if type === 'gifv'}} {{#if type === 'gifv'}}
<AutoplayVideo <AutoplayVideo
@ -32,17 +32,18 @@
</style> </style>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import AutoplayVideo from '../AutoplayVideo.html' import AutoplayVideo from '../../AutoplayVideo.html'
import { show } from '../helpers/showDialog'
import { oncreate } from '../helpers/onCreateDialog'
export default { export default {
oncreate,
components: { components: {
ModalDialog, ModalDialog,
AutoplayVideo AutoplayVideo
}, },
methods: { methods: {
async show() { show
this.set({shown: true})
}
} }
} }
</script> </script>

View file

@ -116,10 +116,21 @@
</style> </style>
<script> <script>
import A11yDialog from 'a11y-dialog' import A11yDialog from 'a11y-dialog'
import { classname } from '../../../_utils/classname'
import { classname } from '../../_utils/classname' import { on, emit } from '../../../_utils/eventBus'
export default { export default {
oncreate() {
let dialogElement = this.refs.node.parentElement
this._a11yDialog = new A11yDialog(dialogElement)
this._a11yDialog.on('hide', () => {
this._a11yDialog.destroy()
emit('destroyDialog', this.get('id'))
requestAnimationFrame(() => document.body.removeChild(dialogElement))
})
on('showDialog', this, this.showDialog)
on('closeDialog', this, this.closeDialog)
},
data: () => ({ data: () => ({
// don't animate if we're showing a modal dialog on top of another modal dialog. it looks ugly // don't animate if we're showing a modal dialog on top of another modal dialog. it looks ugly
shouldAnimate: !process.browser || document.getElementsByClassName('modal-dialog').length < 2 shouldAnimate: !process.browser || document.getElementsByClassName('modal-dialog').length < 2
@ -142,31 +153,24 @@
) )
} }
}, },
oncreate() { methods: {
let dialogElement = this.refs.node.parentElement showDialog(id) {
let a11yDialog = new A11yDialog(dialogElement) if (this.get('id') !== id) {
a11yDialog.on('hide', () => { return
a11yDialog.destroy()
this.fire('close')
console.log('destroyDialog()')
this.fire('destroyDialog')
requestAnimationFrame(() => document.body.removeChild(dialogElement))
})
this.observe('shown', shown => {
if (shown) {
a11yDialog.show()
requestAnimationFrame(() => {
this.set({ fadedIn: true })
})
} }
}) this._a11yDialog.show()
this.observe('closed', closed => { requestAnimationFrame(() => {
if (closed) { this.set({ fadedIn: true })
setTimeout(() => { // use setTimeout to workaround svelte timing issue })
a11yDialog.hide() },
}, 0) closeDialog(id) {
if (this.get('id') !== id) {
return
} }
}) setTimeout(() => { // use setTimeout to workaround svelte timing issue
this._a11yDialog.hide()
}, 0)
}
} }
} }
</script> </script>

View file

@ -1,21 +1,23 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown
:closed
:title :title
background="var(--main-bg)" background="var(--main-bg)"
on:destroyDialog="destroy()"
> >
<GenericDialogList :items on:click="onClick(event)" /> <GenericDialogList :items on:click="onClick(event)" />
</ModalDialog> </ModalDialog>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { store } from '../../_store/store' import { store } from '../../../_store/store'
import { POST_PRIVACY_OPTIONS } from '../../_static/statuses' import { POST_PRIVACY_OPTIONS } from '../../../_static/statuses'
import { setPostPrivacy } from '../../_actions/postPrivacy' import { setPostPrivacy } from '../../../_actions/postPrivacy'
import GenericDialogList from './GenericDialogList.html' import GenericDialogList from './GenericDialogList.html'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
export default { export default {
oncreate,
components: { components: {
ModalDialog, ModalDialog,
GenericDialogList GenericDialogList
@ -25,12 +27,11 @@
postPrivacyOptions: POST_PRIVACY_OPTIONS postPrivacyOptions: POST_PRIVACY_OPTIONS
}), }),
methods: { methods: {
async show() { show,
this.set({shown: true}) close,
},
onClick(item) { onClick(item) {
setPostPrivacy(this.get('realm'), item.key) setPostPrivacy(this.get('realm'), item.key)
this.set({closed: true}) this.close()
} }
}, },
computed: { computed: {

View file

@ -1,21 +1,23 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown
:closed
:title :title
background="var(--main-bg)" background="var(--main-bg)"
on:destroyDialog="destroy()"
> >
<GenericDialogList :items on:click="onClick(event)"/> <GenericDialogList :items on:click="onClick(event)"/>
</ModalDialog> </ModalDialog>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { store } from '../../_store/store' import { store } from '../../../_store/store'
import GenericDialogList from './GenericDialogList.html' import GenericDialogList from './GenericDialogList.html'
import { setAccountFollowed } from '../../_actions/follow' import { setAccountFollowed } from '../../../_actions/follow'
import { doDeleteStatus } from '../../_actions/delete' import { doDeleteStatus } from '../../../_actions/delete'
import { show } from '../helpers/showDialog'
import { close } from '../helpers/closeDialog'
import { oncreate } from '../helpers/onCreateDialog'
export default { export default {
oncreate,
computed: { computed: {
relationship: ($currentAccountRelationship) => $currentAccountRelationship, relationship: ($currentAccountRelationship) => $currentAccountRelationship,
account: ($currentAccountProfile) => $currentAccountProfile, account: ($currentAccountProfile) => $currentAccountProfile,
@ -55,19 +57,18 @@ export default {
}, },
store: () => store, store: () => store,
methods: { methods: {
async show() { show,
this.set({shown: true}) close,
},
async onClick(item) { async onClick(item) {
if (item.key === 'follow') { if (item.key === 'follow') {
let accountId = this.get('accountId') let accountId = this.get('accountId')
let following = this.get('following') let following = this.get('following')
await setAccountFollowed(accountId, !following, true) await setAccountFollowed(accountId, !following, true)
this.set({closed: true}) this.close()
} else if (item.key === 'delete') { } else if (item.key === 'delete') {
let statusId = this.get('statusId') let statusId = this.get('statusId')
await doDeleteStatus(statusId) await doDeleteStatus(statusId)
this.set({closed: true}) this.close()
} }
} }
} }

View file

@ -1,15 +1,14 @@
<ModalDialog <ModalDialog
:id
:label :label
:shown
background="var(--muted-modal-bg)" background="var(--muted-modal-bg)"
muted="true" muted="true"
className="video-modal-dialog" className="video-modal-dialog"
on:destroyDialog="destroy()"
> >
<video poster="{{poster}}" <video :poster
src="{{src}}" :src
width="{{width}}" :width
height="{{height}}" :height
aria-label="Video: {{description || ''}}" aria-label="Video: {{description || ''}}"
controls controls
/> />
@ -24,15 +23,16 @@
</style> </style>
<script> <script>
import ModalDialog from './ModalDialog.html' import ModalDialog from './ModalDialog.html'
import { show } from '../helpers/showDialog'
import { oncreate } from '../helpers/onCreateDialog'
export default { export default {
oncreate,
components: { components: {
ModalDialog ModalDialog
}, },
methods: { methods: {
async show() { show
this.set({shown: true})
}
} }
} }
</script> </script>

View file

@ -1,10 +1,12 @@
import AccountProfileOptionsDialog from './AccountProfileOptionsDialog.html' import AccountProfileOptionsDialog from '../components/AccountProfileOptionsDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showAccountProfileOptionsDialog (account) { export function showAccountProfileOptionsDialog (account) {
let dialog = new AccountProfileOptionsDialog({ let dialog = new AccountProfileOptionsDialog({
target: createDialogElement(), target: createDialogElement(),
data: { data: {
id: createDialogId(),
label: 'Profile options dialog', label: 'Profile options dialog',
title: '', title: '',
account: account account: account

View file

@ -0,0 +1,14 @@
import ComposeDialog from '../components/ComposeDialog.html'
import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showComposeDialog () {
let dialog = new ComposeDialog({
target: createDialogElement(),
data: {
id: createDialogId(),
label: 'Compose dialog'
}
})
dialog.show()
}

View file

@ -1,10 +1,12 @@
import ConfirmationDialog from './ConfirmationDialog.html' import ConfirmationDialog from '../components/ConfirmationDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showConfirmationDialog (options) { export function showConfirmationDialog (options) {
let dialog = new ConfirmationDialog({ let dialog = new ConfirmationDialog({
target: createDialogElement(), target: createDialogElement(),
data: Object.assign({ data: Object.assign({
id: createDialogId(),
label: 'Confirmation dialog' label: 'Confirmation dialog'
}, options) }, options)
}) })

View file

@ -1,10 +1,12 @@
import EmojiDialog from './EmojiDialog.html' import EmojiDialog from '../components/EmojiDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showEmojiDialog (realm) { export function showEmojiDialog (realm) {
let emojiDialog = new EmojiDialog({ let emojiDialog = new EmojiDialog({
target: createDialogElement(), target: createDialogElement(),
data: { data: {
id: createDialogId(),
label: 'Emoji dialog', label: 'Emoji dialog',
title: 'Custom emoji', title: 'Custom emoji',
realm realm

View file

@ -1,10 +1,12 @@
import ImageDialog from './ImageDialog.html' import ImageDialog from '../components/ImageDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showImageDialog (poster, src, type, width, height, description) { export function showImageDialog (poster, src, type, width, height, description) {
let imageDialog = new ImageDialog({ let imageDialog = new ImageDialog({
target: createDialogElement(), target: createDialogElement(),
data: { data: {
id: createDialogId(),
label: 'Image dialog', label: 'Image dialog',
poster, poster,
src, src,

View file

@ -1,10 +1,12 @@
import PostPrivacyDialog from './PostPrivacyDialog.html' import PostPrivacyDialog from '../components/PostPrivacyDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showPostPrivacyDialog (realm) { export function showPostPrivacyDialog (realm) {
let dialog = new PostPrivacyDialog({ let dialog = new PostPrivacyDialog({
target: createDialogElement(), target: createDialogElement(),
data: { data: {
id: createDialogId(),
label: 'Post privacy dialog', label: 'Post privacy dialog',
title: 'Post privacy', title: 'Post privacy',
realm: realm realm: realm

View file

@ -1,10 +1,12 @@
import StatusOptionsDialog from './StatusOptionsDialog.html' import StatusOptionsDialog from '../components/StatusOptionsDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showStatusOptionsDialog (statusId) { export function showStatusOptionsDialog (statusId) {
let dialog = new StatusOptionsDialog({ let dialog = new StatusOptionsDialog({
target: createDialogElement(), target: createDialogElement(),
data: { data: {
id: createDialogId(),
label: 'Status options dialog', label: 'Status options dialog',
title: '', title: '',
statusId: statusId statusId: statusId

View file

@ -1,10 +1,12 @@
import VideoDialog from './VideoDialog.html' import VideoDialog from '../components/VideoDialog.html'
import { createDialogElement } from './createDialogElement' import { createDialogElement } from '../helpers/createDialogElement'
import { createDialogId } from '../helpers/createDialogId'
export function showVideoDialog (poster, src, width, height, description) { export function showVideoDialog (poster, src, width, height, description) {
let videoDialog = new VideoDialog({ let videoDialog = new VideoDialog({
target: createDialogElement(), target: createDialogElement(),
data: { data: {
id: createDialogId(),
label: 'Video dialog', label: 'Video dialog',
poster, poster,
src, src,

View file

@ -1,8 +1,8 @@
export * from './showConfirmationDialog' export * from './creators/showConfirmationDialog'
export * from './showImageDialog' export * from './creators/showImageDialog'
export * from './showVideoDialog' export * from './creators/showVideoDialog'
export * from './showEmojiDialog' export * from './creators/showEmojiDialog'
export * from './showPostPrivacyDialog' export * from './creators/showPostPrivacyDialog'
export * from './showStatusOptionsDialog' export * from './creators/showStatusOptionsDialog'
export * from './showComposeDialog' export * from './creators/showComposeDialog'
export * from './showAccountProfileOptionsDialog' export * from './creators/showAccountProfileOptionsDialog'

View file

@ -0,0 +1,6 @@
import { emit } from '../../../_utils/eventBus'
export function close () {
let id = this.get('id')
emit('closeDialog', id)
}

View file

@ -0,0 +1,5 @@
let count = -1
export function createDialogId () {
return ++count
}

View file

@ -0,0 +1,12 @@
import { on } from '../../../_utils/eventBus'
function onDestroy (id) {
if (this.get('id') !== id) {
return
}
this.destroy()
}
export function oncreate () {
on('destroyDialog', this, onDestroy)
}

View file

@ -0,0 +1,6 @@
import { emit } from '../../../_utils/eventBus'
export function show () {
let id = this.get('id')
emit('showDialog', id)
}

View file

@ -1,12 +0,0 @@
import ComposeDialog from './ComposeDialog.html'
import { createDialogElement } from './createDialogElement'
export function showComposeDialog () {
let dialog = new ComposeDialog({
target: createDialogElement(),
data: {
label: 'Compose dialog'
}
})
dialog.show()
}

View file

@ -3,6 +3,7 @@ import {
scrollContainerToTop scrollContainerToTop
} from '../utils' } from '../utils'
import { foobarRole } from '../roles' import { foobarRole } from '../roles'
import { Selector as $ } from 'testcafe'
fixture`108-compose-dialog.js` fixture`108-compose-dialog.js`
.page`http://localhost:4002` .page`http://localhost:4002`
@ -27,3 +28,24 @@ test('can compose using a dialog', async t => {
.click(showMoreButton) .click(showMoreButton)
await t.expect(getNthStatus(0).innerText).contains('hello from the modal', {timeout: 20000}) await t.expect(getNthStatus(0).innerText).contains('hello from the modal', {timeout: 20000})
}) })
test('can use emoji dialog within compose dialog', async t => {
await t.useRole(foobarRole)
await scrollToStatus(t, 15)
await t.expect(composeButton.getAttribute('aria-label')).eql('Compose')
await sleep(2000)
await t.click(composeButton)
.click(modalDialog.find('.compose-box-toolbar button:nth-child(1)'))
.click($('button img[title=":blobpats:"]'))
.expect(modalDialog.find('.compose-box-input').value).eql(':blobpats: ')
.click(modalDialog.find('.compose-box-button-compose'))
.expect(modalDialog.exists).notOk()
await sleep(5000)
await scrollToTopOfTimeline(t)
await t.hover(getNthStatus(0))
await scrollContainerToTop()
await t
.expect(showMoreButton.innerText).contains('Show 1 more')
.click(showMoreButton)
await t.expect(getNthStatus(0).find('img[alt=":blobpats:"]').exists).ok({timeout: 20000})
})