143 lines
4.6 KiB
TypeScript
143 lines
4.6 KiB
TypeScript
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;
|