RSS Git Download  Clone
Raw Blame History 8kB 149 lines
import { create } from 'zustand'
import prettyBytes from 'pretty-bytes'
import { useI18nStore } from './i18n.store'

function readLocal(key: string, fallback: string): string {
    try { return localStorage.getItem(key) ?? fallback } catch { return fallback }
}

function readLocalBool(key: string, fallback: boolean): boolean {
    try {
        const v = localStorage.getItem(key)
        return v === null ? fallback : v === 'true'
    } catch { return fallback }
}

function readLocalNum(key: string, fallback: number): number {
    try {
        const v = localStorage.getItem(key)
        return v === null ? fallback : Number(v)
    } catch { return fallback }
}

interface SettingsState {
    redisTreeDivider: string
    jsonFormat: number
    animation: boolean
    maxValueDisplay: number
    maxKeys: number
    keysSort: boolean
    searchClientSide: boolean
    searchStartsWith: boolean
    pageCount: number
    keyPageCount: number
    language: string
    undoEnabled: boolean
    showDiffBeforeSave: boolean
    googleAnalytics: string
    connectInfoStorageKey: string
    socketTimeout: number
    maxLightKeysCount: number
    maxValueAsBuffer: number

    setSetting: (key: string, value: any) => void
    getStorageKeyCurrentDatabase: (connectionId: string) => string
    prettyBytes: (value: number) => string
    getHumanizeDurationOptions: () => { language: string; languages: Record<string, any> }
    generateId: () => string
}

