diff --git a/mnemo_cards_admin/src/components/CardEditorPreview.tsx b/mnemo_cards_admin/src/components/CardEditorPreview.tsx new file mode 100644 index 0000000..2cb1497 --- /dev/null +++ b/mnemo_cards_admin/src/components/CardEditorPreview.tsx @@ -0,0 +1,407 @@ +import { Input } from '@/components/ui/input' +import { Textarea } from '@/components/ui/textarea' +import { ImageUpload } from '@/components/ui/image-upload' +import { CardVoicesManager } from '@/components/CardVoicesManager' + +interface CardEditorPreviewProps { + formData: { + packId: string + original: string + translation: string + mnemo: string + transcription: string + transcriptionMnemo: string + back: string + image: string | undefined + imageBack: string | undefined + } + onFormDataChange: (updates: Partial) => void + cardId?: string + disabled?: boolean + packColor?: string +} + +// Helper function to format text with mnemo syntax (similar to Flutter's MnemoText) +function formatMnemoText(text: string | null | undefined): React.ReactNode { + if (!text) return null + + // Replace \n with actual line breaks + const formattedText = text.replaceAll('\\n', '\n') + + // Find all matches of text in curly braces + const matches = Array.from(formattedText.matchAll(/\{(.*?)\}/g)) + + if (matches.length === 0) { + return {formattedText} + } + + const elements: React.ReactNode[] = [] + let lastIndex = 0 + + matches.forEach((match) => { + // Add text before the match + if (match.index !== undefined && match.index > lastIndex) { + elements.push( + + {formattedText.substring(lastIndex, match.index)} + + ) + } + + // Extract text inside braces + let mnemoText = match[1] + + // Check if text starts with color code #RRGGBB + let color = '#ef4444' // Default red color (mnemoRed) + if (mnemoText.startsWith('#') && mnemoText.length > 7) { + const colorCode = mnemoText.substring(0, 7) + color = colorCode + mnemoText = mnemoText.substring(7) + } + + // Add highlighted text + elements.push( + + {mnemoText} + + ) + + lastIndex = match.index! + match[0].length + }) + + // Add remaining text + if (lastIndex < formattedText.length) { + elements.push( + + {formattedText.substring(lastIndex)} + + ) + } + + return <>{elements} +} + +// Helper to get image source +function getImageSrc(image?: string): string | undefined { + if (!image) return undefined + + if (image.startsWith('data:') || image.startsWith('http://') || image.startsWith('https://')) { + return image + } + + return `data:image/png;base64,${image}` +} + +export function CardEditorPreview({ + formData, + onFormDataChange, + cardId, + disabled = false, + packColor = '#6b7280', // Default gray border color +}: CardEditorPreviewProps) { + + return ( +
+ {/* Cards Preview - Front and Back side by side */} +
+
+ {/* Front side */} +
+
Front Side
+
+
+ {/* Top section: Original, Translation */} +
+ {/* Original - large, bold */} +
+ + onFormDataChange({ original: e.target.value }) + } + placeholder="Original text" + className="text-center border-none shadow-none focus-visible:ring-2 focus-visible:ring-primary/20 p-0 h-auto bg-transparent" + disabled={disabled} + style={{ + fontSize: '32px', + fontWeight: 700, + }} + /> +
+ + {/* Translation - smaller, gray */} +
+ + onFormDataChange({ translation: e.target.value }) + } + placeholder="Translation" + className="text-center border-none shadow-none focus-visible:ring-2 focus-visible:ring-primary/20 p-0 h-auto bg-transparent" + disabled={disabled} + style={{ + fontSize: '22px', + fontWeight: 400, + color: formData.translation ? 'rgba(0, 0, 0, 0.5)' : 'rgba(0, 0, 0, 0.3)', + }} + /> +
+
+ + {/* Image in the middle */} +
+ {getImageSrc(formData.image) ? ( + Card { + const target = e.target as HTMLImageElement + target.style.display = 'none' + }} + /> + ) : ( +
+ + + +
+ )} +
+ + {/* Mnemo phrase at the bottom */} +
+
+ {formData.mnemo ? ( +
+ onFormDataChange({ mnemo: e.target.value })} + placeholder="Mnemo phrase" + className="text-center border-none shadow-none focus-visible:ring-2 focus-visible:ring-primary/20 p-0 h-auto bg-transparent w-full mb-2" + disabled={disabled} + style={{ + fontSize: '24px', + fontWeight: 600, + }} + /> + {/* Preview of formatted mnemo text */} +
+ {formatMnemoText(formData.mnemo)} +
+
+ ) : ( + onFormDataChange({ mnemo: e.target.value })} + placeholder="Mnemo phrase" + className="text-center border-none shadow-none focus-visible:ring-2 focus-visible:ring-primary/20 p-0 h-auto bg-transparent w-full" + disabled={disabled} + style={{ + fontSize: '24px', + fontWeight: 600, + }} + /> + )} +
+
+
+
+ + {/* Back side */} +
+
+ {formData.imageBack ? ( + <> +
+ Back { + const target = e.target as HTMLImageElement + target.style.display = 'none' + }} + /> +
+
+