| "use client" |
|
|
| import { useEffect, useRef, useState, useTransition } from "react" |
| import { RxReload } from "react-icons/rx" |
|
|
| import { RenderedScene } from "@/types" |
|
|
| import { getRender, newRender } from "@/app/engine/render" |
| import { useStore } from "@/app/store" |
|
|
| import { cn } from "@/lib/utils" |
| import { getInitialRenderedScene } from "@/lib/getInitialRenderedScene" |
| import { Progress } from "@/app/interface/progress" |
|
|
| export function Panel({ |
| panel, |
| className = "", |
| width = 1, |
| height = 1, |
| }: { |
| panel: number |
| className?: string |
| width?: number |
| height?: number |
| }) { |
| const panelId = `${panel}` |
|
|
| const [mouseOver, setMouseOver] = useState(false) |
| const ref = useRef<HTMLImageElement>(null) |
| const font = useStore(state => state.font) |
| const preset = useStore(state => state.preset) |
|
|
| const setGeneratingImages = useStore(state => state.setGeneratingImages) |
|
|
| const panels = useStore(state => state.panels) |
| const prompt = panels[panel] || "" |
|
|
| const captions = useStore(state => state.captions) |
| const caption = captions[panel] || "" |
|
|
| const zoomLevel = useStore(state => state.zoomLevel) |
| const showCaptions = useStore(state => state.showCaptions) |
|
|
| const addToUpscaleQueue = useStore(state => state.addToUpscaleQueue) |
|
|
| const [_isPending, startTransition] = useTransition() |
| const renderedScenes = useStore(state => state.renderedScenes) |
| const setRendered = useStore(state => state.setRendered) |
|
|
| const rendered = renderedScenes[panel] || getInitialRenderedScene() |
|
|
| const [revision, setRevision] = useState(0) |
|
|
| |
| const renderedRef = useRef<RenderedScene>() |
| const renderedKey = JSON.stringify(rendered) |
| useEffect(() => { renderedRef.current = rendered }, [renderedKey]) |
|
|
| const timeoutRef = useRef<any>(null) |
|
|
| const enableRateLimiter = `${process.env.NEXT_PUBLIC_ENABLE_RATE_LIMITER}` === "true" |
|
|
| const delay = enableRateLimiter ? (1000 + (500 * panel)) : 1000 |
|
|
|
|
| const startImageGeneration = ({ prompt, width, height, revision }: { |
| prompt: string |
| width: number |
| height: number |
| revision: number |
| }) => { |
| if (!prompt?.length) { return } |
|
|
| |
| setGeneratingImages(panelId, true) |
|
|
| |
| setRendered(panelId, getInitialRenderedScene()) |
|
|
| setTimeout(() => { |
| startTransition(async () => { |
|
|
| const withCache = revision === 0 |
|
|
| |
| |
| let cacheInvalidationHack = "" |
| const nbMaxRevisions = 6 |
| for (let i = 0; i < revision && revision < nbMaxRevisions; i++) { |
| const j = Math.random() |
| cacheInvalidationHack += j < 0.3 ? "_" : j < 0.6 ? "," : "-" |
| } |
|
|
| let newRendered: RenderedScene |
| try { |
| |
| newRendered = await newRender({ |
| prompt: cacheInvalidationHack + " " + prompt, |
| width, |
| height, |
|
|
| |
| |
| withCache: revision === 0 |
| }) |
| } catch (err) { |
| |
| newRendered = await newRender({ |
| prompt: cacheInvalidationHack + " " + prompt, |
| width, |
| height, |
| withCache, |
| }) |
| } |
|
|
| if (newRendered) { |
| setRendered(panelId, newRendered) |
|
|
| if (newRendered.status === "completed") { |
| setGeneratingImages(panelId, false) |
| addToUpscaleQueue(panelId, newRendered) |
| } |
|
|
| |
| } else { |
| setRendered(panelId, { |
| renderId: "", |
| status: "pending", |
| assetUrl: "", |
| alt: "", |
| maskUrl: "", |
| error: "", |
| segments: [] |
| }) |
| setGeneratingImages(panelId, false) |
| return |
| } |
| }) |
| }, enableRateLimiter ? 1000 * panel : 0) |
| } |
|
|
|
|
| const checkStatus = () => { |
| startTransition(async () => { |
| clearTimeout(timeoutRef.current) |
|
|
| if (!renderedRef.current?.renderId || renderedRef.current?.status !== "pending") { |
| timeoutRef.current = setTimeout(checkStatus, delay) |
| return |
| } |
|
|
| try { |
| setGeneratingImages(panelId, true) |
| const newRendered = await getRender(renderedRef.current.renderId) |
|
|
| if (JSON.stringify(renderedRef.current) !== JSON.stringify(newRendered)) { |
| setRendered(panelId, renderedRef.current = newRendered) |
| setGeneratingImages(panelId, true) |
| } |
|
|
| if (newRendered.status === "pending") { |
| timeoutRef.current = setTimeout(checkStatus, delay) |
| } else if (newRendered.status === "error" || |
| (newRendered.status === "completed" && !newRendered.assetUrl?.length)) { |
| try { |
| const newAttempt = await newRender({ |
| prompt, |
| width, |
| height, |
| withCache: false, |
| }) |
| setRendered(panelId, newAttempt) |
| } catch (err) { |
| console.error("yeah sorry, something is wrong.. aborting", err) |
| setGeneratingImages(panelId, false) |
| } |
| } else { |
| console.log("panel finished!") |
| setGeneratingImages(panelId, false) |
| addToUpscaleQueue(panelId, newRendered) |
| } |
| } catch (err) { |
| console.error(err) |
| timeoutRef.current = setTimeout(checkStatus, delay) |
| } |
| }) |
| } |
|
|
| useEffect(() => { |
| if (!prompt.length) { return } |
|
|
| startImageGeneration({ prompt, width, height, revision }) |
|
|
| clearTimeout(timeoutRef.current) |
| |
| |
| timeoutRef.current = setTimeout(checkStatus, delay) |
|
|
| return () => { |
| clearTimeout(timeoutRef.current) |
| } |
| }, [prompt, width, height, revision]) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const frameClassName = cn( |
| |
| `w-full h-full`, |
| `border-stone-800`, |
| `transition-all duration-200 ease-in-out`, |
| zoomLevel > 140 ? `border-[2px] md:border-[4px] rounded-sm md:rounded-md` : |
| zoomLevel > 120 ? `border-[1.5px] md:border-[3px] rounded-xs md:rounded-sm` : |
| zoomLevel > 90 ? `border-[1px] md:border-[2px] rounded-xs md:rounded-sm` : |
| zoomLevel > 40 ? `border-[0.5px] md:border-[1px] rounded-none md:rounded-xs` : |
| `border-transparent md:border-[0.5px] rounded-none md:rounded-none`, |
| `shadow-sm`, |
| `overflow-hidden`, |
| `print:border-[1.5px] print:shadow-none`, |
| ) |
|
|
| const handleReload = () => { |
| console.log(`Asked to reload panel ${panelId}`) |
| setRevision(revision + 1) |
| } |
|
|
| if (prompt && !rendered.assetUrl) { |
| return ( |
| <div className={cn( |
| frameClassName, |
| `flex flex-col items-center justify-center`, |
| className, |
| )}> |
| <Progress isLoading /> |
| </div> |
| ) |
| } |
|
|
| return ( |
| <div className={cn( |
| frameClassName, |
| { "grayscale": preset.color === "grayscale" }, |
| className |
| )} |
| onMouseEnter={() => setMouseOver(true)} |
| onMouseLeave={() => setMouseOver(false)} |
| > |
| <div className={cn( |
| `bg-stone-50`, |
| `border-stone-800`, |
| `transition-all duration-200 ease-in-out`, |
| zoomLevel > 140 ? `border-b-[2px] md:border-b-[4px]` : |
| zoomLevel > 120 ? `border-b-[1.5px] md:border-b-[3px]` : |
| zoomLevel > 90 ? `border-b-[1px] md:border-b-[2px]` : |
| zoomLevel > 40 ? `border-b-[0.5px] md:border-b-[1px]` : |
| `border-transparent md:border-b-[0.5px]`, |
| `print:border-b-[1.5px]`, |
| `truncate`, |
| |
| zoomLevel > 200 ? `p-4 md:p-8` : |
| zoomLevel > 180 ? `p-[14px] md:p-8` : |
| zoomLevel > 160 ? `p-[12px] md:p-[28px]` : |
| zoomLevel > 140 ? `p-[10px] md:p-[26px]` : |
| zoomLevel > 120 ? `p-2 md:p-6` : |
| zoomLevel > 100 ? `p-1.5 md:p-[20px]` : |
| zoomLevel > 90 ? `p-1.5 md:p-4` : |
| zoomLevel > 40 ? `p-1 md:p-2` : |
| `p-0.5 md:p-2`, |
| |
| zoomLevel > 220 ? `text-xl md:text-4xl` : |
| zoomLevel > 200 ? `text-lg md:text-3xl` : |
| zoomLevel > 180 ? `text-md md:text-2xl` : |
| zoomLevel > 140 ? `text-2xs md:text-2xl` : |
| zoomLevel > 120 ? `text-3xs md:text-xl` : |
| zoomLevel > 100 ? `text-4xs md:text-lg` : |
| zoomLevel > 90 ? `text-5xs md:text-sm` : |
| zoomLevel > 40 ? `md:text-xs` : `md:text-2xs`, |
| |
| showCaptions ? ( |
| zoomLevel > 90 ? `block` : `hidden md:block` |
| ) : `hidden`, |
| )} |
| >{caption || ""} |
| </div> |
| {rendered.assetUrl && |
| <img |
| ref={ref} |
| src={rendered.assetUrl} |
| width={width} |
| height={height} |
| alt={rendered.alt} |
| className={cn( |
| `comic-panel w-full h-full object-cover max-w-max`, |
| // showCaptions ? `-mt-11` : '' |
| )} |
| />} |
| { |
| // there is an issue, this env check doesn't work.. |
| // process.env.NEXT_PUBLIC_CAN_REDRAW === "true" ? |
| <div |
| className={cn(`relative -mt-14 ml-4`,)}> |
| <div className="flex flex-row"> |
| <div |
| onClick={rendered.status === "completed" ? handleReload : undefined} |
| className={cn( |
| `bg-stone-100 rounded-lg`, |
| `flex flex-row space-x-2 items-center`, |
| `py-2 px-3 cursor-pointer`, |
| `transition-all duration-200 ease-in-out`, |
| rendered.status === "completed" ? "opacity-95" : "opacity-50", |
| mouseOver && rendered.assetUrl ? `scale-95 hover:scale-100 hover:opacity-100`: `scale-0` |
| )}> |
| <RxReload |
| className="w-5 h-5" |
| /> |
| <span className="text-base">Redraw</span> |
| </div> |
| </div> |
| </div> |
| //: null |
| } |
| </div> |
| ) |
| } |