1
This commit is contained in:
102
src/components/visual-novel-runtime/VisualNovelSavePanel.tsx
Normal file
102
src/components/visual-novel-runtime/VisualNovelSavePanel.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { Bookmark, Loader2, Play } from 'lucide-react';
|
||||
|
||||
import type { ProfileSaveArchiveSummary } from '../../../packages/shared/src/contracts/runtime';
|
||||
import type { VisualNovelRunSnapshot } from '../../../packages/shared/src/contracts/visualNovel';
|
||||
|
||||
type VisualNovelSavePanelProps = {
|
||||
run: VisualNovelRunSnapshot;
|
||||
saveArchives?: ProfileSaveArchiveSummary[];
|
||||
isSaving?: boolean;
|
||||
isLoadingArchives?: boolean;
|
||||
resumingWorldKey?: string | null;
|
||||
onSaveRun?: () => void;
|
||||
onResumeSaveArchive?: (worldKey: string) => void;
|
||||
};
|
||||
|
||||
function formatArchiveTime(value: string) {
|
||||
const timestamp = new Date(value).getTime();
|
||||
if (Number.isNaN(timestamp)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('zh-CN', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(timestamp);
|
||||
}
|
||||
|
||||
export function VisualNovelSavePanel({
|
||||
run,
|
||||
saveArchives = [],
|
||||
isSaving = false,
|
||||
isLoadingArchives = false,
|
||||
resumingWorldKey = null,
|
||||
onSaveRun,
|
||||
onResumeSaveArchive,
|
||||
}: VisualNovelSavePanelProps) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!onSaveRun || isSaving}
|
||||
onClick={onSaveRun}
|
||||
className="flex min-h-12 w-full items-center justify-center gap-2 rounded-full border border-[var(--platform-subpanel-border)] bg-[var(--platform-button-primary-fill)] px-4 text-sm font-black text-white transition hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-55"
|
||||
>
|
||||
{isSaving ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Bookmark className="h-4 w-4" />
|
||||
)}
|
||||
<span>{isSaving ? '保存中' : '保存'}</span>
|
||||
</button>
|
||||
|
||||
{isLoadingArchives ? (
|
||||
<div className="rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/74 px-4 py-5 text-center text-sm font-semibold text-[var(--platform-text-soft)]">
|
||||
读取中
|
||||
</div>
|
||||
) : saveArchives.length > 0 ? (
|
||||
<div className="grid gap-3">
|
||||
{saveArchives.map((entry) => {
|
||||
const isResuming = resumingWorldKey === entry.worldKey;
|
||||
return (
|
||||
<button
|
||||
key={entry.worldKey}
|
||||
type="button"
|
||||
disabled={isResuming || !onResumeSaveArchive}
|
||||
onClick={() => onResumeSaveArchive?.(entry.worldKey)}
|
||||
className="flex min-h-20 items-center gap-3 rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/78 p-3 text-left transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-55"
|
||||
>
|
||||
<span className="grid h-11 w-11 shrink-0 place-items-center rounded-[0.85rem] bg-[var(--platform-neutral-bg)] text-[var(--platform-neutral-text)]">
|
||||
{isResuming ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Play className="h-4 w-4" />
|
||||
)}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-sm font-black text-[var(--platform-text-strong)]">
|
||||
{entry.worldName || run.profileId}
|
||||
</span>
|
||||
<span className="mt-1 block truncate text-xs font-semibold text-[var(--platform-text-soft)]">
|
||||
{entry.subtitle || entry.summaryText || run.runId}
|
||||
</span>
|
||||
<span className="mt-1 block text-[11px] font-bold text-[var(--platform-text-soft)]">
|
||||
{formatArchiveTime(entry.lastPlayedAt)}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/74 px-4 py-5 text-center text-sm font-semibold text-[var(--platform-text-soft)]">
|
||||
暂无存档
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VisualNovelSavePanel;
|
||||
Reference in New Issue
Block a user