diff --git a/client/src/app.tsx b/client/src/app.tsx
index d037446..9c15c84 100644
--- a/client/src/app.tsx
+++ b/client/src/app.tsx
@@ -49,10 +49,18 @@ function App() {
// mobile only: game intro should only be shown once and skipped afterwards
useEffect(() => {
- if (openedIntro && !worldId && page == 0) {
+ if (worldId) {
+ console.log('setting page to 1')
setPage(1)
+ } else {
+ if (openedIntro && page == 0) {
+ console.log('setting page to 1')
+ setPage(1)
+ } else {
+ // setPage(0)
+ }
}
- }, [openedIntro])
+ }, [openedIntro, worldId, levelId])
// option to pass language as `?lang=de` in the URL
useEffect(() => {
@@ -96,11 +104,9 @@ function App() {
{t("Line")} {diag.range.start.line}, {t("Character")} {diag.range.start.character}
diff --git a/client/src/components/inventory.tsx b/client/src/components/inventory.tsx index 964752d..77cc599 100644 --- a/client/src/components/inventory.tsx +++ b/client/src/components/inventory.tsx @@ -1,92 +1,122 @@ -import * as React from 'react'; -import { useState, useEffect } from 'react'; +import * as React from 'react' +import { useState, useEffect, createContext, useContext } from 'react'; import '../css/inventory.css' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faLock, faBan, faCheck } from '@fortawesome/free-solid-svg-icons' +import { faLock, faBan, faCheck, faXmark } from '@fortawesome/free-solid-svg-icons' import { faClipboard } from '@fortawesome/free-regular-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, useLoadLevelQuery } from '../state/api'; import { selectDifficulty, selectInventory } from '../state/progress'; import { store } from '../state/store'; import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { t } from 'i18next'; import { WorldLevelIdContext } from './infoview/context'; +import { NavButton } from './navigation'; +import { LoadingIcon } from './utils'; + + +/** Context which manages the inventory */ +const InventoryContext = createContext<{ + theoremTab: string, + setTheoremTab: React.Dispatch>, + categoryTab: "tactic"|"theorem"|"definition", + setCategoryTab: React.Dispatch >, + docTile: any, + setDocTile: React.Dispatch > +}>({ + theoremTab: null, + setTheoremTab: () => {}, + categoryTab: "tactic", + setCategoryTab: () => {}, + docTile: null, + setDocTile: () => {} +}) + + +/** + */ +function InventoryItem({item, name, displayName, locked, disabled, newly, showDoc, recent=false }) { + const icon = locked ? : + disabled ? : item.st + const className = locked ? "locked" : disabled ? "disabled" : newly ? "new" : "" + // Note: This is somewhat a hack as the statement of lemmas comes currently in the form + // `Namespace.statement_name (x y : Nat) : some type` + const title = locked ? t("Not unlocked yet") : + disabled ? t("Not available in this level") : (item.altTitle ? item.altTitle.substring(item.altTitle.indexOf(' ') + 1) : '') -export function Inventory({levelInfo, openDoc, lemmaTab, setLemmaTab, enableAll=false} : - { - levelInfo: LevelInfo|InventoryOverview, - openDoc: (props: {name: string, type: string}) => void, - lemmaTab: any, - setLemmaTab: any, - enableAll?: boolean, - }) { - const { t } = useTranslation() + const { gameId } = React.useContext(GameIdContext) + const difficulty = useSelector(selectDifficulty(gameId)) - // TODO: this state should be preserved when loading a different level. - const [tab, setTab] = useState<"tactic"|"theorem"|"definition">("theorem") + // local state to show checkmark after pressing the copy button + const [copied, setCopied] = useState(false) - let newTheorems = levelInfo?.lemmas?.filter(item => item.new).length > 0 - let newTactics = levelInfo?.tactics?.filter(item => item.new).length > 0 - let newDefinitions = levelInfo?.definitions?.filter(item => item.new).length > 0 + const handleClick = () => { + // if ((difficulty == 0) || !locked) { + showDoc() + // } + } - return ( - ---- { (tab == "theorem") && levelInfo?.lemmas && -{ setTab("theorem") }}>{t("Theorems")}-{ setTab("tactic") }}>{t("Tactics")}-{ setTab("definition") }}>{t("Definitions")}-- } - { (tab == "tactic") && levelInfo?.tactics && - - } - { (tab == "definition") && levelInfo?.definitions && - - } + const copyItemName = (ev) => { + navigator.clipboard.writeText(displayName) + setCopied(true) + setInterval(() => { + setCopied(false) + }, 3000); + ev.stopPropagation() + } + + return + {icon} {displayName} +} -function InventoryList({items, docType, openDoc, tab=null, setTab=undefined, level=undefined, enableAll=false} : + +function InventoryList({ items, tab=null, setTab=()=>{} } : { items: InventoryTile[], - docType: string, - openDoc(props: {name: string, type: string}): void, - tab?: any, - setTab?: any, - level?: LevelInfo|InventoryOverview, - enableAll?: boolean, + tab?: string, + setTab?: React.Dispatch+ {copied ?- ) +: } > }) { - // 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 {gameId} = React.useContext(GameIdContext) - const {worldId, levelId} = React.useContext(WorldLevelIdContext) + const { gameId, worldId, levelId } = React.useContext(GameIdContext) + const { setDocTile, categoryTab, setCategoryTab } = useContext(InventoryContext) const difficulty = useSelector(selectDifficulty(gameId)) - const categorySet = new Set () - for (let item of items) { - categorySet.add(item.category) - } - const categories = Array.from(categorySet).sort() + const inventory: string[] = selectInventory(gameId)(store.getState()) + + const [categories, setCategories] = useState >([]) + const [modifiedItems, setModifiedItems] = useState >([]) + const [recentItems, setRecentItems] = useState >([]) + + + useEffect(() => { + const categorySet = new Set () + + if (!items) {return} + for (let item of items) { + categorySet.add(item.category) + } + setCategories(Array.from(categorySet).sort()) - // Add inventory items from local store as unlocked. - // Items are unlocked if they are in the local store, or if the server says they should be - // given the dependency graph. (OR-connection) (TODO: maybe add different logic for different - // modi) - let inv: string[] = selectInventory(gameId)(store.getState()) - let modifiedItems : InventoryTile[] = items.map(tile => inv.includes(tile.name) ? {...tile, locked: false} : tile) + // Add inventory items from local store as unlocked. + // Items are unlocked if they are in the local store, or if the server says they should be + // given the dependency graph. (OR-connection) (TODO: maybe add different logic for different + // modi) + let _modifiedItems : InventoryTile[] = items?.map(tile => inventory.includes(tile.name) ? {...tile, locked: false} : tile) + setModifiedItems(_modifiedItems) + // Item(s) proved in the preceeding level + setRecentItems(_modifiedItems.filter(x => x.world == worldId && x.level == levelId - 1)) - // Item(s) proved in the preceeding level - let recentItems = modifiedItems.filter(x => x.world == worldId && x.level == levelId - 1) + }, [items, inventory]) return <> - {categories.length > 1 && + { categories.length > 1 && {categories.map((cat) => { let hasNew = modifiedItems.filter(item => item.new && (cat == item.category)).length > 0 @@ -97,91 +127,109 @@ function InventoryList({items, docType, openDoc, tab=null, setTab=undefined, lev {[...modifiedItems].sort( // For lemas, sort entries `available > disabled > locked` // otherwise alphabetically - (x, y) => +(docType == "Lemma") * (+x.locked - +y.locked || +x.disabled - +y.disabled) || x.displayName.localeCompare(y.displayName) + (x, y) => +(categoryTab == "theorem") * (+x.locked - +y.locked || +x.disabled - +y.disabled) || x.displayName.localeCompare(y.displayName) ).filter(item => !item.hidden && ((tab ?? categories[0]) == item.category)).map((item, i) => { return> } -function InventoryItem({item, name, displayName, locked, disabled, newly, showDoc, recent=false, enableAll=false}) { - const icon = locked ?{openDoc({name: item.name, type: docType})}} + showDoc={() => {setDocTile(item)}} name={item.name} displayName={item.displayName} locked={difficulty > 0 ? item.locked : false} disabled={item.disabled} recent={recentItems.map(x => x.name).includes(item.name)} - newly={item.new} enableAll={enableAll} /> + newly={item.new} /> }) } : - disabled ? : item.st - const className = locked ? "locked" : disabled ? "disabled" : newly ? "new" : "" - // Note: This is somewhat a hack as the statement of lemmas comes currently in the form - // `Namespace.statement_name (x y : Nat) : some type` - const title = locked ? t("Not unlocked yet") : - disabled ? t("Not available in this level") : (item.altTitle ? item.altTitle.substring(item.altTitle.indexOf(' ') + 1) : '') - const [copied, setCopied] = useState(false) - const handleClick = () => { - if (enableAll || !locked) { - showDoc() - } - } +/** The `Inventory` shows all items present in the game sorted by item type. */ +export function Inventory () { + const { t } = useTranslation() - const copyItemName = (ev) => { - navigator.clipboard.writeText(displayName) - setCopied(true) - setInterval(() => { - setCopied(false) - }, 3000); - ev.stopPropagation() + const { gameId, worldId, levelId } = React.useContext(GameIdContext) + const levelInfo = useLoadLevelQuery({game: gameId, world: worldId, level: levelId}) + + let { theoremTab, setTheoremTab, categoryTab, setCategoryTab } = useContext(InventoryContext) + + /** Helper function to find if a list of tiles comprises any new elements. */ + function containsNew(tiles: InventoryTile[]) { + console.log(tiles) + return tiles?.filter(item => item.new).length > 0 } -return - {icon} {displayName} --- {copied ?+ ) } -export function Documentation({name, type, handleClose}) { - const {gameId} = React.useContext(GameIdContext) - const doc = useLoadDocQuery({game: gameId, type: type, name: name}) +/** The `documentation` */ +export function Documentation() { + const { gameId } = React.useContext(GameIdContext) + // const docEntry = useLoadDocQuery({game: gameId, type: type, name: name}) + let { docTile, setDocTile } = useContext(InventoryContext) + + const docEntry = useLoadDocQuery({game: gameId, name: docTile.name}) + + + // Set `inventoryDoc` to `null` to close the doc + function closeInventoryDoc() { setDocTile(null) } return: } + return ( + + { levelInfo.data ? <> +-++ { (categoryTab == "theorem") && +{ setCategoryTab("theorem") }}>{t("Theorems")}+{ setCategoryTab("tactic") }}>{t("Tactics")}+{ setCategoryTab("definition") }}>{t("Definitions")}++ } + { (categoryTab == "tactic") && + + } + { (categoryTab == "definition") && + + } + > : } - -} -/** The panel (on the welcome page) showing the user's inventory with tactics, definitions, and lemmas */ -export function InventoryPanel({levelInfo, visible = true}) { - const {gameId} = React.useContext(GameIdContext) - - const [lemmaTab, setLemmaTab] = useState(levelInfo?.lemmaTab) +/** The panel showing the user's inventory with tactics, definitions, and lemmas */ +export function InventoryPanel({visible = true}) { + const {gameId, worldId, levelId} = React.useContext(GameIdContext) + const levelInfo = useLoadLevelQuery({game: gameId, world: worldId, level: levelId}) + const inventory = useLoadInventoryOverviewQuery({game: gameId}) + const [theoremTab, setTheoremTab] = useState{doc.data?.displayName}
-- {/*
{doc.data?.statement}
docstring: {doc.data?.docstring}
*/} -{t(doc.data?.content, {ns: gameId})} ++ {docTile.data?.displayName}
++
{docEntry.data?.statement}
{t(docEntry.data?.content, {ns: gameId})} (null) + const [categoryTab, setCategoryTab] = useState<"tactic"|"theorem"|"definition">('tactic') // 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)} + const [docTile, setDocTile] = useState (null) - useEffect(() => { + 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 (levelInfo?.lemmaTab) { - setLemmaTab(levelInfo?.lemmaTab) + if (levelInfo?.data?.lemmaTab) { + setTheoremTab(levelInfo?.data?.lemmaTab) }}, [levelInfo]) return - {inventoryDoc ? -} + +// HERE: next up: locked items should not be disabled! diff --git a/client/src/components/landing_page.tsx b/client/src/components/landing_page.tsx index c14e3df..42a3649 100644 --- a/client/src/components/landing_page.tsx +++ b/client/src/components/landing_page.tsx @@ -109,23 +109,26 @@ function LandingPage() {+ + {docTile ? + : - + } + - {allTiles.filter(x => x != null).length == 0 ? -+-
- : lean4gameConfig.allGames.map((id, i) => ( -- No Games loaded. Use http://localhost:3000/#/g/local/FOLDER to open a - game directly from a local folder. - -- )) - } - + + + {allTiles.filter(x => x != null).length == 0 ? +++
+ : lean4gameConfig.allGames.map((id, i) => ( ++ No Games loaded. Use http://localhost:3000/#/g/local/FOLDER to open a + game directly from a local folder. + ++ )) + } + } -function PlayableLevel({impressum, setImpressum, privacy, setPrivacy, toggleInfo, togglePreferencesPopup}) { +export function PlayableLevel() { let { t } = useTranslation() const codeviewRef = useRef{t("Development notes")}
diff --git a/client/src/components/level.tsx b/client/src/components/level.tsx index 8dbd0a2..0a61790 100644 --- a/client/src/components/level.tsx +++ b/client/src/components/level.tsx @@ -53,12 +53,13 @@ import { InfoPopup } from './popup/info' import { PreferencesPopup } from './popup/preferences' import { useTranslation } from 'react-i18next' import i18next from 'i18next' +import { ChatButtons } from './chat' monacoSetup() -function Level() { - const params = useParams() +export function Level({visible = true}) { + // const params = useParams() // const levelId = parseInt(params.levelId) // const worldId = params.worldId @@ -87,11 +88,11 @@ function Level() { useEffect(() => {}, []) - return- {levelId == 0 ? - + return: - } - +} function ChatPanel({lastLevel, visible = true}) { @@ -199,7 +200,7 @@ function ChatPanel({lastLevel, visible = true}) { } -function ExercisePanel({codeviewRef, visible=true}: {codeviewRef: React.MutableRefObject+ ++ , visible?: boolean}) { +export function ExercisePanel({codeviewRef, visible=true}: {codeviewRef: React.MutableRefObject , visible?: boolean}) { const {gameId} = React.useContext(GameIdContext) const {worldId, levelId} = useContext(WorldLevelIdContext) const level = useLoadLevelQuery({game: gameId, world: worldId, level: levelId}) @@ -211,7 +212,7 @@ function ExercisePanel({codeviewRef, visible=true}: {codeviewRef: React.MutableR (null) const {gameId} = React.useContext(GameIdContext) @@ -248,9 +249,9 @@ function PlayableLevel({impressum, setImpressum, privacy, setPrivacy, toggleInfo const [typewriterInput, setTypewriterInput] = useState("") const lastLevel = levelId >= gameInfo.data?.worldSize[worldId] - // impressum pop-up - function toggleImpressum() {setImpressum(!impressum)} - function togglePrivacy() {setPrivacy(!privacy)} + // // impressum pop-up + // function toggleImpressum() {setImpressum(!impressum)} + // function togglePrivacy() {setPrivacy(!privacy)} // When clicking on an inventory item, the inventory is overlayed by the item's doc. // If this state is set to a pair `(name, type)` then the according doc will be open. @@ -424,24 +425,7 @@ function PlayableLevel({impressum, setImpressum, privacy, setPrivacy, toggleInfo toggleInfo={toggleInfo} togglePreferencesPopup={togglePreferencesPopup} /> */} - {mobile? - // TODO: This is copied from the `Split` component below... - <> - -- > - : -- - - - } +- - - @@ -451,6 +435,11 @@ function PlayableLevel({impressum, setImpressum, privacy, setPrivacy, toggleInfo > } + // + // + function IntroductionPanel({gameInfo}) { let { t } = useTranslation() const {gameId} = React.useContext(GameIdContext) @@ -466,21 +455,14 @@ function IntroductionPanel({gameInfo}) { hint={{text: t, hidden: false, rawText: t, varNames: []}} step={0} selected={null} toggleSelection={undefined} /> ))}+ // + // - {gameInfo.data?.worldSize[worldId] == 0 ? - : - - } -+