diff options
Diffstat (limited to 'apps/web/src/stores')
| -rw-r--r-- | apps/web/src/stores/editor-store.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings-store.ts | 158 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings/migrations/index.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings/migrations/v2-to-v3.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings/migrations/v3-to-v4.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings/migrations/v4-to-v5.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/stores/keybindings/migrations/v5-to-v6.ts | 17 | ||||
| -rw-r--r-- | apps/web/src/stores/preview-store.ts | 72 | ||||
| -rw-r--r-- | apps/web/src/stores/sounds-store.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/stores/stickers-store.ts | 177 | ||||
| -rw-r--r-- | apps/web/src/stores/timeline-store.ts | 2 |
11 files changed, 310 insertions, 132 deletions
diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts index ad351bf9..96009928 100644 --- a/apps/web/src/stores/editor-store.ts +++ b/apps/web/src/stores/editor-store.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { DEFAULT_CANVAS_PRESETS } from "@/constants/project-constants"; -import type { TCanvasSize } from "@/types/project"; +import type { TCanvasSize } from "@/lib/project/types"; interface EditorState { isInitializing: boolean; diff --git a/apps/web/src/stores/keybindings-store.ts b/apps/web/src/stores/keybindings-store.ts index 906e9aa8..b4cf2703 100644 --- a/apps/web/src/stores/keybindings-store.ts +++ b/apps/web/src/stores/keybindings-store.ts @@ -6,10 +6,10 @@ 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 "@/types/keybinding"; +import type { KeybindingConfig, ShortcutKey } from "@/lib/actions/keybinding"; import { runMigrations, CURRENT_VERSION } from "./keybindings/migrations"; -export const defaultKeybindings: KeybindingConfig = getDefaultShortcuts(); +const defaultKeybindings: KeybindingConfig = getDefaultShortcuts(); export interface KeybindingConflict { key: ShortcutKey; @@ -20,7 +20,9 @@ export interface KeybindingConflict { interface KeybindingsState { keybindings: KeybindingConfig; isCustomized: boolean; - keybindingsEnabled: boolean; + overlayDepth: number; + openOverlayIds: string[]; + isLoadingProject: boolean; isRecording: boolean; updateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => void; @@ -28,8 +30,9 @@ interface KeybindingsState { resetToDefaults: () => void; importKeybindings: (config: KeybindingConfig) => void; exportKeybindings: () => KeybindingConfig; - enableKeybindings: () => void; - disableKeybindings: () => void; + openOverlay: (overlayId: string, source: string) => void; + closeOverlay: (overlayId: string, source: string) => void; + setLoadingProject: (loading: boolean) => void; setIsRecording: (isRecording: boolean) => void; validateKeybinding: ( key: ShortcutKey, @@ -48,9 +51,112 @@ export const useKeybindingsStore = create<KeybindingsState>()( (set, get) => ({ keybindings: { ...defaultKeybindings }, isCustomized: false, - keybindingsEnabled: true, + overlayDepth: 0, + openOverlayIds: [], + isLoadingProject: false, isRecording: false, + openOverlay: (overlayId, source) => + set((s) => { + const openOverlayIds = s.openOverlayIds.includes(overlayId) + ? s.openOverlayIds + : [...s.openOverlayIds, overlayId]; + const nextOverlayDepth = openOverlayIds.length; + // #region agent log + fetch( + "http://127.0.0.1:7245/ingest/669b22f8-172b-4e65-aa3f-1c702ede83f7", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Debug-Session-Id": "3997d9", + }, + body: JSON.stringify({ + sessionId: "3997d9", + runId: "initial", + hypothesisId: "H2", + location: "keybindings-store.ts:openOverlay", + message: "Overlay depth incremented", + data: { + source, + overlayId, + overlayDepth: s.overlayDepth, + nextOverlayDepth, + openOverlayIds, + }, + timestamp: Date.now(), + }), + }, + ).catch(() => {}); + // #endregion + return { + openOverlayIds, + overlayDepth: nextOverlayDepth, + }; + }), + closeOverlay: (overlayId, source) => + set((s) => { + const openOverlayIds = s.openOverlayIds.filter( + (id) => id !== overlayId, + ); + const nextOverlayDepth = openOverlayIds.length; + // #region agent log + fetch( + "http://127.0.0.1:7245/ingest/669b22f8-172b-4e65-aa3f-1c702ede83f7", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Debug-Session-Id": "3997d9", + }, + body: JSON.stringify({ + sessionId: "3997d9", + runId: "initial", + hypothesisId: "H2", + location: "keybindings-store.ts:closeOverlay", + message: "Overlay depth decremented", + data: { + source, + overlayId, + overlayDepth: s.overlayDepth, + nextOverlayDepth, + openOverlayIds, + }, + timestamp: Date.now(), + }), + }, + ).catch(() => {}); + // #endregion + return { + openOverlayIds, + overlayDepth: nextOverlayDepth, + }; + }), + setLoadingProject: (loading) => { + // #region agent log + fetch( + "http://127.0.0.1:7245/ingest/669b22f8-172b-4e65-aa3f-1c702ede83f7", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Debug-Session-Id": "3997d9", + }, + body: JSON.stringify({ + sessionId: "3997d9", + runId: "initial", + hypothesisId: "H2", + location: "keybindings-store.ts:setLoadingProject", + message: "Loading gate updated", + data: { loading }, + timestamp: Date.now(), + }), + }, + ).catch(() => {}); + // #endregion + set({ isLoadingProject: loading }); + }, + updateKeybinding: (key: ShortcutKey, action: TActionWithOptionalArgs) => { set((state) => { const newKeybindings = { ...state.keybindings }; @@ -82,17 +188,9 @@ export const useKeybindingsStore = create<KeybindingsState>()( }); }, - enableKeybindings: () => { - set({ keybindingsEnabled: true }); - }, - - disableKeybindings: () => { - set({ keybindingsEnabled: false }); - }, - - importKeybindings: (config: KeybindingConfig) => { - for (const [key] of Object.entries(config)) { - if (typeof key !== "string" || key.length === 0) { + importKeybindings: (config: KeybindingConfig) => { + for (const [key] of Object.entries(config)) { + if (typeof key !== "string" || key.length === 0) { throw new Error(`Invalid key format: ${key}`); } } @@ -124,6 +222,27 @@ export const useKeybindingsStore = create<KeybindingsState>()( return null; }, setIsRecording: (isRecording: boolean) => { + // #region agent log + fetch( + "http://127.0.0.1:7245/ingest/669b22f8-172b-4e65-aa3f-1c702ede83f7", + { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Debug-Session-Id": "3997d9", + }, + body: JSON.stringify({ + sessionId: "3997d9", + runId: "initial", + hypothesisId: "H2", + location: "keybindings-store.ts:setIsRecording", + message: "Recording gate updated", + data: { isRecording }, + timestamp: Date.now(), + }), + }, + ).catch(() => {}); + // #endregion set({ isRecording }); }, @@ -169,7 +288,10 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null { return `${modifierKey}+${key}` as ShortcutKey; } - if (isDOMElement(target) && isTypableDOMElement({ element: target as HTMLElement })) + if ( + isDOMElement(target) && + isTypableDOMElement({ element: target as HTMLElement }) + ) return null; return `${key}` as ShortcutKey; diff --git a/apps/web/src/stores/keybindings/migrations/index.ts b/apps/web/src/stores/keybindings/migrations/index.ts index e6641f41..330c7df7 100644 --- a/apps/web/src/stores/keybindings/migrations/index.ts +++ b/apps/web/src/stores/keybindings/migrations/index.ts @@ -1,6 +1,7 @@ import { v2ToV3 } from "./v2-to-v3"; import { v3ToV4 } from "./v3-to-v4"; import { v4ToV5 } from "./v4-to-v5"; +import { v5ToV6 } from "./v5-to-v6"; type MigrationFn = ({ state }: { state: unknown }) => unknown; @@ -8,9 +9,10 @@ const migrations: Record<number, MigrationFn> = { 2: v2ToV3, 3: v3ToV4, 4: v4ToV5, + 5: v5ToV6, }; -export const CURRENT_VERSION = 5; +export const CURRENT_VERSION = 6; export function runMigrations({ state, diff --git a/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts b/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts index 1517b52b..da0df588 100644 --- a/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts +++ b/apps/web/src/stores/keybindings/migrations/v2-to-v3.ts @@ -1,4 +1,4 @@ -import type { KeybindingConfig, ShortcutKey } from "@/types/keybinding"; +import type { KeybindingConfig, ShortcutKey } from "@/lib/actions/keybinding"; import type { TActionWithOptionalArgs } from "@/lib/actions"; interface V2State { diff --git a/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts b/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts index b53e082f..be3b4a64 100644 --- a/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts +++ b/apps/web/src/stores/keybindings/migrations/v3-to-v4.ts @@ -1,6 +1,6 @@ import type { TActionWithOptionalArgs } from "@/lib/actions"; -import type { ShortcutKey } from "@/types/keybinding"; -import type { KeybindingConfig } from "@/types/keybinding"; +import type { ShortcutKey } from "@/lib/actions/keybinding"; +import type { KeybindingConfig } from "@/lib/actions/keybinding"; interface V3State { keybindings: KeybindingConfig; diff --git a/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts b/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts index 2ab39247..4e23e0f4 100644 --- a/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts +++ b/apps/web/src/stores/keybindings/migrations/v4-to-v5.ts @@ -1,4 +1,4 @@ -import type { KeybindingConfig } from "@/types/keybinding"; +import type { KeybindingConfig } from "@/lib/actions/keybinding"; interface V4State { keybindings: KeybindingConfig; diff --git a/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts b/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts new file mode 100644 index 00000000..3545815d --- /dev/null +++ b/apps/web/src/stores/keybindings/migrations/v5-to-v6.ts @@ -0,0 +1,17 @@ +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/preview-store.ts b/apps/web/src/stores/preview-store.ts index 03b28fe2..cd6488b3 100644 --- a/apps/web/src/stores/preview-store.ts +++ b/apps/web/src/stores/preview-store.ts @@ -1,20 +1,28 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import type { TPlatformLayout } from "@/types/editor"; - -interface LayoutGuideSettings { - platform: TPlatformLayout | null; -} +import { isGuideId, type GuideId } from "@/lib/guides"; +import { DEFAULT_GRID_CONFIG } from "@/constants/guide-constants"; +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 { - layoutGuide: LayoutGuideSettings; + activeGuide: GuideId | null; overlays: PreviewOverlaysState; - setLayoutGuide: (settings: Partial<LayoutGuideSettings>) => void; - toggleLayoutGuide: (platform: TPlatformLayout) => void; + gridConfig: GridConfig; + toggleGuide: (guideId: GuideId) => void; + setGridConfig: (config: Partial<GridConfig>) => void; setOverlayVisibility: ({ overlay, isVisible, @@ -33,24 +41,33 @@ 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) => ({ - layoutGuide: { platform: null }, + activeGuide: null, overlays: DEFAULT_PREVIEW_OVERLAYS, - setLayoutGuide: (settings) => { + gridConfig: DEFAULT_GRID_CONFIG, + toggleGuide: (guideId) => { set((state) => ({ - layoutGuide: { - ...state.layoutGuide, - ...settings, - }, + activeGuide: state.activeGuide === guideId ? null : guideId, })); }, - toggleLayoutGuide: (platform) => { + setGridConfig: (config) => { set((state) => ({ - layoutGuide: { - platform: state.layoutGuide.platform === platform ? null : platform, - }, + gridConfig: { ...state.gridConfig, ...config }, })); }, setOverlayVisibility: ({ overlay, isVisible }) => { @@ -72,22 +89,23 @@ export const usePreviewStore = create<PreviewState>()( }), { name: "preview-settings", - version: 2, + version: 4, migrate: (persistedState) => { - const state = persistedState as - | { - layoutGuide?: LayoutGuideSettings; - overlays?: PreviewOverlaysState; - } - | undefined; + const state = persistedState as PersistedPreviewState | undefined; + return { - layoutGuide: state?.layoutGuide ?? { platform: null }, + 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) => ({ - layoutGuide: state.layoutGuide, + activeGuide: state.activeGuide, overlays: state.overlays, + gridConfig: state.gridConfig, }), }, ), diff --git a/apps/web/src/stores/sounds-store.ts b/apps/web/src/stores/sounds-store.ts index 8edbcc97..1c58bd69 100644 --- a/apps/web/src/stores/sounds-store.ts +++ b/apps/web/src/stores/sounds-store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import type { SoundEffect, SavedSound } from "@/types/sounds"; +import type { SoundEffect, SavedSound } from "@/lib/sounds/types"; import { storageService } from "@/services/storage/service"; import { toast } from "sonner"; import { EditorCore } from "@/core"; diff --git a/apps/web/src/stores/stickers-store.ts b/apps/web/src/stores/stickers-store.ts index 3f713b3d..7a678084 100644 --- a/apps/web/src/stores/stickers-store.ts +++ b/apps/web/src/stores/stickers-store.ts @@ -1,16 +1,20 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { EditorCore } from "@/core"; -import { searchStickers as searchStickersFromProviders } from "@/lib/stickers"; -import type { StickerSearchResult } from "@/lib/stickers"; -import { buildStickerElement } from "@/lib/timeline/element-utils"; +import { + browseAll, + browseCategory, + searchAll, + searchStickers as searchStickersFromProviders, +} from "@/lib/stickers"; +import type { StickerBrowseResult, StickerSearchResult } from "@/lib/stickers"; import { STICKER_CATEGORIES } from "@/constants/sticker-constants"; -import type { StickerCategory } from "@/types/stickers"; +import type { StickerCategory } from "@/lib/stickers/types"; import { registerDefaultStickerProviders } from "@/lib/stickers/providers"; -import { hasProvider } from "@/lib/stickers/registry"; +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") { @@ -19,7 +23,7 @@ function isValidStickerId(value: unknown): value is string { try { const parsed = parseStickerId({ stickerId: value }); - return hasProvider({ providerId: parsed.providerId }); + return stickersRegistry.has(parsed.providerId); } catch { return false; } @@ -60,20 +64,15 @@ interface StickersStore { selectedCategory: StickerCategory; viewMode: ViewMode; searchResults: StickerSearchResult | null; + browseContent: StickerBrowseResult | null; recentStickers: string[]; isSearching: boolean; - addingSticker: string | null; + isBrowsing: boolean; setSearchQuery: ({ query }: { query: string }) => void; setSelectedCategory: ({ category }: { category: StickerCategory }) => void; searchStickers: ({ query }: { query: string }) => Promise<void>; - addStickerToTimeline: ({ - stickerId, - name, - }: { - stickerId: string; - name?: string; - }) => void; + browseStickers: () => Promise<void>; addToRecentStickers: ({ stickerId }: { stickerId: string }) => void; clearRecentStickers: () => void; } @@ -86,22 +85,34 @@ export const useStickersStore = create<StickersStore>()( viewMode: "browse", searchResults: null, + browseContent: null, recentStickers: [], isSearching: false, - addingSticker: null, + isBrowsing: false, setSearchQuery: ({ query }) => set({ searchQuery: query }), - setSelectedCategory: ({ category }) => - set({ - selectedCategory: category in STICKER_CATEGORIES ? category : "all", - viewMode: "browse", - }), + 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 }) => { - if (!query.trim()) { + const trimmedQuery = query.trim(); + if (!trimmedQuery) { set({ searchResults: null, viewMode: "browse" }); + await get().browseStickers(); return; } @@ -111,12 +122,17 @@ export const useStickersStore = create<StickersStore>()( set({ isSearching: true, viewMode: "search" }); try { - const results = await searchStickersFromProviders({ - query, - category: selectedCategory, - limit: 100, - }); - set({ searchResults: results }); + 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 }); @@ -125,67 +141,70 @@ export const useStickersStore = create<StickersStore>()( } }, - addStickerToTimeline: ({ - stickerId, - name, - }: { - stickerId: string; - name?: string; - }) => { - set({ addingSticker: stickerId }); - try { - const editor = EditorCore.getInstance(); - const currentTime = editor.playback.getCurrentTime(); - const tracks = editor.timeline.getTracks(); + browseStickers: async () => { + const version = ++browseRequestVersion; + const category = get().selectedCategory; + const selectedCategory = + category in STICKER_CATEGORIES ? category : "all"; - const stickerTrack = tracks.find((t) => t.type === "sticker"); - let trackId: string; + set({ isBrowsing: true, viewMode: "browse" }); + try { + const browseContent = + selectedCategory === "all" + ? await browseAll({ + recentStickers: get().recentStickers, + }) + : await browseCategory({ + category: selectedCategory, + }); - if (stickerTrack) { - trackId = stickerTrack.id; - } else { - trackId = editor.timeline.addTrack({ type: "sticker" }); - } + 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 }); + } + } + }, - const element = buildStickerElement({ - stickerId, - name, - startTime: currentTime, - }); - editor.timeline.insertElement({ - placement: { mode: "explicit", trackId }, - element, - }); + addToRecentStickers: ({ stickerId }: { stickerId: string }) => { + const sanitizedStickerIds = sanitizeRecentStickers({ + recentStickers: [stickerId], + }); + if (sanitizedStickerIds.length === 0) { + return; + } - get().addToRecentStickers({ stickerId }); - } finally { - set({ addingSticker: null }); - } - }, + set((state) => { + const recent = [ + sanitizedStickerIds[0], + ...state.recentStickers.filter((s) => s !== sanitizedStickerIds[0]), + ]; + return { + recentStickers: recent.slice(0, MAX_RECENT_STICKERS), + }; + }); - addToRecentStickers: ({ stickerId }: { stickerId: string }) => { - const sanitizedStickerIds = sanitizeRecentStickers({ - recentStickers: [stickerId], - }); - if (sanitizedStickerIds.length === 0) { - return; - } + if (get().viewMode === "browse" && get().selectedCategory === "all") { + void get().browseStickers(); + } + }, - set((state) => { - const recent = [ - sanitizedStickerIds[0], - ...state.recentStickers.filter((s) => s !== sanitizedStickerIds[0]), - ]; - return { - recentStickers: recent.slice(0, MAX_RECENT_STICKERS), - }; - }); - }, + clearRecentStickers: () => { + set({ recentStickers: [] }); - clearRecentStickers: () => set({ recentStickers: [] }), + if (get().viewMode === "browse" && get().selectedCategory === "all") { + void get().browseStickers(); + } + }, }), { name: "stickers-settings", + version: 1, migrate: (persistedState) => { if ( typeof persistedState === "object" && diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts index e5723bfa..e4c8ec89 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/stores/timeline-store.ts @@ -5,7 +5,7 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import type { ClipboardItem } from "@/types/timeline"; +import type { ClipboardItem } from "@/lib/timeline"; interface TimelineStore { snappingEnabled: boolean; |
