Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP progress on world overviews #129

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion client/src/components/inventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faLock, faBan } from '@fortawesome/free-solid-svg-icons'
import { GameIdContext } from '../app';
import Markdown from './markdown';
import { useLoadDocQuery, InventoryTile, LevelInfo, InventoryOverview, useLoadInventoryOverviewQuery } from '../state/api';
import { useLoadDocQuery, InventoryTile, LevelInfo, InventoryOverview, useLoadInventoryOverviewQuery, WorldOverview } from '../state/api';
import { selectDifficulty, selectInventory } from '../state/progress';
import { store } from '../state/store';
import { useSelector } from 'react-redux';
Expand Down Expand Up @@ -37,6 +37,79 @@ export function Inventory({levelInfo, openDoc, enableAll=false} :
)
}

export function OverviewInventory({data, openDoc, enableAll=false, showOverview=true} :
{
data: WorldOverview[],
openDoc: (props: {name: string, type: string}) => void,
enableAll?: boolean,
showOverview?: boolean
}) {

const gameId = React.useContext(GameIdContext)
const difficulty = useSelector(selectDifficulty(gameId))
const [tab, setTab] = useState<string>()
let inv: string[] = selectInventory(gameId)(store.getState())

let levelInfo : InventoryOverview = {
tactics : data ? data.map(world => (world.tactics.map(tile => inv.includes(tile.name) ? {...tile, locked: false} : tile))).flat() : [],
lemmas : data ? data.map(world => (world.lemmas.map(tile => inv.includes(tile.name) ? {...tile, locked: false} : tile))).flat() : [],
definitions : data ? data.map(world => (world.definitions.map(tile => inv.includes(tile.name) ? {...tile, locked: false} : tile))).flat() : [],
lemmaTab: null,
}

return (
<>
<Inventory levelInfo={levelInfo} openDoc={openDoc} enableAll={enableAll}/>
{showOverview &&
<div className="inventory">
{data && <>
<h2>Overviews</h2>
<div className="tab-bar">
{data.map(world => {
return <div key={`category-${world.world}`} className={`tab ${world.world == tab ? "active": ""}`} onClick={() => { setTab((tab == world.world) ? null : world.world) }}>{world.world}</div>
})}
</div>
<div className="inventory-list">
{/* TODO: a bit hacky, do we need to redesign inventory completely and provide it in a better order? */}
{data.map(world => {
if (world.world == tab) {
return [
...world.tactics.map(item => {
return <InventoryItem
key={`${world.world}-${item.name}`}
showDoc={() => {openDoc({name: item.name, type: 'Tactic'})}}
name={item.name} displayName={item.displayName}
locked={difficulty > 0 ? !inv.includes(item.name) : false}
disabled={item.disabled} newly={false} enableAll={enableAll} />
}),
...world.lemmas.map(item => {
return <InventoryItem
key={`${world.world}-${item.name}`}
showDoc={() => {openDoc({name: item.name, type: 'Lemma'})}}
name={item.name} displayName={item.displayName}
locked={difficulty > 0 ? item.locked : false}
disabled={item.disabled} newly={false} enableAll={enableAll} />
}),
...world.definitions.map(item => {
return <InventoryItem
key={`${world.world}-${item.name}`}
showDoc={() => {openDoc({name: item.name, type: 'Definition'})}}
name={item.name} displayName={item.displayName}
locked={difficulty > 0 ? item.locked : false}
disabled={item.disabled} newly={false} enableAll={enableAll} />
})
]
}
})}
</div>
</>}
</div>
}
</>

)
}

function InventoryList({items, docType, openDoc, defaultTab=null, level=undefined, enableAll=false} :
{
items: InventoryTile[],
Expand Down Expand Up @@ -144,3 +217,21 @@ export function InventoryPanel({levelInfo, visible = true}) {
}
</div>
}

/** The panel (on the welcome page) showing the user's inventory with tactics, definitions, and lemmas */
export function InventoryOverviewPanel({data, visible = true, showOverview=true} : {data : WorldOverview[], visible?: boolean, showOverview?: boolean}) {
const gameId = React.useContext(GameIdContext)

// The inventory is overlayed by the doc entry of a clicked item
const [inventoryDoc, setInventoryDoc] = useState<{name: string, type: string}>(null)
// Set `inventoryDoc` to `null` to close the doc
function closeInventoryDoc() {setInventoryDoc(null)}

return <div className={`column inventory-panel ${visible ? '' : 'hidden'}`}>
{inventoryDoc ?
<Documentation name={inventoryDoc.name} type={inventoryDoc.type} handleClose={closeInventoryDoc}/>
:
<OverviewInventory data={data} openDoc={setInventoryDoc} enableAll={true} showOverview={showOverview}/>
}
</div>
}
4 changes: 2 additions & 2 deletions client/src/components/level.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { changedSelection, codeEdited, selectCode, selectSelections, selectCompl
import { store } from '../state/store'
import { Button } from './button'
import Markdown from './markdown'
import {InventoryPanel} from './inventory'
import {InventoryOverviewPanel, InventoryPanel} from './inventory'
import { hasInteractiveErrors } from './infoview/typewriter'
import { DeletedChatContext, InputModeContext, MobileContext, MonacoEditorContext,
ProofContext, ProofStep, SelectionContext, WorldLevelIdContext } from './infoview/context'
Expand Down Expand Up @@ -498,7 +498,7 @@ function Introduction({impressum, setImpressum}) {
}

</div>
<InventoryPanel levelInfo={inventory?.data} />
<InventoryOverviewPanel data={inventory?.data} />
</Split>
}

Expand Down
6 changes: 3 additions & 3 deletions client/src/components/welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { changedOpenedIntro, selectOpenedIntro } from '../state/progress'
import { useGetGameInfoQuery, useLoadInventoryOverviewQuery } from '../state/api'
import { Button } from './button'
import { MobileContext } from './infoview/context'
import { InventoryPanel } from './inventory'
import { InventoryOverviewPanel, InventoryPanel } from './inventory'
import { ErasePopup } from './popup/erase'
import { InfoPopup } from './popup/game_info'
import { PrivacyPolicyPopup } from './popup/privacy_policy'
Expand Down Expand Up @@ -111,15 +111,15 @@ function Welcome() {
<WorldTreePanel worlds={gameInfo.data?.worlds} worldSize={gameInfo.data?.worldSize}
rulesHelp={rulesHelp} setRulesHelp={setRulesHelp} />
:
<InventoryPanel levelInfo={inventory?.data} />
<InventoryOverviewPanel data={inventory?.data} />
)}
</div>
:
<Split className="welcome" minSize={0} snapOffset={200} sizes={[25, 50, 25]}>
<IntroductionPanel introduction={gameInfo.data?.introduction} setPageNumber={setPageNumber} />
<WorldTreePanel worlds={gameInfo.data?.worlds} worldSize={gameInfo.data?.worldSize}
rulesHelp={rulesHelp} setRulesHelp={setRulesHelp} />
<InventoryPanel levelInfo={inventory?.data} />
<InventoryOverviewPanel data={inventory?.data} />
</Split>
}
</div>
Expand Down
9 changes: 8 additions & 1 deletion client/src/state/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ interface Doc {
category: string,
}

