1
This commit is contained in:
@@ -18,7 +18,6 @@ import {
|
||||
resolveCustomWorldLandmarkImageMap,
|
||||
} from '../data/customWorldVisuals';
|
||||
import { resolveCustomWorldCampScene } from '../services/customWorldCamp';
|
||||
import { resolveCustomWorldCoverPresentation } from '../services/customWorldCover';
|
||||
import { normalizeCustomWorldCreatorIntent } from '../services/customWorldCreatorIntent';
|
||||
import {
|
||||
AnimationState,
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
type SceneChapterBlueprint,
|
||||
} from '../types';
|
||||
import { CharacterAnimator } from './CharacterAnimator';
|
||||
import { CustomWorldCoverArtwork } from './CustomWorldCoverArtwork';
|
||||
import type { RpgCreationEditorTarget } from './rpg-creation-editor/RpgCreationEntityEditorModal';
|
||||
import { CustomWorldNpcPortrait } from './CustomWorldNpcVisualEditor';
|
||||
import { ResolvedAssetImage } from './ResolvedAssetImage';
|
||||
@@ -54,8 +52,6 @@ interface CustomWorldEntityCatalogProps {
|
||||
onProfileChange: (profile: CustomWorldProfile) => void;
|
||||
onDeleteStoryNpcs?: (ids: string[]) => void;
|
||||
onDeleteLandmarks?: (ids: string[]) => void;
|
||||
onGenerateRoleAssets?: (roleId: string) => void;
|
||||
onGenerateSceneAssets?: (sceneId: string, sceneKind: 'camp' | 'landmark') => void;
|
||||
createActionLabel?: string;
|
||||
onCreateAction?: () => void;
|
||||
createActionDisabled?: boolean;
|
||||
@@ -389,21 +385,21 @@ function CatalogCard({
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
aria-disabled={disabled}
|
||||
className={`w-full rounded-[1.3rem] border p-2.5 text-left transition-colors ${
|
||||
className={`w-full rounded-[1.3rem] border p-2.5 text-left transition-colors xl:p-3 ${
|
||||
isSelected
|
||||
? 'border-rose-300/35 bg-rose-500/10'
|
||||
: 'platform-subpanel'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex items-start gap-3 xl:gap-3.5">
|
||||
<div
|
||||
className={`platform-subpanel shrink-0 overflow-hidden rounded-[1rem] ${mediaClassName ?? 'h-[4.75rem] w-[4.75rem]'}`}
|
||||
>
|
||||
{media}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="min-w-0 flex-1 xl:min-h-[5.6rem]">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 text-[15px] font-semibold leading-5 text-white">
|
||||
<div className="min-w-0 text-[15px] font-semibold leading-5 text-white xl:line-clamp-1">
|
||||
{title}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -411,7 +407,7 @@ function CatalogCard({
|
||||
{selectionBadge}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1.5 text-sm leading-5 text-zinc-300">
|
||||
<div className="mt-1.5 text-sm leading-5 text-zinc-300 xl:line-clamp-2">
|
||||
{description || '暂无描述'}
|
||||
</div>
|
||||
{actions ? <div className="mt-2 flex flex-wrap gap-2">{actions}</div> : null}
|
||||
@@ -891,8 +887,6 @@ export function CustomWorldEntityCatalog({
|
||||
onProfileChange,
|
||||
onDeleteStoryNpcs,
|
||||
onDeleteLandmarks,
|
||||
onGenerateRoleAssets,
|
||||
onGenerateSceneAssets,
|
||||
createActionLabel,
|
||||
onCreateAction,
|
||||
createActionDisabled = false,
|
||||
@@ -1104,11 +1098,6 @@ export function CustomWorldEntityCatalog({
|
||||
1 +
|
||||
(pendingGeneratedEntity?.kind === 'landmark' ? 1 : 0),
|
||||
} satisfies Record<ResultTab, number>;
|
||||
const coverPresentation = useMemo(
|
||||
() => resolveCustomWorldCoverPresentation(profile),
|
||||
[profile],
|
||||
);
|
||||
|
||||
const bulkDeleteTab: BulkDeleteTab | null =
|
||||
activeTab === 'story' || activeTab === 'landmarks' ? activeTab : null;
|
||||
const isBulkDeleteMode =
|
||||
@@ -1185,28 +1174,28 @@ export function CustomWorldEntityCatalog({
|
||||
return (
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
className="h-full min-h-0 space-y-3 overflow-y-auto overscroll-contain pr-1 scrollbar-hide"
|
||||
className="h-full min-h-0 space-y-3 overflow-y-auto overscroll-contain pr-1 scrollbar-hide xl:space-y-4 xl:pr-2"
|
||||
>
|
||||
<div className="px-1 pb-1 text-center">
|
||||
<div className="px-1 pb-1 text-center xl:rounded-[2rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/55 xl:px-6 xl:py-4 xl:text-left xl:shadow-[0_18px_70px_rgba(255,79,139,0.08)] xl:backdrop-blur-sm">
|
||||
<div className="text-[11px] font-bold tracking-[0.28em] text-zinc-500">
|
||||
世界档案
|
||||
</div>
|
||||
<div className="mt-2 text-3xl font-black text-[var(--platform-text-strong)] sm:text-[2.2rem]">
|
||||
<div className="mt-2 text-3xl font-black text-[var(--platform-text-strong)] sm:text-[2.2rem] xl:mt-1 xl:text-[2rem]">
|
||||
{profile.name}
|
||||
</div>
|
||||
<div className="mt-2 text-sm tracking-[0.18em] text-zinc-400">
|
||||
<div className="mt-2 text-sm tracking-[0.18em] text-zinc-400 xl:mt-1 xl:text-xs">
|
||||
{profile.subtitle}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="platform-sticky-fade sticky top-0 z-10 -mx-1 space-y-3 px-1 pb-3 pt-1 backdrop-blur-sm">
|
||||
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-hide">
|
||||
<div className="platform-sticky-fade sticky top-0 z-10 -mx-1 space-y-3 px-1 pb-3 pt-1 backdrop-blur-sm xl:rounded-[1.75rem] xl:border xl:border-[var(--platform-subpanel-border)] xl:bg-white/70 xl:px-4 xl:py-3 xl:shadow-[0_16px_48px_rgba(255,79,139,0.08)]">
|
||||
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-hide xl:pb-0">
|
||||
{RESULT_TABS.map((tab) => (
|
||||
<div key={tab.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onActiveTabChange(tab.id)}
|
||||
className={`platform-tab px-3 py-2 text-left text-sm ${activeTab === tab.id ? 'platform-tab--active' : ''}`}
|
||||
className={`platform-tab px-3 py-2 text-left text-sm xl:min-w-[5.25rem] xl:px-4 xl:py-2 ${activeTab === tab.id ? 'platform-tab--active' : ''}`}
|
||||
>
|
||||
<div className="font-semibold">{tab.label}</div>
|
||||
<div className="mt-1 text-[10px] tracking-[0.16em] text-[var(--platform-text-soft)]">
|
||||
@@ -1218,7 +1207,7 @@ export function CustomWorldEntityCatalog({
|
||||
</div>
|
||||
|
||||
{activeTab !== 'world' ? (
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center xl:gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<SearchBox
|
||||
value={searchDraft}
|
||||
@@ -1267,7 +1256,7 @@ export function CustomWorldEntityCatalog({
|
||||
</div>
|
||||
|
||||
{activeTab === 'world' ? (
|
||||
<>
|
||||
<div className="space-y-3 xl:grid xl:grid-cols-[0.8fr_1.2fr] xl:items-start xl:gap-3 xl:space-y-0">
|
||||
<Section title="档案规模">
|
||||
<div className="grid grid-cols-3 gap-2 text-center text-[11px] text-zinc-300">
|
||||
<div className="platform-subpanel rounded-xl px-2 py-3">
|
||||
@@ -1291,40 +1280,6 @@ export function CustomWorldEntityCatalog({
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="作品封面"
|
||||
badge={
|
||||
<span className="platform-pill platform-pill--neutral px-2.5 py-1 text-[10px]">
|
||||
{coverPresentation.sourceType === 'uploaded'
|
||||
? '上传封面'
|
||||
: coverPresentation.sourceType === 'generated'
|
||||
? 'AI封面'
|
||||
: '默认封面'}
|
||||
</span>
|
||||
}
|
||||
actions={
|
||||
!readOnly ? (
|
||||
<SmallButton
|
||||
onClick={() => onEditTarget({ kind: 'cover' })}
|
||||
tone="sky"
|
||||
>
|
||||
编辑
|
||||
</SmallButton>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<CustomWorldCoverArtwork
|
||||
imageSrc={coverPresentation.imageSrc}
|
||||
title={profile.name}
|
||||
fallbackLabel={profile.name.slice(0, 4) || '封面'}
|
||||
renderMode={coverPresentation.renderMode}
|
||||
characterImageSrcs={coverPresentation.characterImageSrcs}
|
||||
className="aspect-[16/9] rounded-[1.4rem] border border-[var(--platform-subpanel-border)]"
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="世界概述"
|
||||
actions={
|
||||
@@ -1394,11 +1349,11 @@ export function CustomWorldEntityCatalog({
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'playable' ? (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 xl:grid xl:grid-cols-2 xl:gap-3 xl:space-y-0 2xl:grid-cols-3">
|
||||
{pendingGeneratedEntity?.kind === 'playable' ? (
|
||||
<PendingEntityCard
|
||||
title={pendingGeneratedEntity.title}
|
||||
@@ -1433,7 +1388,7 @@ export function CustomWorldEntityCatalog({
|
||||
isSelectionMode={false}
|
||||
isSelected={false}
|
||||
layout="compact"
|
||||
mediaClassName="h-[4.75rem] w-[4.75rem] sm:h-[5.25rem] sm:w-[5.25rem]"
|
||||
mediaClassName="h-[4.75rem] w-[4.75rem] sm:h-[5.25rem] sm:w-[5.25rem] xl:h-[5.75rem] xl:w-[5.75rem]"
|
||||
onClick={() =>
|
||||
onEditTarget({
|
||||
kind: 'playable',
|
||||
@@ -1441,19 +1396,6 @@ export function CustomWorldEntityCatalog({
|
||||
id: role.id,
|
||||
})
|
||||
}
|
||||
actions={
|
||||
!readOnly && onGenerateRoleAssets ? (
|
||||
<SmallButton
|
||||
onClick={(event) => {
|
||||
event?.stopPropagation();
|
||||
onGenerateRoleAssets(role.id);
|
||||
}}
|
||||
tone="sky"
|
||||
>
|
||||
生成资产
|
||||
</SmallButton>
|
||||
) : null
|
||||
}
|
||||
media={
|
||||
role.imageSrc?.trim() ? (
|
||||
<ResolvedAssetImage
|
||||
@@ -1522,7 +1464,7 @@ export function CustomWorldEntityCatalog({
|
||||
) : null}
|
||||
|
||||
{activeTab === 'story' ? (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 xl:grid xl:grid-cols-2 xl:gap-3 xl:space-y-0 2xl:grid-cols-3">
|
||||
{pendingGeneratedEntity?.kind === 'story' ? (
|
||||
<PendingEntityCard
|
||||
title={pendingGeneratedEntity.title}
|
||||
@@ -1547,7 +1489,7 @@ export function CustomWorldEntityCatalog({
|
||||
isSelectionMode={isBulkDeleteMode}
|
||||
isSelected={selectedBulkIds.includes(npc.id)}
|
||||
layout="compact"
|
||||
mediaClassName="h-[4.75rem] w-[4.75rem] sm:h-[5.25rem] sm:w-[5.25rem]"
|
||||
mediaClassName="h-[4.75rem] w-[4.75rem] sm:h-[5.25rem] sm:w-[5.25rem] xl:h-[5.75rem] xl:w-[5.75rem]"
|
||||
onClick={() =>
|
||||
isBulkDeleteMode
|
||||
? toggleBulkSelected(npc.id)
|
||||
@@ -1563,19 +1505,6 @@ export function CustomWorldEntityCatalog({
|
||||
id: npc.id,
|
||||
})
|
||||
}
|
||||
actions={
|
||||
!readOnly && !isBulkDeleteMode && onGenerateRoleAssets ? (
|
||||
<SmallButton
|
||||
onClick={(event) => {
|
||||
event?.stopPropagation();
|
||||
onGenerateRoleAssets(npc.id);
|
||||
}}
|
||||
tone="sky"
|
||||
>
|
||||
生成资产
|
||||
</SmallButton>
|
||||
) : null
|
||||
}
|
||||
media={
|
||||
<CustomWorldNpcPortrait
|
||||
npc={npc}
|
||||
@@ -1595,7 +1524,7 @@ export function CustomWorldEntityCatalog({
|
||||
) : null}
|
||||
|
||||
{activeTab === 'landmarks' ? (
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-3 xl:grid xl:grid-cols-2 xl:gap-3 xl:space-y-0 2xl:grid-cols-3">
|
||||
{pendingGeneratedEntity?.kind === 'landmark' ? (
|
||||
<PendingEntityCard
|
||||
title={pendingGeneratedEntity.title}
|
||||
@@ -1639,20 +1568,6 @@ export function CustomWorldEntityCatalog({
|
||||
id: scene.id,
|
||||
})
|
||||
}
|
||||
actions={
|
||||
!readOnly && !isBulkDeleteMode && onGenerateSceneAssets ? (
|
||||
<SmallButton
|
||||
onClick={(event) => {
|
||||
event?.stopPropagation();
|
||||
onGenerateSceneAssets(scene.id, scene.kind);
|
||||
}}
|
||||
tone="sky"
|
||||
disabled={scene.kind === 'camp' && isBulkDeleteMode}
|
||||
>
|
||||
生成场景图
|
||||
</SmallButton>
|
||||
) : null
|
||||
}
|
||||
media={
|
||||
<ImageFrame
|
||||
src={scene.imageSrc}
|
||||
|
||||
Reference in New Issue
Block a user