"use client";
import Image from "next/image";
import type { CSSProperties } from "react";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { PanelView } from "@/components/editor/panels/assets/views/base-view";
import { DraggableItem } from "@/components/editor/panels/assets/draggable-item";
import { Button } from "@/components/ui/button";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import {
resolveStickerId,
type StickerItem as StickerData,
} from "@/lib/stickers";
import { useStickersStore } from "@/stores/stickers-store";
import { cn } from "@/utils/ui";
import {
HappyIcon,
ClockIcon,
MultiplicationSignIcon,
Search01Icon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Spinner } from "@/components/ui/spinner";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { OcSlidersVerticalIcon } from "@opencut/ui/icons";
import type { StickerCategory } from "@/types/stickers";
import { STICKER_CATEGORIES } from "@/constants/sticker-constants";
import { parseStickerId } from "@/lib/stickers/sticker-id";
export function StickersView() {
const { selectedCategory, setSelectedCategory } = useStickersStore();
return (
}
>
);
}
function StickerGrid({
items,
shouldCapSize = false,
}: {
items: StickerData[];
shouldCapSize?: boolean;
}) {
const gridStyle: CSSProperties & {
"--sticker-min": string;
"--sticker-max"?: string;
} = {
gridTemplateColumns: shouldCapSize
? "repeat(auto-fill, minmax(var(--sticker-min, 96px), var(--sticker-max, 160px)))"
: "repeat(auto-fit, minmax(var(--sticker-min, 96px), 1fr))",
"--sticker-min": "96px",
...(shouldCapSize ? { "--sticker-max": "160px" } : {}),
};
return (
{items.map((item) => (
))}
);
}
function EmptyView({ message }: { message: string }) {
return (
No stickers found
{message}
);
}
function StickersContentView() {
const {
searchQuery,
viewMode,
searchResults,
recentStickers,
isSearching,
clearRecentStickers,
} = useStickersStore();
const itemsToDisplay = useMemo(() => {
if (viewMode === "search" && searchResults) {
return searchResults.items;
}
return [];
}, [viewMode, searchResults]);
const recentStickerItems = useMemo(() => {
const items: StickerData[] = [];
for (const stickerId of recentStickers) {
const recentStickerItem = toRecentStickerItem({ stickerId });
if (recentStickerItem) {
items.push(recentStickerItem);
}
}
return items;
}, [recentStickers]);
return (
{recentStickerItems.length > 0 && viewMode === "browse" && (
Recent
Clear recent stickers
)}
{viewMode === "search" && (
{isSearching ? (
) : searchResults?.items.length ? (
{searchResults.total} results
) : searchQuery ? (
) : null}
)}
);
}
interface StickerItemProps {
item: StickerData;
shouldCapSize?: boolean;
}
function StickerItem({ item, shouldCapSize = false }: StickerItemProps) {
const { addingSticker, addStickerToTimeline } = useStickersStore();
const isAdding = addingSticker === item.id;
const [hasImageError, setHasImageError] = useState(false);
useEffect(() => {
if (!item.id) {
return;
}
setHasImageError(false);
}, [item.id]);
const displayName = item.name;
const handleAdd = async () => {
try {
await addStickerToTimeline({
stickerId: item.id,
name: item.name,
});
} catch (error) {
console.error("Failed to add sticker:", error);
toast.error("Failed to add sticker to timeline");
}
};
const preview = hasImageError ? (
{displayName}
) : (
{
setHasImageError(true);
}}
loading="lazy"
unoptimized
/>
);
return (
);
}
function getStickerNameFromId({ stickerId }: { stickerId: string }): string {
const stickerIdParts = stickerId.split(":");
if (stickerIdParts.length <= 1) {
return stickerId;
}
return (
stickerIdParts.slice(1).join(":").split(":").pop()?.replaceAll("-", " ") ??
stickerId
);
}
function toRecentStickerItem({
stickerId,
}: {
stickerId: string;
}): StickerData | null {
try {
const { providerId } = parseStickerId({ stickerId });
return {
id: stickerId,
provider: providerId,
name: getStickerNameFromId({ stickerId }),
previewUrl: resolveStickerId({
stickerId,
options: { width: 64, height: 64 },
}),
metadata: {},
};
} catch {
return null;
}
}