RSS Git Download  Clone
Raw Blame History 7kB 225 lines
import { create } from 'zustand'
import { useI18nStore } from './i18n.store'
import { useRedisStateStore } from './redis-state.store'
import { useSettingsStore } from './settings.store'
import { parseRedisInfo } from './redis-parser'

// --- Event bus for tree commands ---
type VoidCallback = () => void
const treeListeners: Record<string, Set<VoidCallback>> = {}

export function onTreeEvent(event: string, cb: VoidCallback): VoidCallback {
    if (!treeListeners[event]) treeListeners[event] = new Set()
    treeListeners[event].add(cb)
    return () => { treeListeners[event].delete(cb) }
}

export function emitTreeEvent(event: string) {
    treeListeners[event]?.forEach(cb => cb())
}

// --- Dialog state ---
interface ConfirmOptions {
    title?: string
    message: string
    disableCancel?: boolean
    onOk?: () => void
    onCancel?: () => void
}

interface PromptOptions {
    title: string
    placeholder: string
    initialValue?: string
    okLabel: string
    cancelLabel: string
    onOk?: (value: string) => void
    onCancel?: () => void
}

interface CommonState {
    // Toast
    toastMessage: string
    toastOpen: boolean
    toastDuration: number
    toast: (message: string, hideDelay?: number) => void
    closeToast: () => void
    toastUndoAction: string | null
    toastWithUndo: (message: string) => Promise<boolean>
    resolveToastUndo: ((clicked: boolean) => void) | null
    handleToastUndoClick: () => void

    // Confirm dialog
    confirmOpen: boolean
    confirmOptions: ConfirmOptions | null
    confirm: (options: ConfirmOptions) => Promise<void>
    resolveConfirm: ((ok: boolean) => void) | null

    // Prompt dialog
    promptOpen: boolean
    promptOptions: PromptOptions | null
    prompt: (options: PromptOptions) => Promise<string>
    resolvePrompt: ((value: string | null) => void) | null

    // Ask Authorization dialog
    askAuthOpen: boolean
    resolveAskAuth: ((result: { username: string; password: string } | null) => void) | null
    askAuth: () => Promise<{ username: string; password: string }>

    // Command palette
    commandPaletteOpen: boolean
    setCommandPaletteOpen: (open: boolean) => void

    // Error handling
    generalHandleError: (dataOrError: any) => boolean

    // Redis info loading
    loadRedisInfoResponse: (options: { response?: any }) => void
}

let lastResponse: any = null

export const useCommonStore = create<CommonState>((set, get) => ({
    toastMessage: '',
    toastOpen: false,
    toastDuration: 5000,
    toast: (message: string, hideDelay?: number) => {
        set({ toastMessage: message, toastOpen: true, toastDuration: hideDelay || 5000 })
    },
    closeToast: () => {
        const resolve = get().resolveToastUndo
        if (resolve) {
            resolve(false)
            set({ resolveToastUndo: null, toastUndoAction: null })
        }
        set({ toastOpen: false })
    },
    toastUndoAction: null,
    resolveToastUndo: null,
    toastWithUndo: (message: string) => {
        return new Promise<boolean>((resolve) => {
            set({ toastMessage: message, toastOpen: true, toastUndoAction: 'Undo', resolveToastUndo: resolve })
        })
    },
    handleToastUndoClick: () => {
        const resolve = get().resolveToastUndo
        if (resolve) {
            resolve(true)
            set({ resolveToastUndo: null, toastUndoAction: null, toastOpen: false })
        }
    },

    confirmOpen: false,
    confirmOptions: null,
    resolveConfirm: null,
    confirm: (options: ConfirmOptions) => {
        return new Promise<void>((resolve, reject) => {
            const strings = useI18nStore.getState().strings
            const isAlert = options.disableCancel === true
            set({
                confirmOpen: true,
                confirmOptions: {
                    title: options.title || (isAlert ? strings?.confirm?.info : strings?.confirm?.title),
                    message: options.message,
                    disableCancel: isAlert,
                },
                resolveConfirm: (ok: boolean) => {
                    set({ confirmOpen: false, confirmOptions: null, resolveConfirm: null })
                    ok ? resolve() : reject()
                },
            })
        })
    },

    promptOpen: false,
    promptOptions: null,
    resolvePrompt: null,
    prompt: (options: PromptOptions) => {
        return new Promise<string>((resolve, reject) => {
            set({
                promptOpen: true,
                promptOptions: options,
                resolvePrompt: (value: string | null) => {
                    set({ promptOpen: false, promptOptions: null, resolvePrompt: null })
                    value !== null ? resolve(value) : reject()
                },
            })
        })
    },

    askAuthOpen: false,
    resolveAskAuth: null,
    askAuth: () => {
        return new Promise<{ username: string; password: string }>((resolve, reject) => {
            set({
                askAuthOpen: true,
                resolveAskAuth: (result: { username: string; password: string } | null) => {
                    set({ askAuthOpen: false, resolveAskAuth: null })
                    result !== null ? resolve(result) : reject()
                },
            })
        })
    },

    commandPaletteOpen: false,
    setCommandPaletteOpen: (open: boolean) => set({ commandPaletteOpen: open }),

    generalHandleError: (dataOrError: any): boolean => {
        if (dataOrError === undefined) return true

        if (!(dataOrError instanceof Error || dataOrError instanceof Object)) {
            dataOrError = new Error(String(dataOrError))
        }

        if (dataOrError instanceof Error || dataOrError.status === 'error') {
            let error: any = dataOrError instanceof Error ? dataOrError : dataOrError.error
            console.warn('generalHandleError')
            console.error(error)

            const strings = useI18nStore.getState().strings
            const codes = strings?.code || {}
            if (typeof error === 'string' && codes.hasOwnProperty(error)) {
                error = new Error(codes[error])
            } else if (error?.code && codes.hasOwnProperty(error.code)) {
                error.message = codes[error.code]
            } else if (error?.message && codes.hasOwnProperty(error.message)) {
                error.message = codes[error.message]
            }

            if (error?.message === 'Connection is closed.') {
                useRedisStateStore.setState({ connection: undefined })
            }

            // Show as alert
            get().confirm({
                title: strings?.title?.error,
                message: error?.message || String(error),
                disableCancel: true,
            }).catch(() => {})

            return false
        }
        return true
    },

    loadRedisInfoResponse: (options: { response?: any } = {}) => {
        let response = options.response || lastResponse
        lastResponse = response
        if (!response) return

        const settings = useSettingsStore.getState()
        const info = parseRedisInfo(response.info)

        const keys = settings.keysSort && response.keys.length <= settings.maxLightKeysCount
            ? [...response.keys].sort()
            : response.keys

        useRedisStateStore.setState({
            info,
            keysRaw: keys,
            keysInfo: response.keysInfo,
            keysInfoFetchedAt: Date.now(),
        })
    },
}))