summaryrefslogtreecommitdiff
path: root/apps/web/src/stores/stickers-store.ts
diff options
context:
space:
mode:
authorMaze <167211895+mazeincoding@users.noreply.github.com>2026-01-31 00:20:04 +0100
committerGitHub <noreply@github.com>2026-01-31 00:20:04 +0100
commit7bf0984698e43b9aa2dd879f65951318ed410390 (patch)
treeccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/stores/stickers-store.ts
parent0173db99448d1c4c6415dcf15272e17551b48b04 (diff)
downloadopencut-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.ts343
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: [] }),
}));