diff options
| author | Maze <167211895+mazeincoding@users.noreply.github.com> | 2026-01-31 00:20:04 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 00:20:04 +0100 |
| commit | 7bf0984698e43b9aa2dd879f65951318ed410390 (patch) | |
| tree | ccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/stores/stickers-store.ts | |
| parent | 0173db99448d1c4c6415dcf15272e17551b48b04 (diff) | |
| download | opencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz | |
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/stores/stickers-store.ts')
| -rw-r--r-- | apps/web/src/stores/stickers-store.ts | 343 |
1 files changed, 144 insertions, 199 deletions
diff --git a/apps/web/src/stores/stickers-store.ts b/apps/web/src/stores/stickers-store.ts index c7c9ef98..edc78220 100644 --- a/apps/web/src/stores/stickers-store.ts +++ b/apps/web/src/stores/stickers-store.ts @@ -1,231 +1,176 @@ import { create } from "zustand"; import { - getCollections, - getCollection, - searchIcons, - downloadSvgAsText, - svgToFile, - type IconSet, - type CollectionInfo, - type IconSearchResult, + getCollections, + getCollection, + searchIcons, + type IconSet, + type CollectionInfo, + type IconSearchResult, } from "@/lib/iconify-api"; -import { useProjectStore } from "@/stores/project-store"; -import { useMediaStore } from "@/stores/media-store"; -import { useTimelineStore } from "@/stores/timeline-store"; -import { usePlaybackStore } from "@/stores/playback-store"; -import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; -import type { MediaFile } from "@/types/media"; +import { EditorCore } from "@/core"; +import { buildStickerElement } from "@/lib/timeline/element-utils"; +import { STICKER_CATEGORY_CONFIG } from "@/constants/stickers-constants"; +import type { StickerCategory } from "@/types/stickers"; -export type StickerCategory = "all" | "general" | "brands" | "emoji"; +type ViewMode = "search" | "browse" | "collection"; interface StickersStore { - searchQuery: string; - selectedCategory: StickerCategory; - selectedCollection: string | null; - viewMode: "search" | "browse" | "collection"; + searchQuery: string; + selectedCategory: StickerCategory; + selectedCollection: string | null; + viewMode: ViewMode; + collections: Record<string, IconSet>; + currentCollection: CollectionInfo | null; + searchResults: IconSearchResult | null; + recentStickers: string[]; + isLoadingCollections: boolean; + isLoadingCollection: boolean; + isSearching: boolean; + addingSticker: string | null; - collections: Record<string, IconSet>; - currentCollection: CollectionInfo | null; - searchResults: IconSearchResult | null; - recentStickers: string[]; - isLoadingCollections: boolean; - isLoadingCollection: boolean; - isSearching: boolean; - isDownloading: boolean; - addingSticker: string | null; - - setSearchQuery: (query: string) => void; - setSelectedCategory: (category: StickerCategory) => void; - setSelectedCollection: (collection: string | null) => void; - setViewMode: (mode: "search" | "browse" | "collection") => void; - - loadCollections: () => Promise<void>; - loadCollection: (prefix: string) => Promise<void>; - searchStickers: (query: string) => Promise<void>; - downloadSticker: (iconName: string) => Promise<File | null>; - addStickerToTimeline: (iconName: string) => Promise<void>; - - addToRecentStickers: (iconName: string) => void; - clearRecentStickers: () => void; + setSearchQuery: ({ query }: { query: string }) => void; + setSelectedCategory: ({ category }: { category: StickerCategory }) => void; + setSelectedCollection: ({ + collection, + }: { + collection: string | null; + }) => void; + setViewMode: ({ mode }: { mode: ViewMode }) => void; + loadCollections: () => Promise<void>; + loadCollection: ({ prefix }: { prefix: string }) => Promise<void>; + searchStickers: ({ query }: { query: string }) => Promise<void>; + addStickerToTimeline: ({ iconName }: { iconName: string }) => void; + addToRecentStickers: ({ iconName }: { iconName: string }) => void; + clearRecentStickers: () => void; } const MAX_RECENT_STICKERS = 50; export const useStickersStore = create<StickersStore>((set, get) => ({ - searchQuery: "", - selectedCategory: "all", - selectedCollection: null, - viewMode: "browse", - - collections: {}, - currentCollection: null, - searchResults: null, - recentStickers: [], - - isLoadingCollections: false, - isLoadingCollection: false, - isSearching: false, - isDownloading: false, - addingSticker: null, - - setSearchQuery: (query) => set({ searchQuery: query }), - - setSelectedCategory: (category) => - set({ - selectedCategory: category, - viewMode: "browse", - selectedCollection: null, - currentCollection: null, - }), - - setSelectedCollection: (collection) => { - set({ - selectedCollection: collection, - viewMode: collection ? "collection" : "browse", - currentCollection: null, - }); - - if (collection) { - get().loadCollection(collection); - } - }, - - setViewMode: (mode) => set({ viewMode: mode }), + searchQuery: "", + selectedCategory: "all", + selectedCollection: null, + viewMode: "browse", - loadCollections: async () => { - set({ isLoadingCollections: true }); - try { - const collections = await getCollections(); - set({ collections }); - } catch (error) { - console.error("Failed to load collections:", error); - } finally { - set({ isLoadingCollections: false }); - } - }, + collections: {}, + currentCollection: null, + searchResults: null, + recentStickers: [], - loadCollection: async (prefix: string) => { - set({ isLoadingCollection: true }); - try { - const collection = await getCollection(prefix); - set({ currentCollection: collection }); - } catch (error) { - console.error(`Failed to load collection ${prefix}:`, error); - set({ currentCollection: null }); - } finally { - set({ isLoadingCollection: false }); - } - }, + isLoadingCollections: false, + isLoadingCollection: false, + isSearching: false, + addingSticker: null, - searchStickers: async (query: string) => { - if (!query.trim()) { - set({ searchResults: null, viewMode: "browse" }); - return; - } + setSearchQuery: ({ query }) => set({ searchQuery: query }), - const { selectedCategory } = get(); + setSelectedCategory: ({ category }) => + set({ + selectedCategory: category, + viewMode: "browse", + selectedCollection: null, + currentCollection: null, + }), - set({ isSearching: true, viewMode: "search" }); - try { - let category: string | undefined; + setSelectedCollection: ({ collection }) => { + set({ + selectedCollection: collection, + viewMode: collection ? "collection" : "browse", + currentCollection: null, + }); - if (selectedCategory !== "all") { - if (selectedCategory === "general") { - category = "General"; - } else if (selectedCategory === "brands") { - category = "Brands / Social"; - } else if (selectedCategory === "emoji") { - category = "Emoji"; - } - } + if (collection) { + get().loadCollection({ prefix: collection }); + } + }, - const results = await searchIcons(query, 100, undefined, category); - set({ searchResults: results }); - } catch (error) { - console.error("Search failed:", error); - set({ searchResults: null }); - } finally { - set({ isSearching: false }); - } - }, + setViewMode: ({ mode }) => set({ viewMode: mode }), - downloadSticker: async (iconName: string) => { - set({ isDownloading: true }); - try { - const svgText = await downloadSvgAsText(iconName, { - width: 200, - height: 200, - }); + loadCollections: async () => { + set({ isLoadingCollections: true }); + try { + const collections = await getCollections(); + set({ collections }); + } catch (error) { + console.error("Failed to load collections:", error); + } finally { + set({ isLoadingCollections: false }); + } + }, - const fileName = `${iconName.replace(":", "-")}.svg`; - const file = svgToFile(svgText, fileName); + loadCollection: async ({ prefix }: { prefix: string }) => { + set({ isLoadingCollection: true }); + try { + const collection = await getCollection(prefix); + set({ currentCollection: collection }); + } catch (error) { + console.error(`Failed to load collection ${prefix}:`, error); + set({ currentCollection: null }); + } finally { + set({ isLoadingCollection: false }); + } + }, - get().addToRecentStickers(iconName); + searchStickers: async ({ query }: { query: string }) => { + if (!query.trim()) { + set({ searchResults: null, viewMode: "browse" }); + return; + } - return file; - } catch (error) { - console.error(`Failed to download sticker ${iconName}:`, error); - return null; - } finally { - set({ isDownloading: false }); - } - }, + const { selectedCategory } = get(); - addStickerToTimeline: async (iconName: string) => { - set({ addingSticker: iconName }); - try { - const { activeProject } = useProjectStore.getState(); - if (!activeProject) { - throw new Error("No active project"); - } + set({ isSearching: true, viewMode: "search" }); + try { + const category = STICKER_CATEGORY_CONFIG[selectedCategory]; + const results = await searchIcons(query, 100, undefined, category); + set({ searchResults: results }); + } catch (error) { + console.error("Search failed:", error); + set({ searchResults: null }); + } finally { + set({ isSearching: false }); + } + }, - const file = await get().downloadSticker(iconName); - if (!file) { - throw new Error("Failed to download sticker"); - } + addStickerToTimeline: ({ iconName }: { iconName: string }) => { + set({ addingSticker: iconName }); + try { + const editor = EditorCore.getInstance(); + const currentTime = editor.playback.getCurrentTime(); + const tracks = editor.timeline.getTracks(); - const mediaItem: Omit<MediaFile, "id"> = { - name: iconName.replace(":", "-"), - type: "image", - file, - url: URL.createObjectURL(file), - width: 200, - height: 200, - duration: TIMELINE_CONSTANTS.DEFAULT_IMAGE_DURATION, - ephemeral: false, - }; + const stickerTrack = tracks.find((t) => t.type === "sticker"); + let trackId: string; - const { addMediaFile } = useMediaStore.getState(); - await addMediaFile(activeProject.id, mediaItem); + if (stickerTrack) { + trackId = stickerTrack.id; + } else { + trackId = editor.timeline.addTrack({ type: "sticker" }); + } - const added = useMediaStore - .getState() - .mediaFiles.find( - (m) => m.url === mediaItem.url && m.name === mediaItem.name - ); - if (!added) { - throw new Error("Sticker not in media store"); - } + const element = buildStickerElement({ iconName, startTime: currentTime }); + editor.timeline.insertElement({ + placement: { mode: "explicit", trackId }, + element, + }); - const { currentTime } = usePlaybackStore.getState(); - const { addElementAtTime } = useTimelineStore.getState(); - addElementAtTime(added, currentTime); - } finally { - set({ addingSticker: null }); - } - }, + get().addToRecentStickers({ iconName }); + } finally { + set({ addingSticker: null }); + } + }, - addToRecentStickers: (iconName: string) => { - set((state) => { - const recent = [ - iconName, - ...state.recentStickers.filter((s) => s !== iconName), - ]; - return { - recentStickers: recent.slice(0, MAX_RECENT_STICKERS), - }; - }); - }, + addToRecentStickers: ({ iconName }: { iconName: string }) => { + set((state) => { + const recent = [ + iconName, + ...state.recentStickers.filter((s) => s !== iconName), + ]; + return { + recentStickers: recent.slice(0, MAX_RECENT_STICKERS), + }; + }); + }, - clearRecentStickers: () => set({ recentStickers: [] }), + clearRecentStickers: () => set({ recentStickers: [] }), })); |
