180 lines
7.3 KiB
TypeScript
180 lines
7.3 KiB
TypeScript
import React from 'react'
|
|
import type { Flashcard } from '@/lib/services/flashcards'
|
|
import { getFlashcardImageUrl } from '@/lib/utils/flashcardUtils'
|
|
import { BluePlayButton } from '@/components/shared/BluePlayButton'
|
|
|
|
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">
|
|
<BluePlayButton
|
|
text={flashcard.example}
|
|
lang="en-US"
|
|
size="md"
|
|
title="點擊聽例句發音"
|
|
/>
|
|
</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>
|
|
)
|
|
} |