tryemoji / src /components /try-emoji.tsx
yadongxie's picture
feat: add web
89682f8
"use client";
import { Dice } from "@/components/dice";
import { EmojiSelector } from "@/components/emoji-selector";
import { GithubForkRibbon } from "@/components/github";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Slider } from "@/components/ui/slider";
import { Toaster } from "@/components/ui/toaster";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useToast } from "@/components/ui/use-toast";
import { presetImage, presetArtStyles } from "@/util/presets";
import { usePrevious } from "@/util/use-previous";
import { useResponse } from "@/util/use-response";
import { getShareUrl, Option, useShare } from "@/util/use-share";
import { clsx } from "clsx";
import { Check, Download, Share2 } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import {
FacebookIcon,
FacebookShareButton,
LinkedinIcon,
LinkedinShareButton,
TwitterShareButton,
XIcon,
} from "react-share";
import { setEmojiFavicon } from "@/util/set-emoji-favicon";
export default function TryEmoji() {
const { option: presetOption, hasShare } = useShare();
const { toast } = useToast();
const [emoji, setEmoji] = useState({
emoji: presetOption.emoji,
name: presetOption.name,
});
const [preset, setPreset] = useState(
presetArtStyles.find((p) => p.prompt === presetOption.prompt)!,
);
const [strength, setStrength] = useState(presetOption.strength);
const [seed, setSeed] = useState(presetOption.seed);
const shareOption: Option = useMemo(() => {
return {
emoji: emoji.emoji,
name: emoji.name,
prompt: preset.prompt,
seed: seed,
strength: strength,
};
}, [emoji.emoji, emoji.name, preset.prompt, seed, strength]);
const { image, loading } = useResponse(
hasShare,
emoji.emoji,
emoji.name,
preset.prompt,
strength,
seed,
);
const previousImage = usePrevious(image);
const mergedImage = useMemo(
() => image || previousImage || presetImage,
[image, previousImage],
);
useEffect(() => {
setEmojiFavicon(emoji.emoji);
}, [emoji.emoji]);
const shareKey = useMemo(() => {
return getShareUrl(shareOption);
}, [shareOption]);
const shareUrl = useMemo(() => {
return `https://tryemoji.com?share=${shareKey}`;
}, [shareKey]);
const warmOrg: Promise<void> = useMemo(() => {
if (image) {
return fetch("/api/share", {
method: "POST",
body: JSON.stringify({
image: image,
key: shareKey,
}),
}).then();
} else {
return new Promise((resolve) => resolve());
}
}, [image, shareKey]);
return (
<TooltipProvider delayDuration={50}>
<Toaster />
<div className="min-h-screen flex flex-col gap-4 bg-zinc-950 items-center justify-center py-4 md:py-12">
<GithubForkRibbon></GithubForkRibbon>
<div className="text-6xl text-zinc-100">
{emoji.emoji || "🐤"} tryEmoji{" "}
</div>
<div className="text-xl text-zinc-100">
Turn emoji into amazing artwork via AI
</div>
<div className="flex items-center justify-center flex-col md:flex-row gap-2 md:gap-4">
<div className="flex-0 w-full md:w-80">
<EmojiSelector
onSelect={(e) => {
const prefix =
e.keywords.indexOf("animal") > -1 ? "super cute" : "";
const keyword = e.keywords.join(", ");
const emoji = e.native;
const name = `${prefix} ${e.name}, ${keyword}`;
setEmoji({ emoji, name });
}}
></EmojiSelector>
</div>
<div className="flex-1">
<div className="max-w-[100vw] h-auto md:h-[512px] w-[512px] rounded-lg overflow-hidden relative">
<img src={mergedImage} className="h-full w-full object-contain" />
<div
className={clsx("transition absolute inset-0", {
"backdrop-blur-xl": loading,
})}
></div>
<div className="hidden absolute top-2 right-2 md:flex gap-2 items-center">
<FacebookShareButton
beforeOnClick={() => warmOrg}
url={shareUrl}
>
<FacebookIcon className="rounded" size={24}></FacebookIcon>
</FacebookShareButton>
<TwitterShareButton onClick={() => warmOrg} url={shareUrl}>
<XIcon className="rounded" size={24} />
</TwitterShareButton>
<LinkedinShareButton onClick={() => warmOrg} url={shareUrl}>
<LinkedinIcon className="rounded" size={24} />
</LinkedinShareButton>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => {
warmOrg.then(() => {
navigator.clipboard.writeText(shareUrl).then(() => {
toast({
description: (
<div className="flex gap-2 text-sm items-center">
<Check className="text-green-500"></Check>
Copied, paste to share
</div>
),
});
});
});
}}
className="flex-0 rounded bg-amber-600 w-6 flex items-center justify-center h-6 text-sm font-semibold text-white shadow-sm hover:bg-amber-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600"
>
<Share2 size={16}></Share2>
</button>
</TooltipTrigger>
<TooltipContent>
<p>Share</p>
</TooltipContent>
</Tooltip>
</div>
<div className="absolute bottom-2 left-2 right-2 flex gap-2 flex-wrap">
<div className="flex flex-auto gap-2 w-full md:w-auto">
<div className="text-xl text-zinc-100">AI</div>
<Tooltip>
<TooltipTrigger asChild>
<Slider
className="flex-1"
defaultValue={[strength]}
onValueChange={(v) => setStrength(v[0])}
max={0.7}
min={0.5}
step={0.025}
/>
</TooltipTrigger>
<TooltipContent>
<p>AI strength</p>
</TooltipContent>
</Tooltip>
</div>
<div className="flex flex-auto md:flex-grow-0 gap-2 w-full md:w-auto">
<Select
value={preset.artist}
onValueChange={(value) =>
setPreset(
presetArtStyles.find((p) => p.artist === value)!,
)
}
>
<Tooltip>
<TooltipTrigger asChild>
<SelectTrigger className="flex-1 w-56 border-0 rounded bg-amber-600 px-2 py-1 text-sm font-semibold text-white shadow-sm hover:bg-amber-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
</TooltipTrigger>
<TooltipContent>
<p>Art style</p>
</TooltipContent>
</Tooltip>
<SelectContent>
{presetArtStyles.map((p) => (
<SelectItem key={p.artist} value={p.artist}>
{p.artist}
</SelectItem>
))}
</SelectContent>
</Select>
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={() => {
setSeed(Math.floor(Math.random() * 2159232));
}}
className="flex-0 rounded bg-amber-600 px-0.5 py-0.5 text-sm font-semibold text-white shadow-sm hover:bg-amber-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600"
>
<Dice></Dice>
</button>
</TooltipTrigger>
<TooltipContent>
<p>Random</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<a
href={image}
download
className="flex-0 block rounded bg-amber-600 px-0.5 py-0.5 text-sm font-semibold text-white shadow-sm hover:bg-amber-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600"
>
<Download />
</a>
</TooltipTrigger>
<TooltipContent>
<p>Download</p>
</TooltipContent>
</Tooltip>
</div>
</div>
</div>
</div>
</div>
<div className="text-xs text-zinc-500 font-sans mt-8 flex gap-2">
<a
className="hover:text-zinc-100"
href="https://lepton.ai"
target="_blank"
>
Powered by Lepton AI
</a>
|
<a
className="hover:text-zinc-100"
href="https://github.com/leptonai/tryemoji"
target="_blank"
>
Github
</a>
</div>
</div>
</TooltipProvider>
);
}