diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-08-15 02:12:16 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-08-15 02:12:16 +0200 |
| commit | 7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae (patch) | |
| tree | 25c6cddf67e3227d99c2c28f71496851962f1f45 /apps/web/src/stores/stickers-store.ts | |
| parent | 5e8a489a147c148b6495a6a25fb4dd9725e6fca6 (diff) | |
| parent | c3f3345d7b1ac820b411a5adbca3172378642953 (diff) | |
| download | opencut-7f91c9ca00cd51bea1cd1e97eeb3c58b7e7a1bae.tar.gz | |
Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging
Diffstat (limited to 'apps/web/src/stores/stickers-store.ts')
| -rw-r--r-- | apps/web/src/stores/stickers-store.ts | 178 |
1 files changed, 178 insertions, 0 deletions
diff --git a/apps/web/src/stores/stickers-store.ts b/apps/web/src/stores/stickers-store.ts new file mode 100644 index 00000000..da8b926b --- /dev/null +++ b/apps/web/src/stores/stickers-store.ts @@ -0,0 +1,178 @@ +import { create } from "zustand"; +import { + getCollections, + getCollection, + searchIcons, + downloadSvgAsText, + svgToFile, + type IconSet, + type CollectionInfo, + type IconSearchResult, +} from "@/lib/iconify-api"; + +export type StickerCategory = "all" | "general" | "brands" | "emoji"; + +interface StickersStore { + searchQuery: string; + selectedCategory: StickerCategory; + selectedCollection: string | null; + viewMode: "search" | "browse" | "collection"; + + collections: Record<string, IconSet>; + currentCollection: CollectionInfo | null; + searchResults: IconSearchResult | null; + recentStickers: string[]; + isLoadingCollections: boolean; + isLoadingCollection: boolean; + isSearching: boolean; + isDownloading: boolean; + + 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>; + + addToRecentStickers: (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, + + 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 }), + + 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 }); + } + }, + + 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 }); + } + }, + + searchStickers: async (query: string) => { + if (!query.trim()) { + set({ searchResults: null, viewMode: "browse" }); + return; + } + + const { selectedCategory } = get(); + + set({ isSearching: true, viewMode: "search" }); + try { + let category: string | undefined; + + if (selectedCategory !== "all") { + if (selectedCategory === "general") { + category = "General"; + } else if (selectedCategory === "brands") { + category = "Brands / Social"; + } else if (selectedCategory === "emoji") { + category = "Emoji"; + } + } + + 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 }); + } + }, + + downloadSticker: async (iconName: string) => { + set({ isDownloading: true }); + try { + const svgText = await downloadSvgAsText(iconName, { + width: 200, + height: 200, + }); + + const fileName = `${iconName.replace(":", "-")}.svg`; + const file = svgToFile(svgText, fileName); + + get().addToRecentStickers(iconName); + + return file; + } catch (error) { + console.error(`Failed to download sticker ${iconName}:`, error); + return null; + } finally { + set({ isDownloading: false }); + } + }, + + addToRecentStickers: (iconName: string) => { + set((state) => { + const recent = [ + iconName, + ...state.recentStickers.filter((s) => s !== iconName), + ]; + return { + recentStickers: recent.slice(0, MAX_RECENT_STICKERS), + }; + }); + }, + + clearRecentStickers: () => set({ recentStickers: [] }), +})); |
