dramaling-vocab-learning/frontend/components/flashcards/FlashcardContentBlocks.tsx

207 lines
9.0 KiB
TypeScript

import React from 'react'
import type { Flashcard } from '@/lib/services/flashcards'
import { getFlashcardImageUrl } from '@/lib/utils/flashcardUtils'
interface FlashcardContentBlocksProps {
flashcard: Flashcard
isEditing: boolean
editedCard: any
onEditChange: (field: string, value: string) => void
isPlayingWord: boolean
isPlayingExample: boolean
onToggleExampleTTS: (text: string, lang?: string) => void
isGeneratingImage: boolean
generationProgress: string
onGenerateImage: () => void
}
export const FlashcardContentBlocks: React.FC<FlashcardContentBlocksProps> = ({
flashcard,
isEditing,
editedCard,
onEditChange,
isPlayingWord,
isPlayingExample,
onToggleExampleTTS,
isGeneratingImage,
generationProgress,
onGenerateImage
}) => {
return (
<div className="p-6 space-y-6">
{/* 翻譯區塊 */}
<div className="bg-green-50 rounded-lg p-4 border border-green-200">
<h3 className="font-semibold text-green-900 mb-3 text-left"></h3>
{isEditing ? (
<input
type="text"
value={editedCard?.translation || ''}
onChange={(e) => onEditChange('translation', e.target.value)}
className="w-full p-3 border border-green-300 rounded-lg focus:ring-2 focus:ring-green-500 bg-white"
placeholder="輸入中文翻譯"
/>
) : (
<p className="text-green-800 font-medium text-left text-lg">
{flashcard.translation}
</p>
)}
</div>
{/* 定義區塊 */}
<div className="bg-gray-50 rounded-lg p-4 border border-gray-200">
<h3 className="font-semibold text-gray-900 mb-3 text-left"></h3>
{isEditing ? (
<textarea
value={editedCard?.definition || ''}
onChange={(e) => onEditChange('definition', e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white h-20 resize-none"
placeholder="輸入英文定義"
/>
) : (
<p className="text-gray-700 text-left leading-relaxed">
{flashcard.definition}
</p>
)}
</div>
{/* 例句區塊 */}
<div className="bg-blue-50 rounded-lg p-4 border border-blue-200">
<h3 className="font-semibold text-blue-900 mb-3 text-left"></h3>
{/* 例句圖片 */}
<div className="mb-4 relative">
{getFlashcardImageUrl(flashcard) ? (
<div className="w-full max-w-sm mx-auto">
<img
src={getFlashcardImageUrl(flashcard)!}
alt={`${flashcard.word} example`}
className="w-full aspect-square object-cover rounded-lg border border-blue-300"
style={{ imageRendering: 'auto' }}
/>
</div>
) : (
<div className="w-full max-w-md mx-auto h-48 bg-gray-100 rounded-lg border-2 border-dashed border-gray-300 flex items-center justify-center">
<div className="text-center text-gray-500">
<svg className="w-12 h-12 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p className="text-sm"></p>
<button
onClick={onGenerateImage}
disabled={isGeneratingImage}
className="mt-2 px-3 py-1 text-sm bg-blue-100 text-blue-700 rounded-full hover:bg-blue-200 transition-colors disabled:opacity-50"
>
{isGeneratingImage ? generationProgress : '生成圖片'}
</button>
</div>
</div>
)}
{/* 圖片上的生成按鈕 */}
{getFlashcardImageUrl(flashcard) && !isGeneratingImage && (
<button
onClick={onGenerateImage}
className="absolute top-2 right-2 px-2 py-1 text-xs bg-white bg-opacity-90 text-gray-700 rounded-md hover:bg-opacity-100 transition-all shadow-sm"
>
</button>
)}
{/* 生成進度覆蓋 */}
{isGeneratingImage && getFlashcardImageUrl(flashcard) && (
<div className="absolute inset-0 bg-white bg-opacity-90 rounded-lg flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
<p className="text-sm text-gray-600">{generationProgress}</p>
</div>
</div>
)}
</div>
{/* 例句內容 */}
<div className="space-y-3">
{isEditing ? (
<>
<textarea
value={editedCard?.example || ''}
onChange={(e) => onEditChange('example', e.target.value)}
className="w-full p-3 border border-blue-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white h-20 resize-none"
placeholder="輸入例句"
/>
<textarea
value={editedCard?.exampleTranslation || ''}
onChange={(e) => onEditChange('exampleTranslation', e.target.value)}
className="w-full p-3 border border-blue-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white h-16 resize-none"
placeholder="輸入例句翻譯"
/>
</>
) : (
<>
<div className="relative">
<p className="text-blue-800 text-left italic text-lg pr-12">
"{flashcard.example}"
</p>
<div className="absolute bottom-0 right-0">
<button
onClick={() => onToggleExampleTTS(flashcard.example, 'en-US')}
disabled={isPlayingWord}
title={isPlayingExample ? "點擊停止播放" : "點擊聽例句發音"}
className={`group relative w-10 h-10 rounded-full shadow-lg transform transition-all duration-200
${isPlayingExample
? 'bg-gradient-to-br from-green-500 to-green-600 shadow-green-200 scale-105'
: 'bg-gradient-to-br from-blue-500 to-blue-600 hover:shadow-xl hover:scale-105 shadow-blue-200'
} ${isPlayingWord ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}
`}
>
{/* 播放中波紋效果 */}
{isPlayingExample && (
<div className="absolute inset-0 bg-blue-400 rounded-full animate-ping opacity-40"></div>
)}
{/* 按鈕圖標 */}
<div className="relative z-10 flex items-center justify-center w-full h-full">
{isPlayingExample ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
</svg>
) : (
<svg className="w-5 h-5 text-white group-hover:scale-110 transition-transform" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
)}
</div>
{/* 懸停提示光環 */}
{!isPlayingExample && !isPlayingWord && (
<div className="absolute inset-0 rounded-full bg-white opacity-0 group-hover:opacity-20 transition-opacity duration-200"></div>
)}
</button>
</div>
</div>
<p className="text-blue-700 text-left text-base">
"{flashcard.exampleTranslation}"
</p>
</>
)}
</div>
</div>
{/* 同義詞區塊 */}
{(flashcard as any).synonyms && (flashcard as any).synonyms.length > 0 && (
<div className="bg-purple-50 rounded-lg p-4 border border-purple-200">
<h3 className="font-semibold text-purple-900 mb-3 text-left"></h3>
<div className="flex flex-wrap gap-2">
{(flashcard as any).synonyms.map((synonym: string, index: number) => (
<span
key={index}
className="bg-white text-purple-700 px-3 py-1 rounded-full text-sm border border-purple-200 font-medium"
>
{synonym}
</span>
))}
</div>
</div>
)}
</div>
)
}