import { create } from "zustand"; import { Capacitor } from "@capacitor/core"; import { loadUiPreferences, saveUiPreferences } from "../app/accountRepository"; import { useShellStore } from "../app/shellStore"; import { normalizeUiPreferences, type UiDevice, type UiLayout, type UiPreferences } from "./uiLayout"; import { publishInputPreferences } from "../input/keybindRuntime"; export function currentUiDevice(): UiDevice { return (typeof window !== "undefined" && /(?:[?&]layout=(?:thor-preview|dual)|[?&]display=(?:top|bottom))\b/.test(window.location.search)) || Capacitor.isNativePlatform() || (typeof navigator !== "undefined" && /Android|iPhone|iPad|iPod/i.test(navigator.userAgent)) || (typeof window !== "undefined" && window.matchMedia?.("(pointer: coarse)").matches && window.innerWidth < 1100) ? "mobile" : "pc"; } interface UiLayoutState { ownerId: string | null; preferences: UiPreferences; draft: UiLayout | null; device: UiDevice; mode: "layout" | "bindings"; selectedButton: { bar: number; slot: number } | null; selectedElement: string | null; begin: () => void; updateDraft: (update: (layout: UiLayout) => UiLayout) => void; persist: (preferences: UiPreferences) => void; finish: (save: boolean) => void; } export const useUiLayoutStore = create((set, get) => ({ ownerId: null, preferences: normalizeUiPreferences(null), draft: null, device: currentUiDevice(), mode: "layout", selectedButton: null, selectedElement: null, begin: () => { const device = currentUiDevice(); set({ draft: get().preferences.current[device], device, mode: "layout", selectedButton: null }); }, updateDraft: (update) => set((state) => state.draft ? { draft: update(state.draft) } : state), persist: (preferences) => { const ownerId = get().ownerId; if (!ownerId) throw new Error("Sign in or choose an offline profile before saving your UI."); // Write first: storage errors leave the editor open with its draft intact. const saved = saveUiPreferences(ownerId, preferences); set({ preferences: saved }); }, finish: (save) => { const { draft, device, preferences } = get(); if (save && draft) get().persist({ ...preferences, current: { ...preferences.current, [device]: draft } }); set({ draft: null, selectedButton: null, mode: "layout" }); }, })); function activateOwner() { const ownerId = useShellStore.getState().session?.ownerId ?? null; useUiLayoutStore.setState({ ownerId, preferences: ownerId ? loadUiPreferences(ownerId) : normalizeUiPreferences(null), draft: null }); } activateOwner(); publishInputPreferences(useUiLayoutStore.getState().preferences.keybinds); useUiLayoutStore.subscribe((state) => publishInputPreferences(state.preferences.keybinds)); useShellStore.subscribe((state, previous) => { if (state.session !== previous.session) activateOwner(); }); export function activeUiLayout(): UiLayout { const state = useUiLayoutStore.getState(); return state.draft ?? state.preferences.current[currentUiDevice()]; }