remove references to testgame on the server, add gameId to router
This commit is contained in:
+7
-2
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Outlet, useParams } from "react-router-dom";
|
||||
|
||||
import '@fontsource/roboto/300.css';
|
||||
import '@fontsource/roboto/400.css';
|
||||
@@ -10,10 +10,15 @@ import '@fontsource/roboto/700.css';
|
||||
import './reset.css';
|
||||
import './app.css';
|
||||
|
||||
export const GameIdContext = React.createContext<string>(undefined);
|
||||
|
||||
function App() {
|
||||
const params = useParams();
|
||||
return (
|
||||
<div className="app">
|
||||
<Outlet />
|
||||
<GameIdContext.Provider value={params.gameId}>
|
||||
<Outlet />
|
||||
</GameIdContext.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons'
|
||||
import Markdown from './Markdown';
|
||||
import { useLoadDocQuery, ComputedInventoryItem } from '../state/api';
|
||||
import { GameIdContext } from '../App';
|
||||
|
||||
export function Inventory({ tactics, lemmas, definitions, setInventoryDoc } :
|
||||
{lemmas: ComputedInventoryItem[],
|
||||
@@ -77,8 +78,8 @@ function InventoryItem({name, displayName, locked, disabled, showDoc}) {
|
||||
}
|
||||
|
||||
export function Documentation({name, type}) {
|
||||
|
||||
const doc = useLoadDocQuery({type: type, name: name})
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const doc = useLoadDocQuery({game: gameId, type: type, name: name})
|
||||
|
||||
return <>
|
||||
<h2 className="doc">{doc.data?.displayName}</h2>
|
||||
|
||||
@@ -40,6 +40,7 @@ import Markdown from './Markdown';
|
||||
|
||||
import Split from 'react-split'
|
||||
import { Alert } from '@mui/material';
|
||||
import { GameIdContext } from '../App';
|
||||
|
||||
export const MonacoEditorContext = React.createContext<monaco.editor.IStandaloneCodeEditor>(null as any);
|
||||
|
||||
@@ -125,9 +126,9 @@ function PlayableLevel({worldId, levelId}) {
|
||||
}]);
|
||||
}
|
||||
|
||||
const gameInfo = useGetGameInfoQuery()
|
||||
|
||||
const level = useLoadLevelQuery({world: worldId, level: levelId})
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||
const level = useLoadLevelQuery({game: gameId, world: worldId, level: levelId})
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@@ -209,8 +210,8 @@ function PlayableLevel({worldId, levelId}) {
|
||||
<Markdown>{level?.data?.conclusion}</Markdown>
|
||||
</div>
|
||||
{levelId >= gameInfo.data?.worldSize[worldId] ?
|
||||
<Button to={`/`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/world/${worldId}/level/${levelId + 1}`}>
|
||||
<Button to={`/game/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/game/${gameId}/world/${worldId}/level/${levelId + 1}`}>
|
||||
Next <FontAwesomeIcon icon={faArrowRight} /></Button>}
|
||||
|
||||
</div>}
|
||||
@@ -230,7 +231,8 @@ function PlayableLevel({worldId, levelId}) {
|
||||
export default Level
|
||||
|
||||
function Introduction({worldId}) {
|
||||
const gameInfo = useGetGameInfoQuery()
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||
|
||||
return <>
|
||||
<div style={gameInfo.isLoading ? null : {display: "none"}} className="app-content loading"><CircularProgress /></div>
|
||||
@@ -245,8 +247,8 @@ function Introduction({worldId}) {
|
||||
</div>
|
||||
<div className="conclusion">
|
||||
{0 == gameInfo.data?.worldSize[worldId] ?
|
||||
<Button to={`/`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/world/${worldId}/level/1`}>
|
||||
<Button to={`/game/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button> :
|
||||
<Button to={`/game/${gameId}/world/${worldId}/level/1`}>
|
||||
Start <FontAwesomeIcon icon={faArrowRight} />
|
||||
</Button>}
|
||||
</div>
|
||||
@@ -255,11 +257,12 @@ function Introduction({worldId}) {
|
||||
}
|
||||
|
||||
function LevelAppBar({isLoading, levelId, worldId, levelTitle}) {
|
||||
const gameInfo = useGetGameInfoQuery()
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||
|
||||
return <div className="app-bar" style={isLoading ? {display: "none"} : null} >
|
||||
<div>
|
||||
<Button to={`/`}><FontAwesomeIcon icon={faHome} /></Button>
|
||||
<Button to={`/game/${gameId}`}><FontAwesomeIcon icon={faHome} /></Button>
|
||||
<span className="app-bar-title">
|
||||
{gameInfo.data?.worlds.nodes[worldId].title && `World: ${gameInfo.data?.worlds.nodes[worldId].title}`}
|
||||
</span>
|
||||
@@ -269,10 +272,10 @@ function LevelAppBar({isLoading, levelId, worldId, levelTitle}) {
|
||||
{levelTitle}
|
||||
</span>
|
||||
<Button disabled={levelId <= 0} inverted={true}
|
||||
to={`/world/${worldId}/level/${levelId - 1}`}
|
||||
to={`/game/${gameId}/world/${worldId}/level/${levelId - 1}`}
|
||||
><FontAwesomeIcon icon={faArrowLeft} /> Previous</Button>
|
||||
<Button disabled={levelId >= gameInfo.data?.worldSize[worldId]} inverted={true}
|
||||
to={`/world/${worldId}/level/${levelId + 1}`}
|
||||
to={`/game/${gameId}/world/${worldId}/level/${levelId + 1}`}
|
||||
>Next <FontAwesomeIcon icon={faArrowRight} /></Button>
|
||||
</div>
|
||||
|
||||
@@ -282,6 +285,7 @@ function LevelAppBar({isLoading, levelId, worldId, levelTitle}) {
|
||||
function useLevelEditor(worldId: string, levelId: number, codeviewRef, initialCode, initialSelections, onDidChangeContent, onDidChangeSelection) {
|
||||
|
||||
const connection = React.useContext(ConnectionContext)
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
|
||||
const [editor, setEditor] = useState<monaco.editor.IStandaloneCodeEditor|null>(null)
|
||||
const [infoProvider, setInfoProvider] = useState<null|InfoProvider>(null)
|
||||
@@ -308,7 +312,7 @@ function useLevelEditor(worldId: string, levelId: number, codeviewRef, initialCo
|
||||
theme: 'vs-code-theme-converted'
|
||||
})
|
||||
|
||||
const infoProvider = new InfoProvider(connection.getLeanClient())
|
||||
const infoProvider = new InfoProvider(connection.getLeanClient(gameId))
|
||||
|
||||
const editorApi = infoProvider.getApi()
|
||||
|
||||
@@ -358,7 +362,7 @@ function useLevelEditor(worldId: string, levelId: number, codeviewRef, initialCo
|
||||
return () => { infoProvider.dispose(); editor.dispose() }
|
||||
}, [])
|
||||
|
||||
const {leanClient, leanClientStarted} = useLeanClient()
|
||||
const {leanClient, leanClientStarted} = useLeanClient(gameId)
|
||||
|
||||
// Create model when level changes
|
||||
useEffect(() => {
|
||||
@@ -391,7 +395,8 @@ function useLevelEditor(worldId: string, levelId: number, codeviewRef, initialCo
|
||||
|
||||
/** Open all files in this world on the server so that they will load faster when accessed */
|
||||
function useLoadWorldFiles(worldId) {
|
||||
const gameInfo = useGetGameInfoQuery()
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||
const store = useStore()
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -14,13 +14,15 @@ import { useGetGameInfoQuery } from '../state/api';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Markdown from './Markdown';
|
||||
import { selectCompleted } from '../state/progress';
|
||||
import { GameIdContext } from '../App';
|
||||
|
||||
|
||||
function LevelIcon({ worldId, levelId, position }) {
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const completed = useSelector(selectCompleted(worldId,levelId))
|
||||
// TODO: relative positioning?
|
||||
return (
|
||||
<Link to={`/world/${worldId}/level/${levelId}`} key={`/world/${worldId}/level/${levelId}`}>
|
||||
<Link to={`/game/${gameId}/${worldId}/level/${levelId}`} key={`/game/${gameId}/world/${worldId}/level/${levelId}`}>
|
||||
<circle fill={completed ? "green" :"#aaa"} cx={position.x + Math.sin(levelId/5) * 9} cy={position.y - Math.cos(levelId/5) * 9} r="0.8" />
|
||||
</Link>
|
||||
)
|
||||
@@ -29,7 +31,8 @@ function LevelIcon({ worldId, levelId, position }) {
|
||||
function Welcome() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const gameInfo = useGetGameInfoQuery()
|
||||
const gameId = React.useContext(GameIdContext)
|
||||
const gameInfo = useGetGameInfoQuery({game: gameId})
|
||||
|
||||
const { nodes, bounds }: any = gameInfo.data ? computeWorldLayout(gameInfo.data?.worlds) : {nodes: []}
|
||||
|
||||
@@ -55,7 +58,7 @@ function Welcome() {
|
||||
let position: cytoscape.Position = nodes[id].position
|
||||
|
||||
svgElements.push(
|
||||
<Link key={`world${id}`} to={`/world/${id}/level/0`}>
|
||||
<Link key={`world${id}`} to={`/game/${gameId}/world/${id}/level/0`}>
|
||||
<circle className="world-circle" cx={position.x} cy={position.y} r="8" />
|
||||
<text className="world-name"
|
||||
x={position.x} y={position.y}>{nodes[id].data.title ? nodes[id].data.title : id}</text>
|
||||
|
||||
@@ -6,12 +6,17 @@ import { useState } from 'react';
|
||||
|
||||
|
||||
export class Connection {
|
||||
private leanClient = null
|
||||
|
||||
getLeanClient(): LeanClient {
|
||||
if (this.leanClient === null) {
|
||||
const socketUrl = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + '/websocket/'
|
||||
private game: string = undefined // We only keep a connection to a single game at a time
|
||||
private leanClient: LeanClient = null
|
||||
|
||||
getLeanClient(game): LeanClient {
|
||||
if (this.game !== game) {
|
||||
if (this.leanClient) {
|
||||
this.leanClient.stop() // Stop previous Lean client
|
||||
}
|
||||
this.game = game
|
||||
// Start a new Lean client for the new `gameId`.
|
||||
const socketUrl = ((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + '/websocket/' + game
|
||||
const uri = monaco.Uri.parse('file:///')
|
||||
this.leanClient = new LeanClient(socketUrl, undefined, uri, () => {})
|
||||
}
|
||||
@@ -22,9 +27,9 @@ export class Connection {
|
||||
/** If not already started, starts the Lean client. resolves the returned promise as soon as a
|
||||
* Lean client is running.
|
||||
*/
|
||||
startLeanClient = () => {
|
||||
startLeanClient = (game) => {
|
||||
return new Promise<LeanClient>((resolve) => {
|
||||
const leanClient = this.getLeanClient()
|
||||
const leanClient = this.getLeanClient(game)
|
||||
if (leanClient.isRunning()) {
|
||||
resolve(leanClient)
|
||||
} else {
|
||||
@@ -47,8 +52,8 @@ export const connection = new Connection()
|
||||
|
||||
export const ConnectionContext = React.createContext(null);
|
||||
|
||||
export const useLeanClient = () => {
|
||||
const leanClient = connection.getLeanClient()
|
||||
export const useLeanClient = (gameId) => {
|
||||
const leanClient = connection.getLeanClient(gameId)
|
||||
const [leanClientStarted, setLeanClientStarted] = useState(leanClient.isStarted())
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -13,21 +13,26 @@ import ErrorPage from './ErrorPage';
|
||||
import Welcome from './components/Welcome';
|
||||
import Level from './components/Level';
|
||||
import { monacoSetup } from 'lean4web/client/src/monacoSetup';
|
||||
import { redirect } from 'react-router-dom';
|
||||
|
||||
monacoSetup()
|
||||
|
||||
const router = createHashRouter([
|
||||
{
|
||||
path: "/",
|
||||
loader: () => redirect("/game/testgame")
|
||||
},
|
||||
{
|
||||
path: "/game/:gameId",
|
||||
element: <App />,
|
||||
errorElement: <ErrorPage />,
|
||||
children: [
|
||||
{
|
||||
path: "/",
|
||||
path: "/game/:gameId",
|
||||
element: <Welcome />,
|
||||
},
|
||||
{
|
||||
path: "/world/:worldId/level/:levelId",
|
||||
path: "/game/:gameId/world/:worldId/level/:levelId",
|
||||
element: <Level />,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -38,13 +38,13 @@ interface Doc {
|
||||
|
||||
|
||||
const customBaseQuery = async (
|
||||
args : {method: string, params?: any},
|
||||
args : {game: string, method: string, params?: any},
|
||||
{ signal, dispatch, getState, extra },
|
||||
extraOptions
|
||||
) => {
|
||||
try {
|
||||
const connection : Connection = extra.connection
|
||||
let leanClient = await connection.startLeanClient()
|
||||
let leanClient = await connection.startLeanClient(args.game)
|
||||
console.log(`Sending request ${args.method}`)
|
||||
let res = await leanClient.sendRequest(args.method, args.params)
|
||||
console.log('Received response', res)
|
||||
@@ -59,14 +59,14 @@ export const apiSlice = createApi({
|
||||
reducerPath: 'gameApi',
|
||||
baseQuery: customBaseQuery,
|
||||
endpoints: (builder) => ({
|
||||
getGameInfo: builder.query<GameInfo, void>({
|
||||
query: () => {return {method: 'info', params: {}}},
|
||||
getGameInfo: builder.query<GameInfo, {game: string}>({
|
||||
query: ({game}) => {return {game, method: 'info', params: {}}},
|
||||
}),
|
||||
loadLevel: builder.query<LevelInfo, {world: string, level: number}>({
|
||||
query: ({world, level}) => {return {method: "loadLevel", params: {world, level}}},
|
||||
loadLevel: builder.query<LevelInfo, {game: string, world: string, level: number}>({
|
||||
query: ({game, world, level}) => {return {game, method: "loadLevel", params: {world, level}}},
|
||||
}),
|
||||
loadDoc: builder.query<Doc, {name: string, type: "lemma"|"tactic"}>({
|
||||
query: ({name, type}) => {return {method: "loadDoc", params: {name, type}}},
|
||||
loadDoc: builder.query<Doc, {game: string, name: string, type: "lemma"|"tactic"}>({
|
||||
query: ({game, name, type}) => {return {game, method: "loadDoc", params: {name, type}}},
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user