1
This commit is contained in:
142
src/components/visual-novel-runtime/VisualNovelRuntimePanels.tsx
Normal file
142
src/components/visual-novel-runtime/VisualNovelRuntimePanels.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { BarChart3, Bookmark, History, Settings, X } from 'lucide-react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import type { ProfileSaveArchiveSummary } from '../../../packages/shared/src/contracts/runtime';
|
||||
import type {
|
||||
VisualNovelResultDraft,
|
||||
VisualNovelRunSnapshot,
|
||||
} from '../../../packages/shared/src/contracts/visualNovel';
|
||||
import { VisualNovelAttributePanel } from './VisualNovelAttributePanel';
|
||||
import { VisualNovelHistoryPanel } from './VisualNovelHistoryPanel';
|
||||
import { VisualNovelSavePanel } from './VisualNovelSavePanel';
|
||||
import { VisualNovelSettingsPanel } from './VisualNovelSettingsPanel';
|
||||
|
||||
export type VisualNovelRuntimePanelKind =
|
||||
| 'history'
|
||||
| 'save'
|
||||
| 'settings'
|
||||
| 'attributes';
|
||||
|
||||
type VisualNovelRuntimePanelProps = {
|
||||
kind: VisualNovelRuntimePanelKind;
|
||||
draft: VisualNovelResultDraft;
|
||||
run: VisualNovelRunSnapshot;
|
||||
isBusy?: boolean;
|
||||
isSaving?: boolean;
|
||||
isLoadingArchives?: boolean;
|
||||
resumingWorldKey?: string | null;
|
||||
saveArchives?: ProfileSaveArchiveSummary[];
|
||||
onClose: () => void;
|
||||
onRegenerateHistoryEntry?: (entryId: string) => void;
|
||||
onSaveRun?: () => void;
|
||||
onResumeSaveArchive?: (worldKey: string) => void;
|
||||
textModeEnabled?: boolean;
|
||||
onTextModeChange?: (enabled: boolean) => void;
|
||||
allowRegeneration?: boolean;
|
||||
};
|
||||
|
||||
const PANEL_META: Record<
|
||||
VisualNovelRuntimePanelKind,
|
||||
{ title: string; icon: typeof History }
|
||||
> = {
|
||||
history: { title: '历史', icon: History },
|
||||
save: { title: '存档', icon: Bookmark },
|
||||
settings: { title: '设置', icon: Settings },
|
||||
attributes: { title: '属性', icon: BarChart3 },
|
||||
};
|
||||
|
||||
export function VisualNovelRuntimePanel({
|
||||
kind,
|
||||
draft,
|
||||
run,
|
||||
isBusy = false,
|
||||
isSaving = false,
|
||||
isLoadingArchives = false,
|
||||
resumingWorldKey = null,
|
||||
saveArchives = [],
|
||||
onClose,
|
||||
onRegenerateHistoryEntry,
|
||||
onSaveRun,
|
||||
onResumeSaveArchive,
|
||||
textModeEnabled = false,
|
||||
onTextModeChange,
|
||||
allowRegeneration = false,
|
||||
}: VisualNovelRuntimePanelProps) {
|
||||
if (typeof document === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const meta = PANEL_META[kind];
|
||||
const Icon = meta.icon;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className="platform-overlay fixed inset-0 z-[150] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4"
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={meta.title}
|
||||
className="platform-modal-shell platform-remap-surface flex max-h-[min(84vh,40rem)] w-full max-w-lg flex-col overflow-hidden rounded-t-[1.45rem] sm:rounded-[1.45rem]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<header className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-4 py-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-[var(--platform-neutral-bg)] text-[var(--platform-neutral-text)]">
|
||||
<Icon className="h-4 w-4" />
|
||||
</span>
|
||||
<h2 className="truncate text-base font-black text-[var(--platform-text-strong)]">
|
||||
{meta.title}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="platform-icon-button h-9 w-9"
|
||||
onClick={onClose}
|
||||
aria-label="关闭"
|
||||
title="关闭"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
|
||||
{kind === 'history' ? (
|
||||
<VisualNovelHistoryPanel
|
||||
run={run}
|
||||
allowRegeneration={allowRegeneration}
|
||||
isBusy={isBusy}
|
||||
onRegenerateHistoryEntry={onRegenerateHistoryEntry}
|
||||
/>
|
||||
) : null}
|
||||
{kind === 'save' ? (
|
||||
<VisualNovelSavePanel
|
||||
run={run}
|
||||
saveArchives={saveArchives}
|
||||
isSaving={isSaving}
|
||||
isLoadingArchives={isLoadingArchives}
|
||||
resumingWorldKey={resumingWorldKey}
|
||||
onSaveRun={onSaveRun}
|
||||
onResumeSaveArchive={onResumeSaveArchive}
|
||||
/>
|
||||
) : null}
|
||||
{kind === 'settings' ? (
|
||||
<VisualNovelSettingsPanel
|
||||
draft={draft}
|
||||
textModeEnabled={textModeEnabled}
|
||||
onTextModeChange={onTextModeChange}
|
||||
/>
|
||||
) : null}
|
||||
{kind === 'attributes' ? <VisualNovelAttributePanel run={run} /> : null}
|
||||
</div>
|
||||
</section>
|
||||
</div>,
|
||||
document.body,
|
||||
);
|
||||
}
|
||||
|
||||
export default VisualNovelRuntimePanel;
|
||||
Reference in New Issue
Block a user