|
"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> |
|
); |
|
} |
|
|