add command LemmaTab to specify default tab when loading level

pull/54/head
Jon Eugster 2 years ago
parent 2b1384d6a6
commit 3cbe336ccb

@ -4,14 +4,14 @@ import './inventory.css'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons' import { faLock, faLockOpen, faBook, faHammer, faBan } from '@fortawesome/free-solid-svg-icons'
import Markdown from './Markdown'; import Markdown from './Markdown';
import { useLoadDocQuery, ComputedInventoryItem } from '../state/api'; import { useLoadDocQuery, ComputedInventoryItem, LevelInfo } from '../state/api';
import { GameIdContext } from '../App'; import { GameIdContext } from '../App';
export function Inventory({ tactics, lemmas, definitions, setInventoryDoc } : export function Inventory({levelInfo, setInventoryDoc } :
{lemmas: ComputedInventoryItem[], {
tactics: ComputedInventoryItem[], levelInfo: LevelInfo,
definitions: ComputedInventoryItem[], setInventoryDoc: (inventoryDoc: {name: string, type: string}) => void,
setInventoryDoc: (inventoryDoc: {name: string, type: string}) => void}) { }) {
function openDoc(name, type) { function openDoc(name, type) {
setInventoryDoc({name, type}) setInventoryDoc({name, type})
@ -22,18 +22,28 @@ export function Inventory({ tactics, lemmas, definitions, setInventoryDoc } :
{/* TODO: Click on Tactic: show info {/* TODO: Click on Tactic: show info
TODO: click on paste icon -> paste into command line */} TODO: click on paste icon -> paste into command line */}
<h2>Tactics</h2> <h2>Tactics</h2>
<InventoryList items={tactics} docType="Tactic" openDoc={openDoc} /> <InventoryList items={levelInfo?.tactics} docType="Tactic" openDoc={openDoc} />
<h2>Definitions</h2> <h2>Definitions</h2>
<InventoryList items={definitions} docType="Definition" openDoc={openDoc} /> <InventoryList items={levelInfo?.definitions} docType="Definition" openDoc={openDoc} />
<h2>Lemmas</h2> <h2>Lemmas</h2>
<InventoryList items={lemmas} docType="Lemma" openDoc={openDoc} /> <InventoryList items={levelInfo?.lemmas} docType="Lemma" openDoc={openDoc}
defaultTab={levelInfo?.lemmaTab} level={levelInfo}/>
</div> </div>
) )
} }
function InventoryList({items, docType, openDoc} : {items: ComputedInventoryItem[], docType: string, openDoc(name: string, type: string): void}) { function InventoryList({items, docType, openDoc, defaultTab=null, level=undefined} :
{
items: ComputedInventoryItem[],
docType: string,
openDoc(name: string, type: string): void,
defaultTab? : string,
level? : LevelInfo,
}) {
// TODO: `level` is only used in the `useEffect` below to check if a new level has
// been loaded. Is there a better way to observe this?
const categorySet = new Set<string>() const categorySet = new Set<string>()
for (let item of items) { for (let item of items) {
@ -41,20 +51,27 @@ function InventoryList({items, docType, openDoc} : {items: ComputedInventoryItem
} }
const categories = Array.from(categorySet).sort() const categories = Array.from(categorySet).sort()
const [tab, setTab] = useState(categories[0]); const [tab, setTab] = useState(defaultTab);
useEffect(() => {
// If the level specifies `LemmaTab "Nat"`, we switch to this tab on loading.
// `defaultTab` is `null` or `undefined` otherwise, in which case we don't want to switch.
if (defaultTab) {
setTab(defaultTab)
}}, [level])
return <> return <>
{categories.length > 1 && {categories.length > 1 &&
<div className="tab-bar"> <div className="tab-bar">
{categories.map((cat) => {categories.map((cat) =>
<div className={`tab ${cat == tab ? "active": ""}`} onClick={() => { setTab(cat) }}>{cat}</div>)} <div className={`tab ${cat == (tab ?? categories[0]) ? "active": ""}`} onClick={() => { setTab(cat) }}>{cat}</div>)}
</div>} </div>}
<div className="inventory-list"> <div className="inventory-list">
{ [...items].sort( { [...items].sort(
// sort unavailable tactics/lemmas/def to the back. // Sort entries `available > disabled > locked`.
(x, y) => +x.locked - +y.locked || +x.disabled - +y.disabled (x, y) => +x.locked - +y.locked || +x.disabled - +y.disabled
).map(item => { ).map(item => {
if (tab == item.category) { if ((tab ?? categories[0]) == item.category) {
return <InventoryItem key={item.name} showDoc={() => {openDoc(item.name, docType)}} return <InventoryItem key={item.name} showDoc={() => {openDoc(item.name, docType)}}
name={item.name} displayName={item.displayName} locked={item.locked} disabled={item.disabled} /> name={item.name} displayName={item.displayName} locked={item.locked} disabled={item.disabled} />
} }

@ -218,8 +218,7 @@ function PlayableLevel({worldId, levelId}) {
</div> </div>
<div className="inventory-panel"> <div className="inventory-panel">
{!level.isLoading && {!level.isLoading &&
<Inventory tactics={level?.data?.tactics} lemmas={level?.data?.lemmas} <Inventory levelInfo={level?.data} setInventoryDoc={setInventoryDoc} />}
definitions={level?.data?.definitions} setInventoryDoc={setInventoryDoc} />}
</div> </div>
<div className="doc-panel"> <div className="doc-panel">
{inventoryDoc && <Documentation name={inventoryDoc.name} type={inventoryDoc.type} />} {inventoryDoc && <Documentation name={inventoryDoc.name} type={inventoryDoc.type} />}

@ -1,5 +1,5 @@
.inventory { .inventory {
padding: 0 1em; padding: 0 1em 1em 1em;
} }
.inventory h2 { .inventory h2 {

@ -18,7 +18,7 @@ export interface ComputedInventoryItem {
locked: boolean locked: boolean
} }
interface LevelInfo { export interface LevelInfo {
title: null|string, title: null|string,
introduction: null|string, introduction: null|string,
conclusion: null|string, conclusion: null|string,
@ -28,6 +28,7 @@ interface LevelInfo {
definitions: ComputedInventoryItem[], definitions: ComputedInventoryItem[],
descrText: null|string, descrText: null|string,
descrFormat: null|string, descrFormat: null|string,
lemmaTab: null|string,
} }
interface Doc { interface Doc {

@ -400,6 +400,12 @@ elab "LemmaDoc" name:ident "as" displayName:str "in" category:str content:str :
category := category.getString, category := category.getString,
content := content.getString }) content := content.getString })
/-- Define which tab of Lemmas is opened by default. Usage: `LemmaTab "Nat"`.
If omitted, the first tab will be open by default. -/
elab "LemmaTab" category:str : command =>
modifyCurLevel fun level => pure {level with lemmaTab := category.getString}
/-- Declare lemmas that are introduced by this level. -/ /-- Declare lemmas that are introduced by this level. -/
elab "NewLemma" args:ident* : command => do elab "NewLemma" args:ident* : command => do
let names := args.map (·.getId) let names := args.map (·.getId)

@ -157,6 +157,7 @@ structure GameLevel where
tactics: InventoryInfo := default tactics: InventoryInfo := default
definitions: InventoryInfo := default definitions: InventoryInfo := default
lemmas: InventoryInfo := default lemmas: InventoryInfo := default
lemmaTab: Option String := none
hints: Array GoalHintEntry := default hints: Array GoalHintEntry := default
/-- The statement in Lean. -/ /-- The statement in Lean. -/
goal : TSyntax `Lean.Parser.Command.declSig := default goal : TSyntax `Lean.Parser.Command.declSig := default

@ -49,6 +49,7 @@ structure LevelInfo where
conclusion : String conclusion : String
descrText : String := "" descrText : String := ""
descrFormat : String := "" descrFormat : String := ""
lemmaTab : Option String
deriving ToJson, FromJson deriving ToJson, FromJson
structure LoadLevelParams where structure LoadLevelParams where
@ -128,7 +129,8 @@ partial def handleServerEvent (ev : ServerEvent) : GameServerM Bool := do
descrText := lvl.descrText, descrText := lvl.descrText,
descrFormat := lvl.descrFormat --toExpr <| format (lvl.goal.raw) --toString <| Syntax.formatStx (lvl.goal.raw) --Syntax.formatStx (lvl.goal.raw) , -- TODO descrFormat := lvl.descrFormat --toExpr <| format (lvl.goal.raw) --toString <| Syntax.formatStx (lvl.goal.raw) --Syntax.formatStx (lvl.goal.raw) , -- TODO
introduction := lvl.introduction introduction := lvl.introduction
conclusion := lvl.conclusion } conclusion := lvl.conclusion
lemmaTab := lvl.lemmaTab }
c.hOut.writeLspResponse ⟨id, ToJson.toJson levelInfo⟩ c.hOut.writeLspResponse ⟨id, ToJson.toJson levelInfo⟩
return true return true
| Message.request id "loadDoc" params => | Message.request id "loadDoc" params =>

Loading…
Cancel
Save