116 lines
4.6 KiB
TypeScript
116 lines
4.6 KiB
TypeScript
import type { CustomWorldDraftCardSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
|
||
|
||
type CustomWorldAgentDraftDrawerProps = {
|
||
draftCards: CustomWorldDraftCardSummary[];
|
||
activeCardId?: string | null;
|
||
onSelectCard: (cardId: string) => void;
|
||
};
|
||
|
||
const DRAWER_KIND_ORDER: CustomWorldDraftCardSummary['kind'][] = [
|
||
'world',
|
||
'chapter',
|
||
'thread',
|
||
'faction',
|
||
'character',
|
||
'landmark',
|
||
'camp',
|
||
];
|
||
|
||
function resolveGroupLabel(kind: CustomWorldDraftCardSummary['kind']) {
|
||
if (kind === 'world') return '世界总卡';
|
||
if (kind === 'chapter') return '第一幕';
|
||
if (kind === 'thread') return '世界线程';
|
||
if (kind === 'faction') return '势力';
|
||
if (kind === 'character') return '关键角色';
|
||
if (kind === 'landmark') return '关键地点';
|
||
if (kind === 'camp') return '营地';
|
||
return '草稿卡';
|
||
}
|
||
|
||
export function CustomWorldAgentDraftDrawer({
|
||
draftCards,
|
||
activeCardId,
|
||
onSelectCard,
|
||
}: CustomWorldAgentDraftDrawerProps) {
|
||
const groupedCards = DRAWER_KIND_ORDER.map((kind) => ({
|
||
kind,
|
||
items: draftCards.filter((card) => card.kind === kind),
|
||
})).filter((group) => group.items.length > 0);
|
||
|
||
return (
|
||
<div className="rounded-[1.5rem] border border-white/10 bg-black/18 px-4 py-4">
|
||
<div className="text-[11px] font-bold tracking-[0.2em] text-zinc-400">
|
||
草稿抽屉
|
||
</div>
|
||
{groupedCards.length > 0 ? (
|
||
<div className="mt-3 space-y-4">
|
||
{groupedCards.map((group) => (
|
||
<section key={group.kind}>
|
||
<div className="flex items-center justify-between gap-3">
|
||
<div className="text-[11px] tracking-[0.18em] text-zinc-400">
|
||
{resolveGroupLabel(group.kind)}
|
||
</div>
|
||
<div className="text-[11px] text-zinc-500">
|
||
{group.items.length}
|
||
</div>
|
||
</div>
|
||
<div className="mt-2 space-y-2">
|
||
{group.items.map((card, index) => {
|
||
const isActive = activeCardId === card.id;
|
||
|
||
return (
|
||
<button
|
||
key={card.id || `${group.kind}-card-${index}`}
|
||
type="button"
|
||
onClick={() => onSelectCard(card.id)}
|
||
className={`w-full rounded-[1.2rem] border px-3 py-3 text-left transition ${
|
||
isActive
|
||
? 'border-sky-300/30 bg-sky-500/10'
|
||
: 'border-white/8 bg-white/5 hover:border-white/14'
|
||
}`}
|
||
>
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="text-sm font-semibold text-white">
|
||
{card.title}
|
||
</div>
|
||
{card.warningCount > 0 ? (
|
||
<span className="rounded-full border border-amber-300/20 bg-amber-500/10 px-2 py-0.5 text-[10px] text-amber-100">
|
||
{card.warningCount}
|
||
</span>
|
||
) : null}
|
||
</div>
|
||
<div className="mt-1 text-[11px] text-zinc-400">
|
||
{card.subtitle}
|
||
</div>
|
||
<div className="mt-2 text-sm leading-6 text-zinc-300">
|
||
{card.summary}
|
||
</div>
|
||
<div className="mt-3 flex flex-wrap gap-2">
|
||
<span className="rounded-full border border-white/10 bg-black/24 px-2.5 py-1 text-[10px] text-zinc-200">
|
||
关联 {card.linkedIds.length}
|
||
</span>
|
||
<span className="rounded-full border border-white/10 bg-black/24 px-2.5 py-1 text-[10px] text-zinc-200">
|
||
{card.status === 'warning' ? '待精修' : '建议稿'}
|
||
</span>
|
||
{card.kind === 'character' && card.assetStatusLabel ? (
|
||
<span className="rounded-full border border-amber-300/20 bg-amber-500/10 px-2.5 py-1 text-[10px] text-amber-100">
|
||
{card.assetStatusLabel}
|
||
</span>
|
||
) : null}
|
||
</div>
|
||
</button>
|
||
);
|
||
})}
|
||
</div>
|
||
</section>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="mt-3 text-sm leading-7 text-zinc-400">
|
||
最小锚点齐备后,世界底稿会先从这里长出来。
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|