155 lines
5.4 KiB
TypeScript
155 lines
5.4 KiB
TypeScript
import { useMemo, useState } from 'react';
|
|
|
|
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
|
|
import { CustomWorldCreationStartCard } from './CustomWorldCreationStartCard';
|
|
import { CustomWorldWorkCard } from './CustomWorldWorkCard';
|
|
import {
|
|
type CustomWorldWorkFilter,
|
|
CustomWorldWorkTabs,
|
|
} from './CustomWorldWorkTabs';
|
|
|
|
type CustomWorldCreationHubProps = {
|
|
items: CustomWorldWorkSummary[];
|
|
loading: boolean;
|
|
error: string | null;
|
|
onBack: () => void;
|
|
onRetry: () => void;
|
|
onCreateNew: () => void;
|
|
onResumeDraft: (sessionId: string) => void;
|
|
onEnterPublished: (profileId: string) => void;
|
|
};
|
|
|
|
function EmptyState({ title }: { title: string }) {
|
|
return (
|
|
<div className="flex min-h-[16rem] flex-col items-center justify-center rounded-[1.8rem] border border-white/8 bg-white/5 px-6 py-8 text-center">
|
|
<div className="text-lg font-semibold text-white">{title}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function CustomWorldCreationHub({
|
|
items,
|
|
loading,
|
|
error,
|
|
onBack,
|
|
onRetry,
|
|
onCreateNew,
|
|
onResumeDraft,
|
|
onEnterPublished,
|
|
}: CustomWorldCreationHubProps) {
|
|
const [activeFilter, setActiveFilter] =
|
|
useState<CustomWorldWorkFilter>('all');
|
|
const draftCount = items.filter((item) => item.status === 'draft').length;
|
|
const publishedCount = items.filter(
|
|
(item) => item.status === 'published',
|
|
).length;
|
|
const filteredItems = useMemo(
|
|
() =>
|
|
items.filter((item) =>
|
|
activeFilter === 'all' ? true : item.status === activeFilter,
|
|
),
|
|
[activeFilter, items],
|
|
);
|
|
|
|
return (
|
|
<div
|
|
className="flex h-full min-h-0 flex-col overflow-y-auto overscroll-y-contain pr-1 pb-[max(1rem,env(safe-area-inset-bottom))]"
|
|
style={{ WebkitOverflowScrolling: 'touch' }}
|
|
>
|
|
<div className="sticky top-0 z-20 -mx-3 bg-[linear-gradient(180deg,rgba(10,12,18,0.96),rgba(10,12,18,0.88),rgba(10,12,18,0))] px-3 pb-4 pt-1 sm:static sm:mx-0 sm:bg-none sm:px-0 sm:pb-5 sm:pt-0">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div>
|
|
<button
|
|
type="button"
|
|
onClick={onBack}
|
|
className="rounded-full border border-white/10 bg-black/18 px-3 py-1.5 text-[11px] text-zinc-300 transition-colors hover:text-white"
|
|
>
|
|
返回
|
|
</button>
|
|
<div className="mt-4 text-[1.8rem] font-black leading-tight text-white sm:text-[2.3rem]">
|
|
创作中心
|
|
</div>
|
|
</div>
|
|
<div className="hidden shrink-0 gap-2 sm:flex">
|
|
<span className="rounded-full border border-amber-300/20 bg-amber-500/10 px-3 py-1 text-[11px] text-amber-100">
|
|
草稿 {draftCount}
|
|
</span>
|
|
<span className="rounded-full border border-emerald-300/20 bg-emerald-500/10 px-3 py-1 text-[11px] text-emerald-100">
|
|
已发布 {publishedCount}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
<CustomWorldCreationStartCard onCreateNew={onCreateNew} />
|
|
|
|
<CustomWorldWorkTabs
|
|
activeFilter={activeFilter}
|
|
draftCount={draftCount}
|
|
publishedCount={publishedCount}
|
|
onChange={setActiveFilter}
|
|
/>
|
|
|
|
{error ? (
|
|
<div className="rounded-3xl border border-rose-400/18 bg-rose-500/10 px-4 py-4 text-sm leading-7 text-rose-100">
|
|
<div>{error}</div>
|
|
<button
|
|
type="button"
|
|
onClick={onRetry}
|
|
className="mt-3 rounded-full border border-white/10 bg-black/20 px-4 py-2 text-sm text-zinc-200 transition hover:text-white"
|
|
>
|
|
重试
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
|
|
{loading ? (
|
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
{Array.from({ length: 3 }).map((_, index) => (
|
|
<div
|
|
key={`skeleton-${index}`}
|
|
className="min-h-[12rem] rounded-[1.8rem] border border-white/8 bg-white/5 p-5"
|
|
>
|
|
<div className="h-4 w-20 rounded-full bg-white/10" />
|
|
<div className="mt-6 h-8 w-36 rounded-full bg-white/10" />
|
|
<div className="mt-4 h-4 w-full rounded-full bg-white/10" />
|
|
<div className="mt-2 h-4 w-4/5 rounded-full bg-white/10" />
|
|
<div className="mt-8 flex gap-2">
|
|
<div className="h-7 w-20 rounded-full bg-white/10" />
|
|
<div className="h-7 w-20 rounded-full bg-white/10" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : filteredItems.length > 0 ? (
|
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
{filteredItems.map((item) => (
|
|
<CustomWorldWorkCard
|
|
key={item.workId}
|
|
item={item}
|
|
onClick={() => {
|
|
if (item.status === 'draft' && item.sessionId) {
|
|
onResumeDraft(item.sessionId);
|
|
return;
|
|
}
|
|
|
|
if (item.status === 'published' && item.profileId) {
|
|
onEnterPublished(item.profileId);
|
|
}
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
) : items.length === 0 ? (
|
|
<EmptyState title="还没有作品" />
|
|
) : (
|
|
<EmptyState title="当前筛选下没有内容" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export type { CustomWorldWorkFilter };
|