reblock
Version:
Build interactive Slack surfaces with React
166 lines (165 loc) • 4.07 kB
JavaScript
import { createContainer, render, Root } from '../renderer'
import { jsxToBlocks } from '../jsx/blocks'
import { activeModals, activeRoots, ensureEventRegistered } from '../events'
import { blocks } from './blocks'
export class ModalRoot extends Root {
client
args
modalArgs
onSubmit
onClose
resolve
reject
viewID
existingViewID
get isOpen() {
return !!this.viewID
}
constructor(client, args, modalArgs, onSubmit, onClose, resolve, reject) {
super()
this.client = client
this.args = args
this.modalArgs = modalArgs
this.onSubmit = onSubmit
this.onClose = onClose
this.resolve = resolve
this.reject = reject
}
async publish() {
try {
activeRoots.add(this)
const children = this.getChildren()
const view = {
...this.modalArgs,
type: 'modal',
blocks: jsxToBlocks(children)[0],
notify_on_close: true,
callback_id: 'reblock',
}
if (!this.viewID) {
this.viewID = (async () => {
const result = await this.client.views.open({
...this.args,
view,
})
if (!result.ok || !result.view?.id) {
const error = new Error('Failed to open modal')
if (this.reject) {
this.reject(error)
}
throw error
}
if (this.resolve) {
this.resolve(result.view.id)
}
this.existingViewID = result.view.id
activeModals.set(result.view.id, this)
return result.view.id
})()
return
}
const viewID = await this.viewID
await this.client.views.update({
view_id: viewID,
view,
})
} catch (error) {
if (this.reject) {
this.reject(error)
}
console.error(error)
}
}
async submit(event) {
this.stopRendering()
activeRoots.delete(this)
if (this.existingViewID) activeModals.delete(this.existingViewID)
if (this.onSubmit) this.onSubmit(event)
}
async close(event) {
this.stopRendering()
activeRoots.delete(this)
if (this.existingViewID) activeModals.delete(this.existingViewID)
if (this.onClose) this.onClose(event)
}
handle = new ModalHandle(this)
}
export class ModalHandle {
root
constructor(root) {
this.root = root
}
get viewID() {
return this.root.existingViewID
}
get isOpen() {
return this.root.isOpen
}
get rendering() {
return this.root.rendering
}
async stop(behavior = 'clear') {
this.root.stopRendering()
activeRoots.delete(this.root)
const viewID = await this.root.viewID
if (viewID) activeModals.delete(viewID)
if (!viewID) return
if (behavior === 'keep') return
if (behavior === 'clear') {
await this.root.client.views.update({
view_id: viewID,
view: {
...this.root.modalArgs,
type: 'modal',
blocks: [],
},
})
return
}
const finalBlocks = behavior === 'clear' ? [] : blocks(behavior)
await this.root.client.views.update({
view_id: viewID,
view: {
...this.root.modalArgs,
type: 'modal',
blocks: finalBlocks,
},
})
}
}
export async function modal(
app,
argsOrId,
titleOrModalArgs,
element,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSubmit,
onClose
) {
ensureEventRegistered(app)
let resolve = undefined
let reject = undefined
const promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
const args =
typeof argsOrId === 'string' ? { trigger_id: argsOrId } : argsOrId
const modalArgs =
typeof titleOrModalArgs === 'string'
? { title: { type: 'plain_text', text: titleOrModalArgs } }
: titleOrModalArgs
const root = new ModalRoot(
app.client,
args,
modalArgs,
onSubmit,
onClose ?? onSubmit,
resolve,
reject
)
const container = createContainer(root)
render(element, container)
await promise
return root.handle
}