collapsable side panel

pull/43/head
Jon Eugster 4 years ago
parent 513a6f0a3e
commit 802d748bf4

@ -7,9 +7,11 @@ import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import { Paper, Box, Typography, Accordion, AccordionSummary, AccordionDetails, Tabs, Tab } from '@mui/material';
import { Paper, Box, Typography, Accordion, AccordionSummary, AccordionDetails, Tabs, Tab, Divider, Button, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUpload, faArrowRotateRight, faChevronLeft, faChevronRight, faBook, faHammer } from '@fortawesome/free-solid-svg-icons'
function TacticDoc(props) {
return (
@ -54,8 +56,7 @@ function LemmaDocs({ lemmas }) {
}, [lemmas]);
return (
<Paper sx={{ px: 2, py: 1, mt: 2 }}>
<Typography variant="h5">Inventory</Typography>
<div>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={curCategory}
@ -64,20 +65,39 @@ function LemmaDocs({ lemmas }) {
</Tabs>
</Box>
{curCategory && categories.get(curCategory).map((lemma) => <LemmaDoc lemma={lemma} key={lemma.name} />)}
</Paper>
)
</div>)
}
function LeftPanel({ spells, inventory }) {
function LeftPanel({ spells, inventory, showSidePanel, setShowSidePanel }) {
return (
<Box>
{spells && spells.length > 0 &&
<Paper sx={{ px: 2, py: 1 }}>
<Typography variant="h5" sx={{ mb: 2 }}>Spell book</Typography>
{spells.map((spell) => <TacticDoc key={spell.name} tactic={spell} />)}
</Paper>}
{inventory && inventory.length > 0 && <LemmaDocs lemmas={inventory} />}
</Box>)
<List className="side">
<ListItem key="tactics" disablePadding sx={{ display: 'block' }}>
<ListItemButton sx={{ minHeight: 48, justifyContent: open ? 'initial' : 'center', px: 2.5 }} onClick={() => setShowSidePanel(true)}>
<ListItemIcon sx={{minWidth: 0, mr: open ? 3 : 'auto', justifyContent: 'center' }}>
<FontAwesomeIcon icon={faHammer}></FontAwesomeIcon>
</ListItemIcon>
<ListItemText primary="Tactics" sx={{ display: showSidePanel ? null : "none" }} />
</ListItemButton>
{spells && spells.length > 0 &&
<Paper sx={{ px: 2, py: 1, display: showSidePanel ? null : "none" }} elevation={0} >
{spells.map((spell) => <TacticDoc key={spell.name} tactic={spell} />)}
</Paper>}
</ListItem>
<ListItem key="lemmas" disablePadding sx={{ display: 'block' }}>
<ListItemButton sx={{ minHeight: 48, justifyContent: open ? 'initial' : 'center', px: 2.5 }} >
<ListItemIcon sx={{minWidth: 0, mr: open ? 3 : 'auto', justifyContent: 'center' }}>
<FontAwesomeIcon icon={faBook}></FontAwesomeIcon>
</ListItemIcon>
<ListItemText primary="Lemmas" sx={{ display: showSidePanel ? null : "none" }} />
</ListItemButton>
{inventory && inventory.length > 0 &&
<Paper sx={{ px: 2, py: 1, mt: 2, display: showSidePanel ? null : "none" }} elevation={0} >
<LemmaDocs lemmas={inventory} />
</Paper>}
</ListItem>
</List>
)
}
export default LeftPanel;

