ytptube/ui/app/composables/useDialog.ts
2025-08-25 22:21:32 +03:00

127 lines
3.2 KiB
TypeScript

import {reactive, readonly} from 'vue'
export type DialogResult<T = string | null> = { status: boolean; value: T }
type BaseOptions = {
/**
* Title of the dialog
*/
title?: string
/**
* Message to display in the dialog
*/
message?: string
/**
* Text for the confirm button
*/
confirmText?: string
}
export type PromptOptions = BaseOptions & {
/**
* Text for the input field
*/
initial?: string
/**
* Placeholder text for the input field
*/
placeholder?: string
/**
* Text for the cancel button
*/
cancelText?: string
/**
* Function to validate the input value
* @returns true if valid, or an error message string if invalid
*/
validate?: (v: string) => true | string
}
export type ConfirmOptions = BaseOptions & {
/**
* Text for the confirm button
*/
cancelText?: string
}
export type AlertOptions = BaseOptions & {}
type QueueItem = {
type: 'prompt' | 'confirm' | 'alert'
opts: PromptOptions | ConfirmOptions | AlertOptions
resolve: (r: DialogResult<any>) => void
}
type DialogState = {
current: QueueItem | null
queue: QueueItem[]
errorMsg: string | null
input: string
}
export const useDialog = () => {
const raw = useState<DialogState>('dialog:state', () => reactive({
current: null,
queue: [],
errorMsg: null,
input: '',
} as DialogState))
const state = raw.value
const _dequeue = () => {
if (!state.current && state.queue.length) {
state.current = state.queue.shift()!
state.errorMsg = null
state.input = state.current.type === 'prompt' ? (state.current.opts as PromptOptions).initial ?? '' : ''
}
}
const promptDialog = (opts: PromptOptions) => new Promise<DialogResult<string>>((resolve) => {
state.queue.push({type: 'prompt', opts, resolve})
_dequeue()
})
const confirmDialog = (opts: ConfirmOptions) => new Promise<DialogResult<null>>((resolve) => {
state.queue.push({type: 'confirm', opts, resolve})
_dequeue()
})
const alertDialog = (opts: AlertOptions) => new Promise<DialogResult<null>>((resolve) => {
state.queue.push({type: 'alert', opts, resolve})
_dequeue()
})
const confirm = (value?: string) => {
if (!state.current) return
if (state.current.type === 'prompt') {
const val = value ?? state.input
const v = (state.current.opts as PromptOptions).validate?.(val)
if (v && v !== true) {
state.errorMsg = v
return
}
state.current.resolve({status: true, value: val})
} else {
state.current.resolve({status: true, value: null})
}
state.current = null
_dequeue()
}
const cancel = () => {
if (!state.current) return
state.current.resolve({status: false, value: null})
state.current = null
_dequeue()
}
return {
promptDialog,
confirmDialog,
alertDialog,
confirm,
cancel,
state: readonly(state) as Readonly<DialogState>,
}
}