-
-
- Camera Config
+
+
+
+
+ Cinema Studio
+
+
+ Camera settings
-
+
+ Build a consistent cinematic look by choosing the camera, lens,
+ focal length, and depth of field.
+
- {/* Scroll columns */}
-
@@ -470,6 +509,8 @@ export default function CinemaStudio({
const [imageUploadProgress, setImageUploadProgress] = useState(0);
const imageInputRef = useRef(null);
const [activeHistoryIndex, setactiveHistoryIndex] = useState(null);
+ const [copiedPromptIndex, setCopiedPromptIndex] = useState(null);
+ const [copiedImageIndex, setCopiedImageIndex] = useState(null);
// ── Internal history state (used when historyItems prop is not provided) ──
const [internalHistory, setInternalHistory] = useState([]);
@@ -525,17 +566,6 @@ export default function CinemaStudio({
}, []);
// ── Adjust height on load ────────────────────────────────────────────────
- useEffect(() => {
- const timer = setTimeout(() => {
- if (textareaRef.current) {
- const el = textareaRef.current;
- el.style.height = "auto";
- el.style.height = el.scrollHeight + "px";
- }
- }, 150);
- return () => clearTimeout(timer);
- }, []);
-
// ── Persistence: Save ────────────────────────────────────────────────────
useEffect(() => {
const timer = setTimeout(() => {
@@ -566,13 +596,6 @@ export default function CinemaStudio({
`${settings.lens}, ${settings.focal}mm, ${settings.aperture}`;
// ── Textarea auto-height ──
- const handleTextareaInput = (e) => {
- const el = e.target;
- el.style.height = "auto";
- el.style.height = el.scrollHeight + "px";
- setSettings((prev) => ({ ...prev, prompt: el.value }));
- };
-
// ── Generate ──
const handleGenerate = useCallback(async () => {
const basePrompt = settings.prompt.trim();
@@ -672,6 +695,75 @@ export default function CinemaStudio({
}
}, [canvasUrl]);
+ const handleCopyPrompt = useCallback(
+ async (prompt, index) => {
+ if (!prompt) return;
+
+ try {
+ await navigator.clipboard.writeText(prompt);
+ setCopiedPromptIndex(index);
+ window.setTimeout(() => {
+ setCopiedPromptIndex((current) => (current === index ? null : current));
+ }, 1600);
+ } catch (error) {
+ console.error("Failed to copy the prompt:", error);
+ onGenerationError?.("Could not copy the prompt to the clipboard.");
+ }
+ },
+ [onGenerationError],
+ );
+
+ const handleCopyImage = useCallback(
+ async (imageUrl, index) => {
+ try {
+ if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
+ throw new Error("Image clipboard access is not supported.");
+ }
+
+ const response = await fetch(imageUrl);
+ if (!response.ok) {
+ throw new Error(`Image request failed with status ${response.status}.`);
+ }
+
+ const sourceBlob = await response.blob();
+ let clipboardBlob = sourceBlob;
+
+ if (sourceBlob.type !== "image/png") {
+ const bitmap = await createImageBitmap(sourceBlob);
+ const canvas = document.createElement("canvas");
+ canvas.width = bitmap.width;
+ canvas.height = bitmap.height;
+ const context = canvas.getContext("2d");
+ if (!context) throw new Error("Could not create an image canvas.");
+ context.drawImage(bitmap, 0, 0);
+ bitmap.close?.();
+
+ clipboardBlob = await new Promise((resolve, reject) => {
+ canvas.toBlob(
+ (blob) =>
+ blob
+ ? resolve(blob)
+ : reject(new Error("Could not convert the image to PNG.")),
+ "image/png",
+ );
+ });
+ }
+
+ await navigator.clipboard.write([
+ new ClipboardItem({ "image/png": clipboardBlob }),
+ ]);
+ setCopiedImageIndex(index);
+ window.setTimeout(() => {
+ setCopiedImageIndex((current) => (current === index ? null : current));
+ }, 1600);
+ } catch (error) {
+ console.error("Failed to copy the image:", error);
+ onGenerationError?.("Could not copy the image to the clipboard.");
+ }
+ },
+ [onGenerationError],
+ );
+
// ── Load history item ──
const loadHistoryItem = (entry, idx) => {
if (entry.settings) {
@@ -686,13 +778,6 @@ export default function CinemaStudio({
}));
if (entry.settings.resolution) setResolution(entry.settings.resolution);
- // Sync textarea height
- if (textareaRef.current) {
- textareaRef.current.value = entry.settings.prompt || "";
- textareaRef.current.style.height = "auto";
- textareaRef.current.style.height =
- textareaRef.current.scrollHeight + "px";
- }
}
setCanvasUrl(entry.url);
};
@@ -701,8 +786,6 @@ export default function CinemaStudio({
setCanvasUrl(null);
setSettings((prev) => ({ ...prev, prompt: "" }));
if (textareaRef.current) {
- textareaRef.current.value = "";
- textareaRef.current.style.height = "auto";
setTimeout(() => textareaRef.current?.focus(), 50);
}
};
@@ -771,6 +854,49 @@ export default function CinemaStudio({
+