@ -7,7 +7,8 @@ import '@fontsource/roboto/700.css';
import ReactMarkdown from 'react-markdown';
import { MathJax } from "better-react-mathjax";
import { Link as RouterLink } from 'react-router-dom';
import { Box, Button, CircularProgress, FormControlLabel, FormGroup, Switch } from '@mui/material';
import { Box, Button, CircularProgress, FormControlLabel, FormGroup, Switch, IconButton } from '@mui/material';
import MuiDrawer from '@mui/material/Drawer';
import Grid from '@mui/material/Unstable_Grid2';
import LeftPanel from './LeftPanel';
import { LeanTaskGutter } from 'lean4web/client/src/editor/taskgutter';
@ -22,6 +23,82 @@ import { ConnectionContext } from '../connection';
import Infoview from './Infoview';
import { useParams } from 'react-router-dom';
import { useLoadLevelQuery } from '../game/api';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUpload, faArrowRotateRight, faChevronLeft, faChevronRight, faBook, faDownload } from '@fortawesome/free-solid-svg-icons'
import { styled, useTheme, Theme, CSSObject } from '@mui/material/styles';
import MuiAppBar, { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
import List from '@mui/material/List';
import CssBaseline from '@mui/material/CssBaseline';
import Typography from '@mui/material/Typography';
import Divider from '@mui/material/Divider';
import MenuIcon from '@mui/icons-material/Menu';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import InboxIcon from '@mui/icons-material/MoveToInbox';
import MailIcon from '@mui/icons-material/Mail';
/** Drawer Test */
const drawerWidth = 400;
const openedMixin = (theme: Theme): CSSObject => ({
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
overflowX: 'hidden',
});
const closedMixin = (theme: Theme): CSSObject => ({
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
overflowX: 'hidden',
width: `calc(${theme.spacing(7)} + 1px)`,
[theme.breakpoints.up('sm')]: {
width: `calc(${theme.spacing(8)} + 1px)`,
},
});
const DrawerHeader = styled('div')(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar,
}));
interface AppBarProps extends MuiAppBarProps {
open?: boolean;
}
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => prop !== 'open' })(
({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: 'nowrap',
boxSizing: 'border-box',
...(open && {
...openedMixin(theme),
'& .MuiDrawer-paper': openedMixin(theme),
}),
...(!open && {
...closedMixin(theme),
'& .MuiDrawer-paper': closedMixin(theme),
}),
}),
);
/** End Drawer Test */
@ -37,6 +114,14 @@ function Level() {
const infoviewRef = useRef<HTMLDivElement>(null)
const messagePanelRef = useRef<HTMLDivElement>(null)
const [showSidePanel, setShowSidePanel] = useState(true)
const toggleSidePanel = () => {
setShowSidePanel(!showSidePanel)
}
const theme = useTheme();
useEffect(() => {
// Scroll to top when loading a new level
messagePanelRef.current!.scrollTo(0,0)
@ -49,35 +134,43 @@ function Level() {
return <>
<Box style={level.isLoading ? null : {display: "none"}} display="flex" alignItems="center" justifyContent="center" sx={{ height: "calc(100vh - 64px)" }}><CircularProgress /></Box>
<Grid style={level.isLoading ? {display: "none"} : null} className="level" container sx={{ mt: 0, ml: 0, mr: 0 }} columnSpacing={{ xs: 1, sm: 2, md: 3 }}>
<Grid xs={4} className="doc-panel">
<LeftPanel spells={level?.data?.tactics} inventory={level?.data?.tactics} />
</Grid>
<Grid xs={9} sm={6} className="main-panel">
<div ref={messagePanelRef} className="message-panel">
<MathJax><ReactMarkdown>{level?.data?.introduction}</ReactMarkdown></MathJax>
</div>
<div className="exercise">
<h4>Aufgabe:</h4>
<MathJax><ReactMarkdown>{level?.data?.descrText}</ReactMarkdown></MathJax>
<div className="statement"><code>{level?.data?.descrFormat}</code></div>
<div ref={codeviewRef} className="codeview"></div>
</div>
</Grid>
<Grid xs={3} className="info-panel">
<Button disabled={levelId <= 1} component={RouterLink} to={`/world/${worldId}/level/${levelId - 1}`} sx={{ ml: 3, mt: 2, mb: 2 }} disableFocusRipple>Previous Level</Button>
<Button disabled={false} component={RouterLink} to={`/world/${worldId}/level/${levelId + 1}`} sx={{ ml: 3, mt: 2, mb: 2 }} disableFocusRipple>Next Level</Button>
<div style={{display: expertInfoview ? 'block' : 'none' }} ref={infoviewRef} className="infoview vscode-light"></div>
<div style={{display: expertInfoview ? 'none' : 'block' }}>
<Infoview leanClient={connection.getLeanClient()} editor={editor} editorApi={infoProvider?.getApi()} />
</div>
<FormGroup>
<FormControlLabel onChange={() => { setExpertInfoview(!expertInfoview) }} control={<Switch />} label="Expert mode" />
</FormGroup>
<Box style={level.isLoading ? {display: "none"} : null} display="flex" className="level" sx={{ mt: 0, ml: 0, mr: 0 }} >
<Drawer variant="permanent" open={showSidePanel} className="doc-panel">
<DrawerHeader>
</DrawerHeader>
<Divider />
<IconButton onClick={toggleSidePanel}>
<FontAwesomeIcon icon={showSidePanel ? faChevronLeft : faChevronRight}></FontAwesomeIcon>
</IconButton>
<LeftPanel spells={level?.data?.tactics} inventory={level?.data?.lemmas} showSidePanel={showSidePanel} setShowSidePanel={setShowSidePanel} />
</Drawer>
<Grid container columnSpacing={{ xs: 1, sm: 2, md: 3 }} sx={{ flexGrow: 1, p: 3 }} className="main-grid">
<Grid xs={8} className="main-panel">
<div ref={messagePanelRef} className="message-panel">
<MathJax><ReactMarkdown>{level?.data?.introduction}</ReactMarkdown></MathJax>
</div>
<div className="exercise">
<h4>Aufgabe:</h4>
<MathJax><ReactMarkdown>{level?.data?.descrText}</ReactMarkdown></MathJax>
<div className="statement"><code>{level?.data?.descrFormat}</code></div>
<div ref={codeviewRef} className="codeview"></div>
</div>
</Grid>
<Grid xs={4} className="info-panel">
<Button disabled={levelId <= 1} component={RouterLink} to={`/world/${worldId}/level/${levelId - 1}`} sx={{ ml: 3, mt: 2, mb: 2 }} disableFocusRipple>Previous Level</Button>
<Button disabled={false} component={RouterLink} to={`/world/${worldId}/level/${levelId + 1}`} sx={{ ml: 3, mt: 2, mb: 2 }} disableFocusRipple>Next Level</Button>
<div style={{display: expertInfoview ? 'block' : 'none' }} ref={infoviewRef} className="infoview vscode-light"></div>
<div style={{display: expertInfoview ? 'none' : 'block' }}>
<Infoview leanClient={connection.getLeanClient()} editor={editor} editorApi={infoProvider?.getApi()} />
</div>
<FormGroup>
<FormControlLabel onChange={() => { setExpertInfoview(!expertInfoview) }} control={<Switch />} label="Expert mode" />
</FormGroup>
</Grid>
</Grid>
</Grid>
</Box>
</>
}

@ -4,7 +4,7 @@
min-height: 0;
}
.main-panel, .info-panel, .doc-panel {
.main-panel, .info-panel {
height: 100%;
overflow: auto;
}
@ -35,10 +35,18 @@
margin-bottom: 0;
}
.doc-panel li {
border-bottom: 1px solid rgba(0, 0, 0, 0.12); /* This should be teh same colour as `divider` in LeftPanel.tsx */
}
.doc-panel li:first-of-type {
border-top: 1px solid rgb(0, 0, 0, 0.12); /* This should be teh same colour as `divider` in LeftPanel.tsx */
}
/***************************************/
/* TODO: For development purposes only */
/***************************************/
.doc-panel {
/* .doc-panel {
border: 1px solid red;
}
@ -61,3 +69,8 @@
.codeview {
border: 1px solid rgb(98, 148, 255);
}
.main-grid {
border: 1px solid rgb(255, 0, 0);
margin: 1px;
} */

@ -1,17 +1,17 @@
import GameServer.Commands
-- import TestGame.MyNat
-- LemmaDoc zero_add as zero_add in "Addition"
-- "This lemma says `∀ a : , 0 + a = a`."
LemmaDoc zero_add as zero_add in "Addition"
"This lemma says `∀ a : , 0 + a = a`."
-- LemmaDoc add_zero as add_zero in "Addition"
-- "This lemma says `∀ a : , a + 0 = a`."
LemmaDoc add_zero as add_zero in "Addition"
"This lemma says `∀ a : , a + 0 = a`."
-- LemmaDoc add_succ as add_succ in "Addition"
-- "This lemma says `∀ a b : , a + succ b = succ (a + b)`."
LemmaDoc add_succ as add_succ in "Addition"
"This lemma says `∀ a b : , a + succ b = succ (a + b)`."
-- LemmaSet addition : "Addition lemmas" :=
-- zero_add add_zero
LemmaSet addition : "Addition lemmas" :=
zero_add add_zero
LemmaDoc not_not as not_not in "Logic"
"`∀ (A : Prop), ¬¬A ↔ A`."
@ -44,3 +44,6 @@ LemmaDoc even_square as even_square in "Nat"
LemmaSet natural : "Natürliche Zahlen" :=
even odd not_odd not_even
LemmaSet logic : "Logik" :=
not_not

@ -31,5 +31,3 @@ Hint : 42 = 42 =>
"Man schreibt eine Taktik pro Zeile, also gib 'rfl' ein gefolgt von ENTER."
Conclusion "Bravo!"
Tactics rfl

@ -26,3 +26,4 @@ deshalb musst du dieses häufig gar nicht mehr schreiben.
"
Tactics rfl
Lemmas zero_add

Loading…
Cancel
Save