summaryrefslogtreecommitdiff
path: root/apps/web/src/stores
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/stores')
-rw-r--r--apps/web/src/stores/assets-panel-store.tsx124
-rw-r--r--apps/web/src/stores/editor-store.ts24
-rw-r--r--apps/web/src/stores/keybindings-store.ts251
-rw-r--r--apps/web/src/stores/keybindings/migrations/index.ts32
-rw-r--r--apps/web/src/stores/keybindings/migrations/v2-to-v3.ts26
-rw-r--r--apps/web/src/stores/keybindings/migrations/v3-to-v4.ts25
-rw-r--r--apps/web/src/stores/keybindings/migrations/v4-to-v5.ts17
-rw-r--r--apps/web/src/stores/keybindings/migrations/v5-to-v6.ts17
-rw-r--r--apps/web/src/stores/keybindings/migrations/v6-to-v7.ts19
-rw-r--r--apps/web/src/stores/panel-store.ts93
-rw-r--r--apps/web/src/stores/preview-store.ts112
-rw-r--r--apps/web/src/stores/properties-store.ts19
-rw-r--r--apps/web/src/stores/sounds-store.ts251
-rw-r--r--apps/web/src/stores/stickers-store.ts238
-rw-r--r--apps/web/src/stores/timeline-store.ts57
15 files changed, 0 insertions, 1305 deletions
diff --git a/apps/web/src/stores/assets-panel-store.tsx b/apps/web/src/stores/assets-panel-store.tsx
deleted file mode 100644
index 1539d305..00000000
--- a/apps/web/src/stores/assets-panel-store.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import type { ElementType } from "react";
-import { create } from "zustand";
-import { persist } from "zustand/middleware";
-import {
- ArrowRightDoubleIcon,
- ClosedCaptionIcon,
- Folder03Icon,
- Happy01Icon,
- HeadphonesIcon,
- MagicWand05Icon,
- TextIcon,
- Settings01Icon,
- SlidersHorizontalIcon,
- ColorsIcon,
-} from "@hugeicons/core-free-icons";
-import { HugeiconsIcon, type IconSvgElement } from "@hugeicons/react";
-
-export const TAB_KEYS = [
- "media",
- "sounds",
- "text",
- "stickers",
- "effects",
- "transitions",
- "captions",
- "adjustment",
- "settings",
-] as const;
-
-export type Tab = (typeof TAB_KEYS)[number];
-
-const createHugeiconsIcon =
- ({ icon }: { icon: IconSvgElement }) =>
- ({ className }: { className?: string }) => (
- <HugeiconsIcon icon={icon} className={className} />
- );
-
-export const tabs = {
- media: {
- icon: createHugeiconsIcon({ icon: Folder03Icon }),
- label: "Media",
- },
- sounds: {
- icon: createHugeiconsIcon({ icon: HeadphonesIcon }),
- label: "Sounds",
- },
- text: {
- icon: createHugeiconsIcon({ icon: TextIcon }),
- label: "Text",
- },
- stickers: {
- icon: createHugeiconsIcon({ icon: Happy01Icon }),
- label: "Stickers",
- },
- effects: {
- icon: createHugeiconsIcon({ icon: MagicWand05Icon }),
- label: "Effects",
- },
- transitions: {
- icon: createHugeiconsIcon({ icon: ArrowRightDoubleIcon }),
- label: "Transitions",
- },
- captions: {
- icon: createHugeiconsIcon({ icon: ClosedCaptionIcon }),
- label: "Captions",
- },
- adjustment: {
- icon: createHugeiconsIcon({ icon: SlidersHorizontalIcon }),
- label: "Adjustment",
- },
- settings: {
- icon: createHugeiconsIcon({ icon: Settings01Icon }),
- label: "Settings",
- },
-} satisfies Record<
- Tab,
- { icon: ElementType<{ className?: string }>; label: string }
->;
-
-export type MediaViewMode = "grid" | "list";
-export type MediaSortKey = "name" | "type" | "duration" | "size";
-export type MediaSortOrder = "asc" | "desc";
-
-interface AssetsPanelStore {
- activeTab: Tab;
- setActiveTab: (tab: Tab) => void;
- highlightMediaId: string | null;
- requestRevealMedia: (mediaId: string) => void;
- clearHighlight: () => void;
-
- /* Media */
- mediaViewMode: MediaViewMode;
- setMediaViewMode: (mode: MediaViewMode) => void;
- mediaSortBy: MediaSortKey;
- mediaSortOrder: MediaSortOrder;
- setMediaSort: (key: MediaSortKey, order: MediaSortOrder) => void;
-}
-
-export const useAssetsPanelStore = create<AssetsPanelStore>()(
- persist(
- (set) => ({
- activeTab: "media",
- setActiveTab: (tab) => set({ activeTab: tab }),
- highlightMediaId: null,
- requestRevealMedia: (mediaId) =>
- set({ activeTab: "media", highlightMediaId: mediaId }),
- clearHighlight: () => set({ highlightMediaId: null }),
- mediaViewMode: "grid",
- setMediaViewMode: (mode) => set({ mediaViewMode: mode }),
- mediaSortBy: "name",
- mediaSortOrder: "asc",
- setMediaSort: (key, order) =>
- set({ mediaSortBy: key, mediaSortOrder: order }),
- }),
- {
- name: "assets-panel",
- partialize: (state) => ({
- mediaViewMode: state.mediaViewMode,
- mediaSortBy: state.mediaSortBy,
- mediaSortOrder: state.mediaSortOrder,
- }),
- },
- ),
-);
diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts
deleted file mode 100644
index 07cb433e..00000000
--- a/apps/web/src/stores/editor-store.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { create } from "zustand";
-import { DEFAULT_CANVAS_PRESETS } from "@/lib/canvas/sizes";
-import type { TCanvasSize } from "@/lib/project/types";
-
-interface EditorState {
- isInitializing: boolean;
- isPanelsReady: boolean;
- canvasPresets: TCanvasSize[];
- setInitializing: (loading: boolean) => void;
- setPanelsReady: (ready: boolean) => void;
- initializeApp: () => Promise<void>;
-}
-
-export const useEditorStore = create<EditorState>()((set) => ({
- isInitializing: true,
- isPanelsReady: false,
- canvasPresets: DEFAULT_CANVAS_PRESETS,
- setInitializing: (loading) => set({ isInitializing: loading }),
- setPanelsReady: (ready) => set({ isPanelsReady: ready }),
- initializeApp: async () => {
- set({ isInitializing: true, isPanelsReady: false });
- set({ isPanelsReady: true, isInitializing: false });
- },
-}));
diff --git a/apps/web/src/stores/keybindings-store.ts b/apps/web/src/stores/keybindings-store.ts
deleted file mode 100644
index ff77e0b4..00000000
--- a/apps/web/src/stores/keybindings-store.ts
+++ /dev/null
@@ -1,251 +0,0 @@
-"use client";
-
-import { create } from "zustand";
-import { persist } from "zustand/middleware";
-import type { TActionWithOptionalArgs } from "@/lib/actions";
-import { getDefaultShortcuts } from "@/lib/actions";
-import { isTypableDOMElement } from "@/utils/browser";
-import { isAppleDevice } from "@/utils/platform";
-import type { KeybindingConfig, ShortcutKey } from "@/lib/actions/keybinding";
-import { runMigrations, CURRENT_VERSION } from "./keybindings/migrations";
-
-const defaultKeybindings: KeybindingConfig = getDefaultShortcuts();
-
-export interface KeybindingConflict {
- key: ShortcutKey;
- existingAction: TActionWithOptionalArgs;
- newAction: TActionWithOptionalArgs;
-}
-
-interface KeybindingsState {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
- overlayDepth: number;
- openOverlayIds: string[];
- isLoadingProject: boolean;
- isRecording: boolean;
-
- updateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => void;
- removeKeybinding: (key: ShortcutKey) => void;
- resetToDefaults: () => void;
- importKeybindings: (config: KeybindingConfig) => void;
- exportKeybindings: () => KeybindingConfig;
- openOverlay: (overlayId: string) => void;
- closeOverlay: (overlayId: string) => void;
- setLoadingProject: (loading: boolean) => void;
- setIsRecording: (isRecording: boolean) => void;
- validateKeybinding: (
- key: ShortcutKey,
- action: TActionWithOptionalArgs,
- ) => KeybindingConflict | null;
- getKeybindingsForAction: (action: TActionWithOptionalArgs) => ShortcutKey[];
- getKeybindingString: (ev: KeyboardEvent) => ShortcutKey | null;
-}
-
-function isDOMElement(element: EventTarget | null): element is HTMLElement {
- return element instanceof HTMLElement;
-}
-
-export const useKeybindingsStore = create<KeybindingsState>()(
- persist(
- (set, get) => ({
- keybindings: { ...defaultKeybindings },
- isCustomized: false,
- overlayDepth: 0,
- openOverlayIds: [],
- isLoadingProject: false,
- isRecording: false,
-
- openOverlay: (overlayId) =>
- set((s) => {
- const openOverlayIds = s.openOverlayIds.includes(overlayId)
- ? s.openOverlayIds
- : [...s.openOverlayIds, overlayId];
- const nextOverlayDepth = openOverlayIds.length;
- return {
- openOverlayIds,
- overlayDepth: nextOverlayDepth,
- };
- }),
- closeOverlay: (overlayId) =>
- set((s) => {
- const openOverlayIds = s.openOverlayIds.filter(
- (id) => id !== overlayId,
- );
- const nextOverlayDepth = openOverlayIds.length;
- return {
- openOverlayIds,
- overlayDepth: nextOverlayDepth,
- };
- }),
- setLoadingProject: (loading) => {
- set({ isLoadingProject: loading });
- },
-
- updateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => {
- set((state) => {
- const newKeybindings = { ...state.keybindings };
- newKeybindings[key] = action;
-
- return {
- keybindings: newKeybindings,
- isCustomized: true,
- };
- });
- },
-
- removeKeybinding: (key: ShortcutKey) => {
- set((state) => {
- const newKeybindings = { ...state.keybindings };
- delete newKeybindings[key];
-
- return {
- keybindings: newKeybindings,
- isCustomized: true,
- };
- });
- },
-
- resetToDefaults: () => {
- set({
- keybindings: { ...defaultKeybindings },
- isCustomized: false,
- });
- },
-
- importKeybindings: (config: KeybindingConfig) => {
- for (const [key] of Object.entries(config)) {
- if (typeof key !== "string" || key.length === 0) {
- throw new Error(`Invalid key format: ${key}`);
- }
- }
- set({
- keybindings: { ...config },
- isCustomized: true,
- });
- },
-
- exportKeybindings: () => {
- return get().keybindings;
- },
-
- validateKeybinding: (
- key: ShortcutKey,
- action: TActionWithOptionalArgs,
- ) => {
- const { keybindings } = get();
- const existingAction = keybindings[key];
-
- if (existingAction && existingAction !== action) {
- return {
- key,
- existingAction,
- newAction: action,
- };
- }
-
- return null;
- },
- setIsRecording: (isRecording: boolean) => {
- set({ isRecording });
- },
-
- getKeybindingsForAction: (action: TActionWithOptionalArgs) => {
- const { keybindings } = get();
- return Object.keys(keybindings).filter(
- (key) => keybindings[key as ShortcutKey] === action,
- ) as ShortcutKey[];
- },
-
- getKeybindingString: (ev: KeyboardEvent) => {
- return generateKeybindingString(ev) as ShortcutKey | null;
- },
- }),
- {
- name: "opencut-keybindings",
- version: CURRENT_VERSION,
- partialize: (state) => ({
- keybindings: state.keybindings,
- isCustomized: state.isCustomized,
- }),
- migrate: (persisted, version) =>
- runMigrations({ state: persisted, fromVersion: version }),
- },
- ),
-);
-
-function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
- const target = ev.target;
- const modifierKey = getActiveModifier(ev);
- const key = getPressedKey(ev);
- if (!key) return null;
-
- if (modifierKey) {
- if (
- modifierKey === "shift" &&
- isDOMElement(target) &&
- isTypableDOMElement({ element: target as HTMLElement })
- ) {
- return null;
- }
-
- return `${modifierKey}+${key}` as ShortcutKey;
- }
-
- if (
- isDOMElement(target) &&
- isTypableDOMElement({ element: target as HTMLElement })
- )
- return null;
-
- return `${key}` as ShortcutKey;
-}
-
-function getPressedKey(ev: KeyboardEvent): string | null {
- const key = (ev.key ?? "").toLowerCase();
- const code = ev.code ?? "";
-
- if (code === "Space" || key === " " || key === "spacebar" || key === "space")
- return "space";
-
- if (key.startsWith("arrow")) return key.slice(5);
-
- if (key === "escape") return "escape";
- if (key === "tab") return "tab";
- if (key === "home") return "home";
- if (key === "end") return "end";
- if (key === "delete") return "delete";
- if (key === "backspace") return "backspace";
-
- if (code.startsWith("Key")) {
- const letter = code.slice(3).toLowerCase();
- if (letter.length === 1 && letter >= "a" && letter <= "z") return letter;
- }
-
- // Use physical key position for AZERTY and other non-QWERTY layouts
- if (code.startsWith("Digit")) {
- const digit = code.slice(5);
- if (digit.length === 1 && digit >= "0" && digit <= "9") return digit;
- }
-
- const isDigit = key.length === 1 && key >= "0" && key <= "9";
- if (isDigit) return key;
-
- if (key === "/" || key === "." || key === "enter") return key;
-
- return null;
-}
-
-function getActiveModifier(ev: KeyboardEvent): string | null {
- const modifierKeys = {
- ctrl: isAppleDevice() ? ev.metaKey : ev.ctrlKey,
- alt: ev.altKey,
- shift: ev.shiftKey,
- };
-
- const activeModifier = Object.keys(modifierKeys)
- .filter((key) => modifierKeys[key as keyof typeof modifierKeys])
- .join("+");
-
- return activeModifier === "" ? null : activeModifier;
-}
diff --git a/apps/web/src/stores/keybindings/migrations/index.ts b/apps/web/src/stores/keybindings/migrations/index.ts
deleted file mode 100644
index 71fd81a6..00000000
--- a/apps/web/src/stores/keybindings/migrations/index.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { v2ToV3 } from "./v2-to-v3";
-import { v3ToV4 } from "./v3-to-v4";
-import { v4ToV5 } from "./v4-to-v5";
-import { v5ToV6 } from "./v5-to-v6";
-import { v6ToV7 } from "./v6-to-v7";
-
-type MigrationFn = ({ state }: { state: unknown }) => unknown;
-
-const migrations: Record<number, MigrationFn> = {
- 2: v2ToV3,
- 3: v3ToV4,
- 4: v4ToV5,
- 5: v5ToV6,
- 6: v6ToV7,
-};
-
-export const CURRENT_VERSION = 7;
-
-export function runMigrations({
- state,
- fromVersion,
-}: {
- state: unknown;
- fromVersion: number;
-}): unknown {
- let current = state;
- for (let version = fromVersion; version < CURRENT_VERSION; version++) {
- const migrate = migrations[version];
- if (migrate) current = migrate({ state: current });
- }
- return current;
-}
diff --git a/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts b/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts
deleted file mode 100644
index 6ebff92c..00000000
--- a/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import type { KeybindingConfig, ShortcutKey } from "@/lib/actions/keybinding";
-import type { TActionWithOptionalArgs } from "@/lib/actions";
-
-interface V2State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
-
-export function v2ToV3({ state }: { state: unknown }): unknown {
- const v2 = state as V2State;
-
- const renames: Record<string, string> = {
- "split-selected": "split",
- "split-selected-left": "split-left",
- "split-selected-right": "split-right",
- };
-
- const migrated = { ...v2.keybindings };
- for (const [key, action] of Object.entries(migrated)) {
- if (action && renames[action]) {
- migrated[key as ShortcutKey] = renames[action] as TActionWithOptionalArgs;
- }
- }
-
- return { ...v2, keybindings: migrated };
-}
diff --git a/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts b/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts
deleted file mode 100644
index 0f9b35da..00000000
--- a/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { TActionWithOptionalArgs } from "@/lib/actions";
-import type { ShortcutKey } from "@/lib/actions/keybinding";
-import type { KeybindingConfig } from "@/lib/actions/keybinding";
-
-interface V3State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
-
-export function v3ToV4({ state }: { state: unknown }): unknown {
- const v3 = state as V3State;
-
- const renames: Record<string, string> = {
- "paste-selected": "paste-copied",
- };
-
- const migrated = { ...v3.keybindings };
- for (const [key, action] of Object.entries(migrated)) {
- if (action && renames[action]) {
- migrated[key as ShortcutKey] = renames[action] as TActionWithOptionalArgs;
- }
- }
-
- return { ...v3, keybindings: migrated };
-}
diff --git a/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts b/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts
deleted file mode 100644
index 880df1dc..00000000
--- a/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { KeybindingConfig } from "@/lib/actions/keybinding";
-
-interface V4State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
-
-export function v4ToV5({ state }: { state: unknown }): unknown {
- const v4 = state as V4State;
- const keybindings = { ...v4.keybindings };
-
- if (!keybindings.escape) {
- keybindings.escape = "deselect-all";
- }
-
- return { ...v4, keybindings };
-}
diff --git a/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts b/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts
deleted file mode 100644
index ebabb7c0..00000000
--- a/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { KeybindingConfig } from "@/lib/actions/keybinding";
-
-interface V5State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
-
-export function v5ToV6({ state }: { state: unknown }): unknown {
- const v5 = state as V5State;
- const keybindings = { ...v5.keybindings };
-
- if (keybindings.escape === "deselect-all") {
- keybindings.escape = "cancel-interaction";
- }
-
- return { ...v5, keybindings };
-}
diff --git a/apps/web/src/stores/keybindings/migrations/v6-to-v7.ts b/apps/web/src/stores/keybindings/migrations/v6-to-v7.ts
deleted file mode 100644
index 8c5fd96c..00000000
--- a/apps/web/src/stores/keybindings/migrations/v6-to-v7.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { KeybindingConfig } from "@/lib/actions/keybinding";
-
-interface V6State {
- keybindings: KeybindingConfig;
- isCustomized: boolean;
-}
-
-export function v6ToV7({ state }: { state: unknown }): unknown {
- const v6 = state as V6State;
- const keybindings = { ...v6.keybindings };
-
- for (const key of Object.keys(keybindings) as Array<keyof KeybindingConfig>) {
- if (keybindings[key] === ("split-element" as never)) {
- keybindings[key] = "split";
- }
- }
-
- return { ...v6, keybindings };
-}
diff --git a/apps/web/src/stores/panel-store.ts b/apps/web/src/stores/panel-store.ts
deleted file mode 100644
index 757a37a6..00000000
--- a/apps/web/src/stores/panel-store.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { create } from "zustand";
-import { persist } from "zustand/middleware";
-import { PANEL_CONFIG } from "@/lib/panels/layout";
-
-export interface PanelSizes {
- tools: number;
- preview: number;
- properties: number;
- mainContent: number;
- timeline: number;
-}
-
-export type PanelId = keyof PanelSizes;
-
-interface PanelState {
- panels: PanelSizes;
- setPanel: (panel: PanelId, size: number) => void;
- setPanels: (sizes: Partial<PanelSizes>) => void;
- resetPanels: () => void;
-}
-
-export const usePanelStore = create<PanelState>()(
- persist(
- (set) => ({
- ...PANEL_CONFIG,
- setPanel: (panel, size) =>
- set((state) => ({
- panels: {
- ...state.panels,
- [panel]: size,
- },
- })),
- setPanels: (sizes) =>
- set((state) => ({
- panels: {
- ...state.panels,
- ...sizes,
- },
- })),
- resetPanels: () => set({ ...PANEL_CONFIG }),
- }),
- {
- name: "panel-sizes",
- version: 2,
- migrate: (persistedState) => {
- const state = persistedState as
- | {
- panels?: Partial<PanelSizes> | null;
- toolsPanel?: number;
- previewPanel?: number;
- propertiesPanel?: number;
- mainContent?: number;
- timeline?: number;
- tools?: number;
- preview?: number;
- properties?: number;
- }
- | undefined
- | null;
-
- if (!state) return { panels: { ...PANEL_CONFIG.panels } };
-
- if (state.panels && typeof state.panels === "object") {
- return {
- panels: {
- ...PANEL_CONFIG.panels,
- ...state.panels,
- },
- };
- }
-
- return {
- panels: {
- tools: state.tools ?? state.toolsPanel ?? PANEL_CONFIG.panels.tools,
- preview:
- state.preview ??
- state.previewPanel ??
- PANEL_CONFIG.panels.preview,
- properties:
- state.properties ??
- state.propertiesPanel ??
- PANEL_CONFIG.panels.properties,
- mainContent: state.mainContent ?? PANEL_CONFIG.panels.mainContent,
- timeline: state.timeline ?? PANEL_CONFIG.panels.timeline,
- },
- };
- },
- partialize: (state) => ({
- panels: state.panels,
- }),
- },
- ),
-);
diff --git a/apps/web/src/stores/preview-store.ts b/apps/web/src/stores/preview-store.ts
deleted file mode 100644
index b5957cb5..00000000
--- a/apps/web/src/stores/preview-store.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import { create } from "zustand";
-import { persist } from "zustand/middleware";
-import { isGuideId, type GuideId } from "@/lib/guides";
-import { DEFAULT_GRID_CONFIG } from "@/lib/guides/grid";
-import type { GridConfig } from "@/lib/guides/types";
-
-interface PreviewOverlaysState {
- bookmarks: boolean;
-}
-
-interface PersistedPreviewState {
- activeGuide?: string | null;
- layoutGuide?: {
- platform?: string | null;
- };
- overlays?: PreviewOverlaysState;
- gridConfig?: GridConfig;
-}
-
-interface PreviewState {
- activeGuide: GuideId | null;
- overlays: PreviewOverlaysState;
- gridConfig: GridConfig;
- toggleGuide: (guideId: GuideId) => void;
- setGridConfig: (config: Partial<GridConfig>) => void;
- setOverlayVisibility: ({
- overlay,
- isVisible,
- }: {
- overlay: keyof PreviewOverlaysState;
- isVisible: boolean;
- }) => void;
- toggleOverlayVisibility: ({
- overlay,
- }: {
- overlay: keyof PreviewOverlaysState;
- }) => void;
-}
-
-const DEFAULT_PREVIEW_OVERLAYS: PreviewOverlaysState = {
- bookmarks: true,
-};
-
-function getPersistedActiveGuide(
- state: PersistedPreviewState | undefined,
-): GuideId | null {
- const persistedGuide =
- state?.activeGuide ?? state?.layoutGuide?.platform ?? null;
-
- if (typeof persistedGuide !== "string") {
- return null;
- }
-
- return isGuideId(persistedGuide) ? persistedGuide : null;
-}
-
-export const usePreviewStore = create<PreviewState>()(
- persist(
- (set) => ({
- activeGuide: null,
- overlays: DEFAULT_PREVIEW_OVERLAYS,
- gridConfig: DEFAULT_GRID_CONFIG,
- toggleGuide: (guideId) => {
- set((state) => ({
- activeGuide: state.activeGuide === guideId ? null : guideId,
- }));
- },
- setGridConfig: (config) => {
- set((state) => ({
- gridConfig: { ...state.gridConfig, ...config },
- }));
- },
- setOverlayVisibility: ({ overlay, isVisible }) => {
- set((state) => ({
- overlays: {
- ...state.overlays,
- [overlay]: isVisible,
- },
- }));
- },
- toggleOverlayVisibility: ({ overlay }) => {
- set((state) => ({
- overlays: {
- ...state.overlays,
- [overlay]: !state.overlays[overlay],
- },
- }));
- },
- }),
- {
- name: "preview-settings",
- version: 4,
- migrate: (persistedState) => {
- const state = persistedState as PersistedPreviewState | undefined;
-
- return {
- activeGuide: getPersistedActiveGuide(state),
- overlays: state?.overlays ?? DEFAULT_PREVIEW_OVERLAYS,
- gridConfig: {
- rows: state?.gridConfig?.rows ?? DEFAULT_GRID_CONFIG.rows,
- cols: state?.gridConfig?.cols ?? DEFAULT_GRID_CONFIG.cols,
- },
- };
- },
- partialize: (state) => ({
- activeGuide: state.activeGuide,
- overlays: state.overlays,
- gridConfig: state.gridConfig,
- }),
- },
- ),
-);
diff --git a/apps/web/src/stores/properties-store.ts b/apps/web/src/stores/properties-store.ts
deleted file mode 100644
index f45f176d..00000000
--- a/apps/web/src/stores/properties-store.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { create } from "zustand";
-
-interface ClipEffectsTarget {
- elementId: string;
- trackId: string;
-}
-
-interface PropertiesState {
- clipEffectsTarget: ClipEffectsTarget | null;
- openClipEffects: ({ elementId, trackId }: ClipEffectsTarget) => void;
- closeClipEffects: () => void;
-}
-
-export const usePropertiesStore = create<PropertiesState>()((set) => ({
- clipEffectsTarget: null,
- openClipEffects: ({ elementId, trackId }) =>
- set({ clipEffectsTarget: { elementId, trackId } }),
- closeClipEffects: () => set({ clipEffectsTarget: null }),
-}));
diff --git a/apps/web/src/stores/sounds-store.ts b/apps/web/src/stores/sounds-store.ts
deleted file mode 100644
index b4b5772b..00000000
--- a/apps/web/src/stores/sounds-store.ts
+++ /dev/null
@@ -1,251 +0,0 @@
-import { create } from "zustand";
-import type { SoundEffect, SavedSound } from "@/lib/sounds/types";
-import { storageService } from "@/services/storage/service";
-import { toast } from "sonner";
-import { EditorCore } from "@/core";
-import { buildLibraryAudioElement } from "@/lib/timeline/element-utils";
-
-interface SoundsStore {
- topSoundEffects: SoundEffect[];
- isLoading: boolean;
- error: string | null;
- hasLoaded: boolean;
- showCommercialOnly: boolean;
- toggleCommercialFilter: () => void;
- searchQuery: string;
- searchResults: SoundEffect[];
- isSearching: boolean;
- searchError: string | null;
- lastSearchQuery: string;
- scrollPosition: number;
- currentPage: number;
- hasNextPage: boolean;
- totalCount: number;
- isLoadingMore: boolean;
- savedSounds: SavedSound[];
- isSavedSoundsLoaded: boolean;
- isLoadingSavedSounds: boolean;
- savedSoundsError: string | null;
-
- addSoundToTimeline: ({ sound }: { sound: SoundEffect }) => Promise<boolean>;
- setTopSoundEffects: ({ sounds }: { sounds: SoundEffect[] }) => void;
- setLoading: ({ loading }: { loading: boolean }) => void;
- setError: ({ error }: { error: string | null }) => void;
- setHasLoaded: ({ loaded }: { loaded: boolean }) => void;
- setSearchQuery: ({ query }: { query: string }) => void;
- setSearchResults: ({ results }: { results: SoundEffect[] }) => void;
- setSearching: ({ searching }: { searching: boolean }) => void;
- setSearchError: ({ error }: { error: string | null }) => void;
- setLastSearchQuery: ({ query }: { query: string }) => void;
- setScrollPosition: ({ position }: { position: number }) => void;
- setCurrentPage: ({ page }: { page: number }) => void;
- setHasNextPage: ({ hasNext }: { hasNext: boolean }) => void;
- setTotalCount: ({ count }: { count: number }) => void;
- setLoadingMore: ({ loading }: { loading: boolean }) => void;
- appendSearchResults: ({ results }: { results: SoundEffect[] }) => void;
- appendTopSounds: ({ results }: { results: SoundEffect[] }) => void;
- resetPagination: () => void;
- loadSavedSounds: () => Promise<void>;
- saveSoundEffect: ({
- soundEffect,
- }: {
- soundEffect: SoundEffect;
- }) => Promise<void>;
- removeSavedSound: ({ soundId }: { soundId: number }) => Promise<void>;
- isSoundSaved: ({ soundId }: { soundId: number }) => boolean;
- toggleSavedSound: ({
- soundEffect,
- }: {
- soundEffect: SoundEffect;
- }) => Promise<void>;
- clearSavedSounds: () => Promise<void>;
-}
-
-export const useSoundsStore = create<SoundsStore>((set, get) => ({
- topSoundEffects: [],
- isLoading: false,
- error: null,
- hasLoaded: false,
- showCommercialOnly: true,
-
- toggleCommercialFilter: () => {
- set((state) => ({ showCommercialOnly: !state.showCommercialOnly }));
- },
-
- searchQuery: "",
- searchResults: [],
- isSearching: false,
- searchError: null,
- lastSearchQuery: "",
- scrollPosition: 0,
- currentPage: 1,
- hasNextPage: false,
- totalCount: 0,
- isLoadingMore: false,
- savedSounds: [],
- isSavedSoundsLoaded: false,
- isLoadingSavedSounds: false,
- savedSoundsError: null,
-
- setTopSoundEffects: ({ sounds }) => set({ topSoundEffects: sounds }),
- setLoading: ({ loading }) => set({ isLoading: loading }),
- setError: ({ error }) => set({ error }),
- setHasLoaded: ({ loaded }) => set({ hasLoaded: loaded }),
- setSearchQuery: ({ query }) => set({ searchQuery: query }),
- setSearchResults: ({ results }) =>
- set({ searchResults: results, currentPage: 1 }),
- setSearching: ({ searching }) => set({ isSearching: searching }),
- setSearchError: ({ error }) => set({ searchError: error }),
- setLastSearchQuery: ({ query }) => set({ lastSearchQuery: query }),
- setScrollPosition: ({ position }) => set({ scrollPosition: position }),
- setCurrentPage: ({ page }) => set({ currentPage: page }),
- setHasNextPage: ({ hasNext }) => set({ hasNextPage: hasNext }),
- setTotalCount: ({ count }) => set({ totalCount: count }),
- setLoadingMore: ({ loading }) => set({ isLoadingMore: loading }),
-
- appendSearchResults: ({ results }) =>
- set((state) => ({
- searchResults: [...state.searchResults, ...results],
- })),
-
- appendTopSounds: ({ results }) =>
- set((state) => ({
- topSoundEffects: [...state.topSoundEffects, ...results],
- })),
-
- resetPagination: () =>
- set({
- currentPage: 1,
- hasNextPage: false,
- totalCount: 0,
- isLoadingMore: false,
- }),
-
- loadSavedSounds: async () => {
- if (get().isSavedSoundsLoaded) return;
-
- try {
- set({ isLoadingSavedSounds: true, savedSoundsError: null });
- const savedSoundsData = await storageService.loadSavedSounds();
- set({
- savedSounds: savedSoundsData.sounds,
- isSavedSoundsLoaded: true,
- isLoadingSavedSounds: false,
- });
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to load saved sounds";
- set({
- savedSoundsError: errorMessage,
- isLoadingSavedSounds: false,
- });
- console.error("Failed to load saved sounds:", error);
- }
- },
-
- saveSoundEffect: async ({ soundEffect }) => {
- try {
- await storageService.saveSoundEffect({ soundEffect });
-
- const savedSoundsData = await storageService.loadSavedSounds();
- set({ savedSounds: savedSoundsData.sounds });
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to save sound";
- set({ savedSoundsError: errorMessage });
- toast.error("Failed to save sound");
- console.error("Failed to save sound:", error);
- }
- },
-
- removeSavedSound: async ({ soundId }) => {
- try {
- await storageService.removeSavedSound({ soundId });
-
- set((state) => ({
- savedSounds: state.savedSounds.filter((sound) => sound.id !== soundId),
- }));
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to remove sound";
- set({ savedSoundsError: errorMessage });
- toast.error("Failed to remove sound");
- console.error("Failed to remove sound:", error);
- }
- },
-
- isSoundSaved: ({ soundId }) => {
- const { savedSounds } = get();
- return savedSounds.some((sound) => sound.id === soundId);
- },
-
- toggleSavedSound: async ({ soundEffect }) => {
- const { isSoundSaved, saveSoundEffect, removeSavedSound } = get();
-
- if (isSoundSaved({ soundId: soundEffect.id })) {
- await removeSavedSound({ soundId: soundEffect.id });
- } else {
- await saveSoundEffect({ soundEffect });
- }
- },
-
- clearSavedSounds: async () => {
- try {
- await storageService.clearSavedSounds();
- set({
- savedSounds: [],
- savedSoundsError: null,
- });
- } catch (error) {
- const errorMessage =
- error instanceof Error ? error.message : "Failed to clear saved sounds";
- set({ savedSoundsError: errorMessage });
- toast.error("Failed to clear saved sounds");
- console.error("Failed to clear saved sounds:", error);
- }
- },
-
- addSoundToTimeline: async ({ sound }) => {
- const audioUrl = sound.previewUrl;
- if (!audioUrl) {
- toast.error("Sound file not available");
- return false;
- }
-
- try {
- const editor = EditorCore.getInstance();
- const currentTime = editor.playback.getCurrentTime();
-
- const response = await fetch(audioUrl);
- if (!response.ok)
- throw new Error(`Failed to download audio: ${response.statusText}`);
-
- const arrayBuffer = await response.arrayBuffer();
- const audioContext = new AudioContext();
- const buffer = await audioContext.decodeAudioData(arrayBuffer);
-
- const element = buildLibraryAudioElement({
- sourceUrl: audioUrl,
- name: sound.name,
- duration: sound.duration,
- startTime: currentTime,
- buffer,
- });
-
- editor.timeline.insertElement({
- placement: { mode: "auto", trackType: "audio" },
- element,
- });
- return true;
- } catch (error) {
- console.error("Failed to add sound to timeline:", error);
- toast.error(
- error instanceof Error
- ? error.message
- : "Failed to add sound to timeline",
- { id: `sound-${sound.id}` },
- );
- return false;
- }
- },
-}));
diff --git a/apps/web/src/stores/stickers-store.ts b/apps/web/src/stores/stickers-store.ts
deleted file mode 100644
index 191050dd..00000000
--- a/apps/web/src/stores/stickers-store.ts
+++ /dev/null
@@ -1,238 +0,0 @@
-import { create } from "zustand";
-import { persist } from "zustand/middleware";
-import {
- browseAll,
- browseCategory,
- searchAll,
- searchStickers as searchStickersFromProviders,
-} from "@/lib/stickers";
-import type { StickerBrowseResult, StickerSearchResult } from "@/lib/stickers";
-import { STICKER_CATEGORIES } from "@/lib/stickers/categories";
-import type { StickerCategory } from "@/lib/stickers/types";
-import { registerDefaultStickerProviders } from "@/lib/stickers/providers";
-import { stickersRegistry } from "@/lib/stickers/registry";
-import { parseStickerId } from "@/lib/stickers/sticker-id";
-
-const MAX_RECENT_STICKERS = 50;
-let browseRequestVersion = 0;
-
-function isValidStickerId(value: unknown): value is string {
- if (typeof value !== "string") {
- return false;
- }
-
- try {
- const parsed = parseStickerId({ stickerId: value });
- return stickersRegistry.has(parsed.providerId);
- } catch {
- return false;
- }
-}
-
-function sanitizeRecentStickers({
- recentStickers,
-}: {
- recentStickers: unknown;
-}): string[] {
- registerDefaultStickerProviders({});
-
- if (!Array.isArray(recentStickers)) {
- return [];
- }
-
- const sanitized: string[] = [];
- for (const stickerId of recentStickers) {
- if (!isValidStickerId(stickerId)) {
- continue;
- }
- if (sanitized.includes(stickerId)) {
- continue;
- }
- sanitized.push(stickerId);
- if (sanitized.length >= MAX_RECENT_STICKERS) {
- break;
- }
- }
-
- return sanitized;
-}
-
-type ViewMode = "search" | "browse";
-
-interface StickersStore {
- searchQuery: string;
- selectedCategory: StickerCategory;
- viewMode: ViewMode;
- searchResults: StickerSearchResult | null;
- browseContent: StickerBrowseResult | null;
- recentStickers: string[];
- isSearching: boolean;
- isBrowsing: boolean;
-
- setSearchQuery: ({ query }: { query: string }) => void;
- setSelectedCategory: ({ category }: { category: StickerCategory }) => void;
- searchStickers: ({ query }: { query: string }) => Promise<void>;
- browseStickers: () => Promise<void>;
- addToRecentStickers: ({ stickerId }: { stickerId: string }) => void;
- clearRecentStickers: () => void;
-}
-
-export const useStickersStore = create<StickersStore>()(
- persist(
- (set, get) => ({
- searchQuery: "",
- selectedCategory: "all",
- viewMode: "browse",
-
- searchResults: null,
- browseContent: null,
- recentStickers: [],
-
- isSearching: false,
- isBrowsing: false,
-
- setSearchQuery: ({ query }) => set({ searchQuery: query }),
-
- setSelectedCategory: ({ category }) => {
- set({
- selectedCategory: category in STICKER_CATEGORIES ? category : "all",
- browseContent: null,
- });
-
- const query = get().searchQuery.trim();
- if (query) {
- void get().searchStickers({ query });
- return;
- }
-
- void get().browseStickers();
- },
-
- searchStickers: async ({ query }: { query: string }) => {
- const trimmedQuery = query.trim();
- if (!trimmedQuery) {
- set({ searchResults: null, viewMode: "browse" });
- await get().browseStickers();
- return;
- }
-
- const category = get().selectedCategory;
- const selectedCategory =
- category in STICKER_CATEGORIES ? category : "all";
-
- set({ isSearching: true, viewMode: "search" });
- try {
- if (selectedCategory === "all") {
- const browseContent = await searchAll({ query: trimmedQuery });
- set({ browseContent, searchResults: null });
- } else {
- const results = await searchStickersFromProviders({
- query: trimmedQuery,
- category: selectedCategory,
- limit: 100,
- });
- set({ searchResults: results });
- }
- } catch (error) {
- console.error("Search failed:", error);
- set({ searchResults: null });
- } finally {
- set({ isSearching: false });
- }
- },
-
- browseStickers: async () => {
- const version = ++browseRequestVersion;
- const category = get().selectedCategory;
- const selectedCategory =
- category in STICKER_CATEGORIES ? category : "all";
-
- set({ isBrowsing: true, viewMode: "browse" });
- try {
- const browseContent =
- selectedCategory === "all"
- ? await browseAll({
- recentStickers: get().recentStickers,
- })
- : await browseCategory({
- category: selectedCategory,
- });
-
- if (version !== browseRequestVersion) return;
- set({ browseContent });
- } catch (error) {
- if (version !== browseRequestVersion) return;
- console.error("Browse failed:", error);
- set({ browseContent: null });
- } finally {
- if (version === browseRequestVersion) {
- set({ isBrowsing: false });
- }
- }
- },
-
- addToRecentStickers: ({ stickerId }: { stickerId: string }) => {
- const sanitizedStickerIds = sanitizeRecentStickers({
- recentStickers: [stickerId],
- });
- if (sanitizedStickerIds.length === 0) {
- return;
- }
-
- set((state) => {
- const recent = [
- sanitizedStickerIds[0],
- ...state.recentStickers.filter((s) => s !== sanitizedStickerIds[0]),
- ];
- return {
- recentStickers: recent.slice(0, MAX_RECENT_STICKERS),
- };
- });
-
- if (get().viewMode === "browse" && get().selectedCategory === "all") {
- void get().browseStickers();
- }
- },
-
- clearRecentStickers: () => {
- set({ recentStickers: [] });
-
- if (get().viewMode === "browse" && get().selectedCategory === "all") {
- void get().browseStickers();
- }
- },
- }),
- {
- name: "stickers-settings",
- version: 1,
- migrate: (persistedState) => {
- if (
- typeof persistedState === "object" &&
- persistedState !== null &&
- "selectedCategory" in persistedState
- ) {
- const typedState = persistedState as {
- selectedCategory?: string;
- recentStickers?: string[];
- };
- const category = typedState.selectedCategory ?? "all";
- return {
- ...typedState,
- selectedCategory:
- category in STICKER_CATEGORIES
- ? (category as StickerCategory)
- : "all",
- recentStickers: sanitizeRecentStickers({
- recentStickers: typedState.recentStickers ?? [],
- }),
- };
- }
- return persistedState;
- },
- partialize: (state) => ({
- selectedCategory: state.selectedCategory,
- recentStickers: state.recentStickers,
- }),
- },
- ),
-);
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
deleted file mode 100644
index b7ddb885..00000000
--- a/apps/web/src/stores/timeline-store.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * UI state for the timeline
- * For core logic, use EditorCore instead.
- */
-
-import { create } from "zustand";
-import { persist } from "zustand/middleware";
-
-interface TimelineStore {
- snappingEnabled: boolean;
- toggleSnapping: () => void;
- rippleEditingEnabled: boolean;
- toggleRippleEditing: () => void;
- expandedElementIds: Set<string>;
- toggleElementExpanded: (elementId: string) => void;
-}
-
-export const useTimelineStore = create<TimelineStore>()(
- persist(
- (set) => ({
- snappingEnabled: true,
-
- toggleSnapping: () => {
- set((state) => ({ snappingEnabled: !state.snappingEnabled }));
- },
-
- rippleEditingEnabled: false,
-
- toggleRippleEditing: () => {
- set((state) => ({
- rippleEditingEnabled: !state.rippleEditingEnabled,
- }));
- },
-
- expandedElementIds: new Set<string>(),
-
- toggleElementExpanded: (elementId) => {
- set((state) => {
- const next = new Set(state.expandedElementIds);
- if (next.has(elementId)) {
- next.delete(elementId);
- } else {
- next.add(elementId);
- }
- return { expandedElementIds: next };
- });
- },
- }),
- {
- name: "timeline-store",
- partialize: (state) => ({
- snappingEnabled: state.snappingEnabled,
- rippleEditingEnabled: state.rippleEditingEnabled,
- }),
- },
- ),
-);