import React, { useState, useCallback, useRef, useEffect } from 'react' import { Stack, Text, Card, Button, Box, Flex } from '@sanity/ui' import { set, useFormValue, useClient } from 'sanity' import { domToBlob } from 'modern-screenshot' import { getHumanRelativeDate } from '../utils/date-formatting' // Detect iOS devices const isIOS = () => { if (typeof window === 'undefined') return false return /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) } interface ImageGeneratorProps { onChange: (event: any) => void renderDefault: (props: any) => React.ReactElement } export function ImageGenerator (props: ImageGeneratorProps) { const { onChange, renderDefault } = props const [isGenerating, setIsGenerating] = useState(false) const [hasPreview, setHasPreview] = useState(false) const [visualLineCount, setVisualLineCount] = useState(1) const [isCapturingForIOS, setIsCapturingForIOS] = useState(false) const terminalRef = useRef(null) const contentRef = useRef(null) const dateOfDocument = useFormValue(['_createdAt']) as string const relativeDate = getHumanRelativeDate(dateOfDocument) const documentContent = useFormValue(['content']) as string const client = useClient({ apiVersion: '2025-02-10' }) // Format text for display - now just clean whitespace const formatText = useCallback((text: string) => { return text.trim() }, []) const formattedText = documentContent ? formatText(documentContent) : '' useEffect(() => { if (documentContent?.trim()) { setHasPreview(true) } else { setHasPreview(false) } }, [documentContent]) // Calculate visual line count after text wrapping useEffect(() => { if (!formattedText) { setVisualLineCount(1) return } // Use setTimeout to ensure DOM is updated const timer = setTimeout(() => { if (contentRef.current) { // Account for the content area's padding-right: 40px const contentWidth = contentRef.current.clientWidth - 40 || 960 const tempDiv = document.createElement('div') tempDiv.style.cssText = ` position: absolute; visibility: hidden; width: ${contentWidth}px; font-family: JetBrains Mono, Monaco, monospace; font-size: 24px; line-height: 32px; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; padding: 0; margin: 0; ` tempDiv.textContent = formattedText document.body.appendChild(tempDiv) const lineHeight = 32 const height = tempDiv.offsetHeight const lines = Math.ceil(height / lineHeight) setVisualLineCount(Math.max(1, lines)) document.body.removeChild(tempDiv) } }, 0) return () => clearTimeout(timer) }, [formattedText]) const generateImage = useCallback(async () => { if (!documentContent || !hasPreview || !terminalRef.current) return setIsGenerating(true) try { const iosDevice = isIOS() // Set iOS capture state to conditionally render iOS-compatible styles if (iosDevice) { setIsCapturingForIOS(true) // Give React time to re-render with iOS-compatible styles await new Promise(resolve => setTimeout(resolve, 100)) } const blob = await domToBlob(terminalRef.current, { width: terminalRef.current.clientWidth, height: terminalRef.current.clientHeight, scale: 2, style: { transform: 'scale(1)', transformOrigin: 'top left', }, ...(iosDevice && { // iOS-specific options to improve compatibility includeQueryParams: true, }), }) // Reset iOS capture state if (iosDevice) { setIsCapturingForIOS(false) } if (blob) { try { // Upload the image to Sanity as an asset const asset = await client.assets.upload('image', blob, { filename: `ama-generated-${Date.now()}.png`, }) onChange(set({ _type: 'image', asset: { _type: 'reference', _ref: asset._id, }, })) } catch (error) { console.error('Error uploading image to Sanity:', error) } } setIsGenerating(false) } catch (error) { console.error('Error generating image:', error) setIsGenerating(false) } }, [documentContent, hasPreview, onChange, client]) return ( Generated Image