초기 커밋: DefVideo 소스 등록

abcVideo 플레이어 소스 (client / server / shared / pythonsource / docs / .claude).
.gitignore 적용으로 node_modules·storage·samplevideo·미디어 등 대용량 일괄 제외.
103 files, ~964K.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 03:20:27 +00:00
commit 82662d417d
103 changed files with 17213 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
import React, { useState } from 'react';
import type { Annotation } from '@abcvideo/shared';
import { secondsToTimecode } from '../../utils/timecode';
interface Props {
annotations: Annotation[];
currentTime: number;
onSeek: (time: number) => void;
onDelete: (id: string) => void;
onExport: (format: string) => void;
}
export default function AnnotationPanel({
annotations,
currentTime,
onSeek,
onDelete,
onExport,
}: Props) {
const [tab, setTab] = useState<'subtitle' | 'memo'>('subtitle');
const filtered = annotations.filter((a) => a.type === tab);
return (
<div className="flex flex-col h-full">
{/* Tabs */}
<div className="flex border-b border-gray-700">
{(['subtitle', 'memo'] as const).map((t) => (
<button
key={t}
onClick={() => setTab(t)}
className={`flex-1 py-2 text-sm ${
tab === t ? 'text-white border-b-2 border-blue-500' : 'text-gray-400'
}`}
>
{t === 'subtitle' ? '자막' : '메모'}
</button>
))}
</div>
{/* List */}
<div className="flex-1 overflow-y-auto divide-y divide-gray-800">
{filtered.length === 0 && (
<p className="text-gray-500 text-sm text-center py-6">
{tab === 'subtitle' ? '자막이 없습니다' : '메모가 없습니다'}
</p>
)}
{filtered.map((a) => {
const active = currentTime >= a.timeStart && currentTime <= a.timeEnd;
return (
<div
key={a.id}
className={`px-3 py-2 cursor-pointer hover:bg-gray-800 ${
active ? 'bg-gray-800 border-l-2 border-yellow-400' : ''
}`}
onClick={() => onSeek(a.timeStart)}
>
<div className="text-xs text-gray-400 font-mono">
{secondsToTimecode(a.timeStart)} {secondsToTimecode(a.timeEnd)}
</div>
<div className="text-sm text-white mt-0.5 truncate">{a.text}</div>
<button
onClick={(e) => { e.stopPropagation(); onDelete(a.id); }}
className="text-xs text-red-400 hover:text-red-300 mt-1"
>
</button>
</div>
);
})}
</div>
{/* Export buttons */}
<div className="p-2 border-t border-gray-700 flex gap-1 flex-wrap">
{['vtt', 'srt', 'json', 'csv'].map((f) => (
<button
key={f}
onClick={() => onExport(f)}
className="text-xs bg-gray-700 hover:bg-gray-600 text-white px-2 py-1 rounded"
>
{f.toUpperCase()}
</button>
))}
</div>
</div>
);
}