export interface WorldOverview {
world: string
tactics: InventoryTile[]
lemmas: InventoryTile[]
definitions: InventoryTile[]
}

// Define a service using a base URL and expected endpoints
export const apiSlice = createApi({
reducerPath: 'gameApi',
Expand All @@ -82,7 +89,7 @@ export const apiSlice = createApi({
loadLevel: builder.query<LevelInfo, {game: string, world: string, level: number}>({
query: ({game, world, level}) => `${game}/level__${world}__${level}.json`,
}),
loadInventoryOverview: builder.query<InventoryOverview, {game: string}>({
loadInventoryOverview: builder.query<WorldOverview[], {game: string}>({
query: ({game}) => `${game}/inventory.json`,
}),
loadDoc: builder.query<Doc, {game: string, name: string, type: "lemma"|"tactic"}>({
Expand Down
7 changes: 7 additions & 0 deletions server/GameServer/EnvExtensions.lean
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ structure GameLevel where
image : String := default
deriving Inhabited, Repr

structure WorldOverview where
world: Name
tactics: Array InventoryTile := default
definitions: Array InventoryTile := default
lemmas: Array InventoryTile := default
deriving FromJson, ToJson, Inhabited, Repr

/-- Json-encodable version of `GameLevel`
Fields:
- description: Lemma in mathematical language.
Expand Down
48 changes: 48 additions & 0 deletions server/GameServer/Game.lean
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,54 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
fw.stdin.writeLspMessage msg

return true
| Message.request id "loadInventoryOverview" _ =>
let s ← get
let some game ← getGame? s.game
| return false

let mut overview : Array WorldOverview := #[]
for ⟨worldId, world⟩ in game.worlds.nodes.toList do
let mut tactics : Array InventoryTile := #[]
let mut theorems : Array InventoryTile := #[]
let mut definitions : Array InventoryTile := #[]
for ⟨_levelId, lvl⟩ in world.levels.toList do
tactics := tactics ++ lvl.tactics.tiles.filterMap (fun tile => match tile.new with
| true => some {tile with new := false, locked := true, disabled := false}
| false => none)
theorems := theorems ++ lvl.lemmas.tiles.filterMap (fun tile => match tile.new with
| true => some {tile with new := false, locked := true, disabled := false}
| false => none)
definitions := definitions ++ lvl.definitions.tiles.filterMap (fun tile => match tile.new with
| true => some {tile with new := false, locked := true, disabled := false}
| false => none)
overview := overview.push {
world := worldId,
tactics := tactics,
lemmas := theorems,
definitions := definitions }
let c ← read
c.hOut.writeLspResponse ⟨id, ToJson.toJson overview⟩
return true
-- -- All Levels have the same tiles, so we just load them from level 1 of an arbitrary world
-- -- and reset `new`, `disabled` and `unlocked`.
-- -- Note: as we allow worlds without any levels (for developing), we might need
-- -- to try until we find the first world with levels.
-- for ⟨worldId, _⟩ in game.worlds.nodes.toList do
-- let some lvl ← getLevel? {game := s.game, world := worldId, level := 1}
-- | do continue
-- let inventory : InventoryOverview := {
-- tactics := lvl.tactics.tiles.map
-- ({ · with locked := true, disabled := false, new := false }),
-- lemmas := lvl.lemmas.tiles.map
-- ({ · with locked := true, disabled := false, new := false }),
-- definitions := lvl.definitions.tiles.map
-- ({ · with locked := true, disabled := false, new := false }),
-- lemmaTab := none
-- }
-- let c ← read
-- c.hOut.writeLspResponse ⟨id, ToJson.toJson inventory⟩
-- return true
-- return false
| _ => return false
| _ => return false

Expand Down