Initial MVP: local visual novel library with Ren'Py → PGVN converter
- FastAPI backend: admin bootstrap with forced credential change, admin-created users with one-time generated passwords, roles as permission sets - Games and versions with content-addressed blob storage; forks copy only the manifest, so mods share assets with the original - Worker: archive/RPA extraction, unrpyc for .rpyc, own .rpy parser, Python subset translated via ast, media converted to WebP/Opus/WebM - React panel (library search, game page, upload, users, roles) and a framework-free web player with saves, history, skip/auto, transitions - docker compose: postgres, backend, worker, nginx on a single HTTP port Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
364b312064
72 files changed
+9707
No files matched your search
@@ -0,0 +1,60 @@
|
||||
import { Navigate, NavLink, Route, Routes } from "react-router-dom";
|
||||
import { useAuth } from "./auth";
|
||||
import { ChangeCredentials } from "./pages/ChangeCredentials";
|
||||
import { GamePage } from "./pages/GamePage";
|
||||
import { Library } from "./pages/Library";
|
||||
import { Login } from "./pages/Login";
|
||||
import { NewGame } from "./pages/NewGame";
|
||||
import { Play } from "./pages/Play";
|
||||
import { Roles } from "./pages/Roles";
|
||||
import { Users } from "./pages/Users";
|
||||
|
||||
function RequirePerm({ perm, children }: { perm: string; children: JSX.Element }) {
|
||||
const { can } = useAuth();
|
||||
return can(perm) ? children : <div className="page"><p className="muted">Нет доступа к этой странице.</p></div>;
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const { me, loading, logout, can } = useAuth();
|
||||
if (loading) return <div className="center muted">Загрузка…</div>;
|
||||
if (!me) return <Login />;
|
||||
if (me.must_change_password || me.must_change_username) return <ChangeCredentials forced />;
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/play/:versionId" element={<RequirePerm perm="games.play"><Play /></RequirePerm>} />
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<div className="layout">
|
||||
<header className="topbar">
|
||||
<NavLink to="/" className="brand">PGames</NavLink>
|
||||
<nav>
|
||||
<NavLink to="/" end>Библиотека</NavLink>
|
||||
{can("games.upload") && <NavLink to="/games/new">Загрузить игру</NavLink>}
|
||||
{can("users.manage") && <NavLink to="/admin/users">Пользователи</NavLink>}
|
||||
{can("roles.manage") && <NavLink to="/admin/roles">Роли</NavLink>}
|
||||
</nav>
|
||||
<div className="user">
|
||||
<NavLink to="/account">{me.username}</NavLink>
|
||||
<span className="muted"> · {me.role.name}</span>
|
||||
<button className="link" onClick={() => void logout()}>Выйти</button>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="/" element={<RequirePerm perm="games.play"><Library /></RequirePerm>} />
|
||||
<Route path="/games/new" element={<RequirePerm perm="games.upload"><NewGame /></RequirePerm>} />
|
||||
<Route path="/games/:gameId" element={<RequirePerm perm="games.play"><GamePage /></RequirePerm>} />
|
||||
<Route path="/admin/users" element={<RequirePerm perm="users.manage"><Users /></RequirePerm>} />
|
||||
<Route path="/admin/roles" element={<RequirePerm perm="roles.manage"><Roles /></RequirePerm>} />
|
||||
<Route path="/account" element={<ChangeCredentials />} />
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user