mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
Merge branch 'staging' of https://github.com/mazeincoding/AppCut into staging
This commit is contained in:
@@ -16,6 +16,8 @@ export interface MediaItem {
|
||||
width?: number; // For video/image width
|
||||
height?: number; // For video/image height
|
||||
fps?: number; // For video frame rate
|
||||
// Ephemeral items are used by timeline directly and should not appear in the media library or be persisted
|
||||
ephemeral?: boolean;
|
||||
// Text-specific properties
|
||||
content?: string; // Text content
|
||||
fontSize?: number; // Font size
|
||||
|
||||
@@ -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: [] }),
|
||||
}));
|
||||
Reference in New Issue
Block a user