export const useSettingsStore = create<SettingsState>((set) => ({
    // All localStorage keys match Angular settings.service.ts exactly
    redisTreeDivider: readLocal('p3xr-main-treecontrol-divider', ':'),
    jsonFormat: readLocalNum('p3xr-json-format', 4),
    animation: readLocalNum('p3xr-animation-settings', 0) === 1,
    maxValueDisplay: readLocalNum('p3xr-main-treecontrol-max-value-display', 1024),
    maxKeys: readLocalNum('p3xr-max-keys', 1000),
    keysSort: readLocalBool('p3xr-main-treecontrol-key-sort', true),
    searchClientSide: readLocalBool('p3xr-main-treecontrol-search-client-mode', false),
    searchStartsWith: readLocalBool('p3xr-main-treecontrol-search-starts-with', false),
    pageCount: readLocalNum('p3xr-main-treecontrol-page-size', 250),
    keyPageCount: readLocalNum('p3xr-main-key-page-size', 5),
    language: readLocal('p3xr-language', 'en'),
    undoEnabled: readLocalBool('p3xr-undo-enabled', true),
    showDiffBeforeSave: readLocalBool('p3xr-show-diff-before-save', false),
    googleAnalytics: 'G-8M2CK7993T',
    connectInfoStorageKey: 'p3xr-connect-info',
    socketTimeout: 300000,
    maxLightKeysCount: 110000,
    maxValueAsBuffer: 1000 * 256,

    setSetting: (key: string, value: any) => {
        try { localStorage.setItem(key, String(value)) } catch {}
        const map: Record<string, string> = {
            'p3xr-main-treecontrol-divider': 'redisTreeDivider',
            'p3xr-json-format': 'jsonFormat',
            'p3xr-animation-settings': 'animation',
            'p3xr-main-treecontrol-max-value-display': 'maxValueDisplay',
            'p3xr-max-keys': 'maxKeys',
            'p3xr-main-treecontrol-key-sort': 'keysSort',
            'p3xr-main-treecontrol-search-client-mode': 'searchClientSide',
            'p3xr-main-treecontrol-search-starts-with': 'searchStartsWith',
            'p3xr-main-treecontrol-page-size': 'pageCount',
            'p3xr-main-key-page-size': 'keyPageCount',
            'p3xr-language': 'language',
            'p3xr-undo-enabled': 'undoEnabled',
            'p3xr-show-diff-before-save': 'showDiffBeforeSave',
        }
        const stateKey = map[key]
        if (stateKey) {
            set({ [stateKey]: value } as any)
        }

        if (key === 'p3xr-animation-settings') {
            const enabled = String(value) === '1'
            document.body.classList.toggle('p3xr-animation', enabled)
            document.body.classList.toggle('p3xr-no-animation', !enabled)
        }
    },

    getStorageKeyCurrentDatabase: (connectionId: string) => `p3xr-main-current-database-${connectionId}`,

    prettyBytes: (value: number) => {
        let lang = useSettingsStore.getState().language
        if (lang === 'auto') lang = useI18nStore.getState().currentLang
        return prettyBytes(value, { locale: lang })
    },

    getHumanizeDurationOptions: () => {
        // Use resolved language from i18n store (not raw localStorage which may be 'auto')
        let lang = useSettingsStore.getState().language
        if (lang === 'auto') {
            lang = useI18nStore.getState().currentLang
        }
        const languageMap: Record<string, string> = {
            'pt-BR': 'pt', 'zn': 'zh_CN', 'zh-HK': 'zh_TW', 'zh-TW': 'zh_TW', 'pt-PT': 'pt',
        }
        const customLanguages: Record<string, any> = {
            az: { y: () => 'il', mo: () => 'ay', w: () => 'həftə', d: () => 'gün', h: () => 'saat', m: () => 'dəqiqə', s: () => 'saniyə', ms: () => 'millisaniyə' },
            be: { y: (c: number) => c === 1 ? 'год' : 'гадоў', mo: (c: number) => c === 1 ? 'месяц' : 'месяцаў', w: (c: number) => c === 1 ? 'тыдзень' : 'тыдняў', d: (c: number) => c === 1 ? 'дзень' : 'дзён', h: (c: number) => c === 1 ? 'гадзіна' : 'гадзін', m: (c: number) => c === 1 ? 'хвіліна' : 'хвілін', s: (c: number) => c === 1 ? 'секунда' : 'секунд', ms: (c: number) => c === 1 ? 'мілісекунда' : 'мілісекунд' },
            bs: { y: () => 'godina', mo: () => 'mjeseci', w: () => 'sedmica', d: () => 'dana', h: () => 'sati', m: () => 'minuta', s: () => 'sekundi', ms: () => 'milisekundi' },
            fil: { y: () => 'taon', mo: () => 'buwan', w: () => 'linggo', d: () => 'araw', h: () => 'oras', m: () => 'minuto', s: () => 'segundo', ms: () => 'millisegundo' },
            hy: { y: () => 'տարի', mo: () => 'ամիս', w: () => 'շաբաթ', d: () => 'օր', h: () => 'ժամ', m: () => 'րոպusage', s: () => 'վdelays', ms: () => 'միdelays' },
            ka: { y: () => 'წელი', mo: () => 'თვე', w: () => 'კვირა', d: () => 'დღე', h: () => 'საათი', m: () => 'წუთი', s: () => 'წამი', ms: () => 'მილიწამი' },
            kk: { y: () => 'жыл', mo: () => 'ай', w: () => 'апта', d: () => 'күн', h: () => 'сағат', m: () => 'минут', s: () => 'секунд', ms: () => 'миллисекунд' },
            ky: { y: () => 'жыл', mo: () => 'ай', w: () => 'апта', d: () => 'күн', h: () => 'саат', m: () => 'мүнөт', s: () => 'секунд', ms: () => 'миллисекунд' },
            ne: { y: () => 'वर्ष', mo: () => 'महिना', w: () => 'हप्ता', d: () => 'दिन', h: () => 'घण्टा', m: () => 'मिनेट', s: () => 'सेकेन्ड', ms: () => 'मिलिसेकेन्ड' },
            si: { y: () => 'වසර', mo: () => 'මාස', w: () => 'සති', d: () => 'දින', h: () => 'පැය', m: () => 'මිනිත්තු', s: () => 'තත්පර', ms: () => 'මිලි තත්පර' },
            tg: { y: () => 'сол', mo: () => 'моҳ', w: () => 'ҳафта', d: () => 'рӯз', h: () => 'соат', m: () => 'дақиқа', s: () => 'сония', ms: () => 'миллисония' },
            nb: { y: (c: number) => c === 1 ? 'år' : 'år', mo: (c: number) => c === 1 ? 'måned' : 'måneder', w: (c: number) => c === 1 ? 'uke' : 'uker', d: (c: number) => c === 1 ? 'dag' : 'dager', h: (c: number) => c === 1 ? 'time' : 'timer', m: (c: number) => c === 1 ? 'minutt' : 'minutter', s: (c: number) => c === 1 ? 'sekund' : 'sekunder', ms: () => 'millisekund' },
        }
        return {
            language: languageMap[lang] || lang,
            languages: customLanguages,
        }
    },

    // Apply animation class on init
    _initAnimation: (() => {
        const enabled = readLocalNum('p3xr-animation-settings', 0) === 1
        document.body.classList.toggle('p3xr-animation', enabled)
        document.body.classList.toggle('p3xr-no-animation', !enabled)
    })(),

    generateId: () => {
        return 'P3Xid' + Array.from(crypto.getRandomValues(new Uint8Array(16)))
            .map(b => b.toString(16).padStart(2, '0')).join('')
    },
}))