This commit is contained in:
2026-04-16 15:45:00 +08:00
parent 6363267bca
commit 91b63675eb
43 changed files with 5652 additions and 853 deletions

View File

@@ -1,10 +1,16 @@
import { type ReactNode,useMemo, useState } from 'react';
import { type ReactNode, useMemo, useState } from 'react';
import { normalizeCustomWorldLandmarks } from '../data/customWorldSceneGraph';
import { Character, CustomWorldProfile } from '../types';
import { getNineSliceStyle, UI_CHROME } from '../uiAssets';
import { CustomWorldEntityCatalog, type ResultTab } from './CustomWorldEntityCatalog';
import { type CustomWorldEditorTarget,CustomWorldEntityEditorModal } from './CustomWorldEntityEditorModal';
import {
CustomWorldEntityCatalog,
type ResultTab,
} from './CustomWorldEntityCatalog';
import {
type CustomWorldEditorTarget,
CustomWorldEntityEditorModal,
} from './CustomWorldEntityEditorModal';
interface CustomWorldResultViewProps {
profile: CustomWorldProfile;
@@ -17,8 +23,13 @@ interface CustomWorldResultViewProps {
onEditSetting?: () => void;
onRegenerate?: () => void;
onContinueExpand?: () => void;
onSave: () => void;
onSave?: () => void;
onProfileChange: (profile: CustomWorldProfile) => void;
readOnly?: boolean;
backLabel?: string;
editActionLabel?: string;
regenerateActionLabel?: string;
saveActionLabel?: string;
}
function SmallButton({
@@ -48,7 +59,9 @@ function SmallButton({
);
}
function getCreateTargetByTab(activeTab: ResultTab): CustomWorldEditorTarget | null {
function getCreateTargetByTab(
activeTab: ResultTab,
): CustomWorldEditorTarget | null {
if (activeTab === 'playable') return { kind: 'playable', mode: 'create' };
if (activeTab === 'story') return { kind: 'story', mode: 'create' };
if (activeTab === 'landmarks') return { kind: 'landmark', mode: 'create' };
@@ -82,7 +95,10 @@ function removeStoryNpcsFromProfile(
} satisfies CustomWorldProfile;
}
function removeLandmarksFromProfile(profile: CustomWorldProfile, ids: string[]) {
function removeLandmarksFromProfile(
profile: CustomWorldProfile,
ids: string[],
) {
const idSet = new Set(ids);
const nextLandmarks = profile.landmarks.filter(
(landmark) => !idSet.has(landmark.id),
@@ -115,12 +131,24 @@ export function CustomWorldResultView({
onContinueExpand,
onSave,
onProfileChange,
readOnly = false,
backLabel = '返回',
editActionLabel = '修改设定',
regenerateActionLabel = '重新生成',
saveActionLabel = '保存到我的作品',
}: CustomWorldResultViewProps) {
const [editorTarget, setEditorTarget] = useState<CustomWorldEditorTarget | null>(null);
const [editorTarget, setEditorTarget] =
useState<CustomWorldEditorTarget | null>(null);
const [activeTab, setActiveTab] = useState<ResultTab>('world');
const createTarget = useMemo(() => getCreateTargetByTab(activeTab), [activeTab]);
const createLabel = useMemo(() => getCreateLabelByTab(activeTab), [activeTab]);
const createTarget = useMemo(
() => getCreateTargetByTab(activeTab),
[activeTab],
);
const createLabel = useMemo(
() => getCreateLabelByTab(activeTab),
[activeTab],
);
const onRegenerate = () => {
if (isGenerating || !triggerRegenerate) return;
@@ -151,7 +179,7 @@ export function CustomWorldResultView({
disabled={isGenerating}
className={`self-start rounded-full border border-white/10 bg-black/18 px-3 py-1.5 text-[11px] text-zinc-300 transition-colors hover:text-white ${isGenerating ? 'opacity-45' : ''}`}
>
{backLabel}
</button>
</div>
@@ -165,15 +193,22 @@ export function CustomWorldResultView({
onProfileChange={onProfileChange}
onDeleteStoryNpcs={handleDeleteStoryNpcs}
onDeleteLandmarks={handleDeleteLandmarks}
createActionLabel={createLabel}
onCreateAction={createTarget ? () => setEditorTarget(createTarget) : undefined}
createActionLabel={readOnly ? undefined : createLabel}
onCreateAction={
readOnly || !createTarget
? undefined
: () => setEditorTarget(createTarget)
}
readOnly={readOnly}
/>
</div>
{isGenerating && (
<div className="mt-3 rounded-2xl border border-sky-400/18 bg-sky-500/10 px-4 py-4">
<div className="flex items-center justify-between gap-3">
<div className="text-sm font-semibold text-white">{progressLabel}</div>
<div className="text-sm font-semibold text-white">
{progressLabel}
</div>
<div className="text-xs text-sky-100">{Math.round(progress)}%</div>
</div>
<div className="mt-3 h-3 overflow-hidden rounded-full border border-white/10 bg-black/35">
@@ -199,28 +234,41 @@ export function CustomWorldResultView({
) : null}
<div className="flex items-center justify-end gap-3">
{onEditSetting ? (
<SmallButton onClick={onEditSetting}></SmallButton>
<SmallButton onClick={onEditSetting}>{editActionLabel}</SmallButton>
) : null}
{triggerRegenerate ? (
<SmallButton onClick={onRegenerate} tone="sky"></SmallButton>
<SmallButton onClick={onRegenerate} tone="sky">
{regenerateActionLabel}
</SmallButton>
) : null}
{profile.generationStatus === 'key_only' && onContinueExpand ? (
<SmallButton onClick={onContinueExpand} tone="sky" disabled={isGenerating}>
<SmallButton
onClick={onContinueExpand}
tone="sky"
disabled={isGenerating}
>
</SmallButton>
) : null}
<button
type="button"
onClick={onSave}
disabled={isGenerating}
className={`pixel-nine-slice pixel-pressable text-left ${isGenerating ? 'opacity-55' : ''}`}
style={getNineSliceStyle(UI_CHROME.choiceButton, { paddingX: 16, paddingY: 10 })}
>
<div className="flex items-center justify-between gap-4">
<span className="text-sm font-semibold text-white"></span>
<span className="text-white/60"></span>
</div>
</button>
{onSave ? (
<button
type="button"
onClick={onSave}
disabled={isGenerating}
className={`pixel-nine-slice pixel-pressable text-left ${isGenerating ? 'opacity-55' : ''}`}
style={getNineSliceStyle(UI_CHROME.choiceButton, {
paddingX: 16,
paddingY: 10,
})}
>
<div className="flex items-center justify-between gap-4">
<span className="text-sm font-semibold text-white">
{saveActionLabel}
</span>
<span className="text-white/60"></span>
</div>
</button>
) : null}
</div>
</div>