"use client"; import { useEffect, useRef, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; import { DownloadIcon } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { ExportLanguageDialog } from "@/components/public/export-language-dialog"; import { ExportLanguageToggle, useExportLanguage, type ExportLanguage, } from "@/components/public/export-language"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; const EXPORT_WIDTH = 1200; function withoutNotesFileName(fileName: string): string { return /\.[^.]+$/u.test(fileName) ? fileName.replace(/(\.[^.]+)$/u, "-without-notes$1") : `${fileName}-without-notes`; } function downloadFileName(value: string): string { return value.replace(/[\\/:*?"<>|]/gu, "-"); } export function imageExportFileName( fileName: string, language: ExportLanguage, withoutNotes: boolean, localized = true, ): string { const selectedFileName = withoutNotes ? withoutNotesFileName(fileName) : fileName; const languageFileName = localized ? (/\.[^.]+$/u.test(selectedFileName) ? selectedFileName.replace(/(\.[^.]+)$/u, `-${language}$1`) : `${selectedFileName}-${language}`) : selectedFileName; return downloadFileName(languageFileName); } export function GuideImageExport({ children, fileName, width = EXPORT_WIDTH, height, format = "png", disabled = false, allowWithoutNotes = true, captureClassName, buttonLabel = "ส่งออกภาพ", language: controlledLanguage, showLanguageSelector = true, promptForLanguage = false, localizeFileName = true, }: { children: ReactNode; fileName: string; width?: number; height?: number; format?: "png" | "webp"; disabled?: boolean; allowWithoutNotes?: boolean; captureClassName?: string; buttonLabel?: string; language?: ExportLanguage; showLanguageSelector?: boolean; promptForLanguage?: boolean; localizeFileName?: boolean; }) { const captureRef = useRef(null); const [captureMode, setCaptureMode] = useState< "with-notes" | "without-notes" | null >(null); const capturing = captureMode !== null; const [storedLanguage, setStoredLanguage] = useExportLanguage(); const language = controlledLanguage ?? storedLanguage; const [pendingCaptureMode, setPendingCaptureMode] = useState< "with-notes" | "without-notes" | null >(null); function selectLanguage(nextLanguage: ExportLanguage) { if (!pendingCaptureMode) return; setStoredLanguage(nextLanguage); setCaptureMode(pendingCaptureMode); setPendingCaptureMode(null); } useEffect(() => { if (!capturing) return; let cancelled = false; async function capture() { try { const node = captureRef.current; if (!node) throw new Error("Capture target is unavailable"); if (captureMode === "without-notes") { node.querySelectorAll("[data-export-note]").forEach((note) => { note.style.display = "none"; }); // A note can be the only remaining content in the artifact right column. const right = node.querySelector(".artifact-export-right"); if (right) { const sections = right.querySelectorAll( "[data-artifact-export-section]", ); const hasVisibleSection = Array.from(sections).some( (section) => getComputedStyle(section).display !== "none", ); if (!hasVisibleSection) right.style.display = "none"; } } const { domToBlob, waitUntilLoad } = await import("modern-screenshot"); await Promise.all([document.fonts.ready, waitUntilLoad(node)]); const blob = await domToBlob(node, { backgroundColor: getComputedStyle(node).backgroundColor, type: `image/${format}`, scale: 1, width, height, }); if (cancelled) return; const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.download = imageExportFileName( fileName, language, captureMode === "without-notes", localizeFileName, ); link.href = url; document.body.append(link); link.click(); link.remove(); window.setTimeout(() => URL.revokeObjectURL(url), 0); } catch { if (!cancelled) toast.error("ส่งออกภาพไม่สำเร็จ กรุณาลองอีกครั้ง"); } finally { if (!cancelled) setCaptureMode(null); } } void capture(); return () => { cancelled = true; }; }, [capturing, captureMode, fileName, format, height, language, localizeFileName, width]); return ( <> {showLanguageSelector ? ( ) : null}
{(["with-notes", "without-notes"] as const).filter((mode) => allowWithoutNotes || mode === "with-notes").map((mode) => ( ))}
{ if (!open) setPendingCaptureMode(null); }} onSelect={selectLanguage} /> {capturing ? createPortal( , document.body, ) : null} ); }