73 lines
2.7 KiB
TypeScript
73 lines
2.7 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import './App.css';
|
|
import { StoryEngine } from './engine/engine';
|
|
import { BRANCHES } from './engine/branches';
|
|
import { LocalStorageEventStore } from './engine/eventStore';
|
|
import { LocalStorageProgressStore } from './engine/progress';
|
|
import { SqliteEventStore, isTauriRuntime } from './engine/sqliteEventStore';
|
|
import type { Chapter } from './engine/types';
|
|
import { getJevClient } from './services/jev';
|
|
import { StoryPlayer } from './ui/StoryPlayer';
|
|
import { ConsentGate } from './ui/ConsentGate';
|
|
import { ReportPage } from './ui/report/ReportPage';
|
|
import chapter1 from '../content/chapters/chapter1.json';
|
|
import chapter2 from '../content/chapters/chapter2.json';
|
|
import chapter3 from '../content/chapters/chapter3.json';
|
|
|
|
const chapters = [chapter1, chapter2, chapter3] as Chapter[];
|
|
|
|
export default function App() {
|
|
const [screen, setScreen] = useState<'story' | 'report'>('story');
|
|
// consent: unknown=查询中 / needed=待同意 / granted=已同意(事件存证命中,同意门只出现一次)
|
|
const [consent, setConsent] = useState<'unknown' | 'needed' | 'granted'>('unknown');
|
|
// 引擎与存储整个会话只建一次;重玩走 engine.replay()(新 sessionId,旧事件保留)
|
|
// 运行时选择:Tauri 原生端用 SQLite,浏览器 dev 用 localStorage(同一 append-only 接口)
|
|
const { engine, store, jev, progress } = useMemo(() => {
|
|
const store = isTauriRuntime() ? new SqliteEventStore() : new LocalStorageEventStore();
|
|
const jev = getJevClient();
|
|
const progress = new LocalStorageProgressStore();
|
|
return { engine: new StoryEngine(chapters, store, jev, undefined, progress, BRANCHES), store, jev, progress };
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
void (async () => {
|
|
const ok = await engine.hasConsent();
|
|
// 同意门之后才恢复剧情进度(刷新/热更新前的断点)
|
|
if (ok) await engine.restore(progress.load());
|
|
setConsent(ok ? 'granted' : 'needed');
|
|
})();
|
|
}, [engine, progress]);
|
|
|
|
if (consent !== 'granted') {
|
|
return (
|
|
<div className="app">
|
|
{consent === 'needed' && (
|
|
<ConsentGate
|
|
onAgree={async () => {
|
|
await engine.grantConsent();
|
|
setConsent('granted');
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="app">
|
|
{screen === 'story' ? (
|
|
<StoryPlayer engine={engine} onFinish={() => setScreen('report')} />
|
|
) : (
|
|
<ReportPage
|
|
store={store}
|
|
jev={jev}
|
|
onReplay={() => {
|
|
engine.replay('chapter1');
|
|
setScreen('story');
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|