64 lines
3.0 KiB
TypeScript
64 lines
3.0 KiB
TypeScript
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<UiLayoutState>((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()];
|
|
}
|