undo button

pull/43/head
Alexander Bentkamp 3 years ago
parent fbdc1afe34
commit 45f88cf5a8

@ -10,18 +10,10 @@ import '@fontsource/roboto/700.css';
import './reset.css'; import './reset.css';
import './app.css'; import './app.css';
type SetTitleType = {setTitle: (string) => void, setSubtitle: (string) => void}
export const SetTitleContext = React.createContext<SetTitleType>({setTitle: () => {}, setSubtitle: () => {}})
function App() { function App() {
const [title, setTitle] = useState("")
const [subtitle, setSubtitle] = useState("")
return ( return (
<div className="app"> <div className="app">
<SetTitleContext.Provider value={{setTitle, setSubtitle}}>
<Outlet /> <Outlet />
</SetTitleContext.Provider>
</div> </div>
) )
} }

@ -35,15 +35,22 @@ code {
} }
.btn { .btn {
line-height: inherit;
text-decoration: none; text-decoration: none;
color: var(--clr-primary); background: var(--clr-primary);
background: white; color: white;
display: inline-block; display: inline-block;
border-radius: 0.2em; border-radius: 0.2em;
padding: 0.2em .6em; padding: 0.2rem .6em;
font-size: 1rem; font-size: 1rem;
margin: 0 .2em; margin: 0 .2em;
border: 0; border: 0;
cursor: pointer;
}
.btn-inverted {
color: var(--clr-primary);
background: white;
} }
.btn-disabled, .btn[disabled] { .btn-disabled, .btn[disabled] {
@ -63,7 +70,6 @@ code {
} }
.app-bar { .app-bar {
z-index: 1500;
flex: 0; flex: 0;
background: var(--clr-primary); background: var(--clr-primary);
display: flex; display: flex;

@ -3,12 +3,13 @@ import { Link, LinkProps } from "react-router-dom";
export interface ButtonProps extends LinkProps { export interface ButtonProps extends LinkProps {
disabled?: boolean disabled?: boolean
inverted?: boolean
} }
export function Button(props: ButtonProps) { export function Button(props: ButtonProps) {
if (props.disabled) { if (props.disabled) {
return <span className="btn btn-disabled" {...props}>{props.children}</span> return <span className={`btn btn-disabled ${props.inverted ? 'btn-inverted' : ''}`} {...props}>{props.children}</span>
} else { } else {
return <Link className="btn" {...props}>{props.children}</Link> return <Link className={`btn ${props.inverted ? 'btn-inverted' : ''}`} {...props}>{props.children}</Link>
} }
} }

@ -33,13 +33,10 @@ import { Main } from './infoview/main'
import type { Location } from 'vscode-languageserver-protocol'; import type { Location } from 'vscode-languageserver-protocol';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faHome, faArrowRight, faArrowLeft } from '@fortawesome/free-solid-svg-icons' import { faHome, faArrowRight, faArrowLeft, faRotateLeft } from '@fortawesome/free-solid-svg-icons'
import { styled, useTheme, Theme, CSSObject } from '@mui/material/styles'; import { styled, useTheme, Theme, CSSObject } from '@mui/material/styles';
import { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
import Divider from '@mui/material/Divider';
import Markdown from './Markdown'; import Markdown from './Markdown';
import { SetTitleContext } from '../App';
import Split from 'react-split' import Split from 'react-split'
@ -73,6 +70,40 @@ function Level() {
introductionPanelRef.current!.scrollTo(0,0) introductionPanelRef.current!.scrollTo(0,0)
}, [levelId]) }, [levelId])
React.useEffect(() => {
if (!commandLineMode) {
// Delete last input attempt from command line
editor.executeEdits("command-line", [{
range: editor.getSelection(),
text: "",
forceMoveMarkers: false
}]);
editor.focus()
}
}, [commandLineMode])
const handleUndo = () => {
const endPos = editor.getModel().getFullModelRange().getEndPosition()
let range
console.log(endPos.column)
if (endPos.column === 1) {
range = monaco.Selection.fromPositions(
new monaco.Position(endPos.lineNumber - 1, 1),
endPos
)
} else {
range = monaco.Selection.fromPositions(
new monaco.Position(endPos.lineNumber, 1),
endPos
)
}
editor.executeEdits("undo-button", [{
range,
text: "",
forceMoveMarkers: false
}]);
}
const connection = React.useContext(ConnectionContext) const connection = React.useContext(ConnectionContext)
const gameInfo = useGetGameInfoQuery() const gameInfo = useGetGameInfoQuery()
@ -103,10 +134,10 @@ function Level() {
<span className="app-bar-title"> <span className="app-bar-title">
{levelId && `Level ${levelId}`}{level?.data?.title && `: ${level?.data?.title}`} {levelId && `Level ${levelId}`}{level?.data?.title && `: ${level?.data?.title}`}
</span> </span>
<Button disabled={levelId <= 1} <Button disabled={levelId <= 1} inverted={true}
to={`/world/${worldId}/level/${levelId - 1}`} to={`/world/${worldId}/level/${levelId - 1}`}
><FontAwesomeIcon icon={faArrowLeft} />&nbsp;Previous</Button> ><FontAwesomeIcon icon={faArrowLeft} />&nbsp;Previous</Button>
<Button disabled={levelId >= gameInfo.data?.worldSize[worldId]} <Button disabled={levelId >= gameInfo.data?.worldSize[worldId]} inverted={true}
to={`/world/${worldId}/level/${levelId + 1}`} to={`/world/${worldId}/level/${levelId + 1}`}
>Next&nbsp;<FontAwesomeIcon icon={faArrowRight} /></Button> >Next&nbsp;<FontAwesomeIcon icon={faArrowRight} /></Button>
</div> </div>
@ -125,9 +156,13 @@ function Level() {
</div> </div>
</div> </div>
<div className="info-panel"> <div className="info-panel">
<FormGroup className="input-mode-switch"> <div className="input-mode-switch">
{commandLineMode && <button className="btn" onClick={handleUndo}><FontAwesomeIcon icon={faRotateLeft} /> Undo</button>}
<FormGroup>
<FormControlLabel control={<Switch onChange={(ev) => { setCommandLineMode(!commandLineMode) }} />} label="Editor mode" /> <FormControlLabel control={<Switch onChange={(ev) => { setCommandLineMode(!commandLineMode) }} />} label="Editor mode" />
</FormGroup> </FormGroup>
</div>
<EditorContext.Provider value={editorConnection}> <EditorContext.Provider value={editorConnection}>
<MonacoEditorContext.Provider value={editor}> <MonacoEditorContext.Provider value={editor}>
<InputModeContext.Provider value={{commandLineMode, setCommandLineMode}}> <InputModeContext.Provider value={{commandLineMode, setCommandLineMode}}>

@ -14,7 +14,6 @@ import { useGetGameInfoQuery } from '../state/api';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Markdown from './Markdown'; import Markdown from './Markdown';
import { selectCompleted } from '../state/progress'; import { selectCompleted } from '../state/progress';
import { SetTitleContext } from '../App';
function LevelIcon({ worldId, levelId, position }) { function LevelIcon({ worldId, levelId, position }) {
@ -34,12 +33,9 @@ function Welcome() {
const { nodes, bounds }: any = gameInfo.data ? computeWorldLayout(gameInfo.data?.worlds) : {nodes: []} const { nodes, bounds }: any = gameInfo.data ? computeWorldLayout(gameInfo.data?.worlds) : {nodes: []}
const {setTitle, setSubtitle} = React.useContext(SetTitleContext);
useEffect(() => { useEffect(() => {
if (gameInfo.data?.title) { if (gameInfo.data?.title) {
window.document.title = gameInfo.data.title window.document.title = gameInfo.data.title
setTitle(gameInfo.data.title)
} }
}, [gameInfo.data?.title]) }, [gameInfo.data?.title])

@ -4,43 +4,67 @@ import { LspDiagnosticsContext } from '../../../../node_modules/lean4-infoview/s
import { useServerNotificationEffect } from '../../../../node_modules/lean4-infoview/src/infoview/util'; import { useServerNotificationEffect } from '../../../../node_modules/lean4-infoview/src/infoview/util';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'
import { DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol'; import { DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol';
import { MonacoEditorContext } from '../Level' import { InputModeContext, MonacoEditorContext } from '../Level'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faWandMagicSparkles } from '@fortawesome/free-solid-svg-icons' import { faWandMagicSparkles } from '@fortawesome/free-solid-svg-icons'
function hasErrorsOrWarnings(diags) {
return diags.some(
(d) =>
!d.message.startsWith("unsolved goals") &&
(d.severity == DiagnosticSeverity.Error || d.severity == DiagnosticSeverity.Warning)
)
}
export function CommandLine() { export function CommandLine() {
const editor = React.useContext(MonacoEditorContext) const editor = React.useContext(MonacoEditorContext)
const commandInput = useRef<HTMLInputElement>() const commandInput = useRef<HTMLInputElement>()
const [processing, setProcessing] = useState(false) const [processing, setProcessing] = useState(false)
// const allDiags = React.useContext(LspDiagnosticsContext) const { commandLineMode } = React.useContext(InputModeContext)
// const fileDiags = allDiags.get(editor.getModel().uri.toString()) const allDiags = React.useContext(LspDiagnosticsContext)
const diags = allDiags.get(editor.getModel().uri.toString())
React.useEffect(() => {
if (hasErrorsOrWarnings(diags)) {
// alert("err")
}
}, [])
React.useEffect(() => {
if (commandLineMode) {
const endPos = editor.getModel().getFullModelRange().getEndPosition()
if (editor.getModel().getLineContent(endPos.lineNumber).trim() !== "") {
editor.executeEdits("command-line", [{
range: monaco.Selection.fromPositions(endPos, endPos),
text: "\n",
forceMoveMarkers: false
}]);
}
editor.setPosition(editor.getModel().getFullModelRange().getEndPosition())
}
}, [commandLineMode])
const handleSubmit : React.FormEventHandler<HTMLFormElement> = (ev) => { const handleSubmit : React.FormEventHandler<HTMLFormElement> = (ev) => {
ev.preventDefault() ev.preventDefault()
var selection = monaco.Selection.fromPositions( editor.executeEdits("command-line", [{
range: monaco.Selection.fromPositions(
editor.getPosition(), editor.getPosition(),
editor.getModel().getFullModelRange().getEndPosition() editor.getModel().getFullModelRange().getEndPosition()
); ),
var text = commandInput.current!.value + "\n"; text: commandInput.current!.value + "\n",
var op = {range: selection, text: text, forceMoveMarkers: false}; forceMoveMarkers: false
editor.executeEdits("my-source", [op], editor.getSelections()); }]);
setProcessing(true) setProcessing(true)
} }
useServerNotificationEffect('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => { useServerNotificationEffect('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => {
if (params.uri == editor.getModel().uri.toString()) { if (params.uri == editor.getModel().uri.toString()) {
setProcessing(false) setProcessing(false)
const hasErrorsOrWarnings = params.diagnostics.some( if (!hasErrorsOrWarnings(params.diagnostics)) {
(d) =>
!d.message.startsWith("unsolved goals") &&
(d.severity == DiagnosticSeverity.Error || d.severity == DiagnosticSeverity.Warning)
)
if (!hasErrorsOrWarnings) {
commandInput.current!.value = ""; commandInput.current!.value = "";
editor.setPosition(editor.getModel().getFullModelRange().getEndPosition()) editor.setPosition(editor.getModel().getFullModelRange().getEndPosition())
} }
@ -50,7 +74,7 @@ export function CommandLine() {
return <div className="command-line"> return <div className="command-line">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<input type="text" ref={commandInput} disabled={processing} /> <input type="text" ref={commandInput} disabled={processing} />
<button type="submit" disabled={processing} className="btn"><FontAwesomeIcon icon={faWandMagicSparkles} /> Run</button> <button type="submit" disabled={processing} className="btn btn-inverted"><FontAwesomeIcon icon={faWandMagicSparkles} /> Run</button>
</form> </form>
</div> </div>
} }

@ -112,7 +112,6 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
}), [selectedLocs]) }), [selectedLocs])
const goalFilter = { reverse: false, showType: true, showInstance: true, showHiddenAssumption: true, showLetValue: true } const goalFilter = { reverse: false, showType: true, showInstance: true, showHiddenAssumption: true, showLetValue: true }
/* Adding {' '} to manage string literals properly: https://reactjs.org/docs/jsx-in-depth.html#string-literals-1 */ /* Adding {' '} to manage string literals properly: https://reactjs.org/docs/jsx-in-depth.html#string-literals-1 */
return <> return <>
{hasError && {hasError &&
@ -121,13 +120,13 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
<a className='link pointer dim' onClick={e => { e.preventDefault(); void triggerUpdate(); }}>{' '}Try again.</a> <a className='link pointer dim' onClick={e => { e.preventDefault(); void triggerUpdate(); }}>{' '}Try again.</a>
</div>} </div>}
<LocationsContext.Provider value={locs}> <LocationsContext.Provider value={locs}>
{goals && goals.goals.length > 0 && <div className="goals-section"> <div className="goals-section">
<div className="goals-section-title">Main Goal</div> { goals && goals.goals.length > 0
<Goal filter={goalFilter} key='mainGoal' goal={goals.goals[0]} showHints={true} /> ? <><div className="goals-section-title">Main Goal</div>
</div>} <Goal filter={goalFilter} key='mainGoal' goal={goals.goals[0]} showHints={true} /></>
: <div className="goals-section-title">No Goals</div> }
</div>
</LocationsContext.Provider> </LocationsContext.Provider>
{/* <FilteredGoals headerChildren='Expected type' key='term-goal'
goals={termGoal !== undefined ? {goals: [{...termGoal, hints: []}]} : undefined} /> */}
{userWidgets.map(widget => {userWidgets.map(widget =>
<details key={`widget::${widget.id}::${widget.range?.toString()}`} open> <details key={`widget::${widget.id}::${widget.range?.toString()}`} open>
<summary className='mv2 pointer'>{widget.name}</summary> <summary className='mv2 pointer'>{widget.name}</summary>
@ -135,14 +134,6 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
selectedLocations={selectedLocs} widget={widget}/> selectedLocations={selectedLocs} widget={widget}/>
</details> </details>
)} )}
{/* <div style={{display: hasMessages ? 'block' : 'none'}} key='messages'>
<details key='messages' open>
<summary className='mv2 pointer'>Messages ({messages.length})</summary>
<div className='ml1'>
<MessagesList uri={pos.uri} messages={messages} />
</div>
</details>
</div> */}
{nothingToShow && ( {nothingToShow && (
isPaused ? isPaused ?
/* Adding {' '} to manage string literals properly: https://reactjs.org/docs/jsx-in-depth.html#string-literals-1 */ /* Adding {' '} to manage string literals properly: https://reactjs.org/docs/jsx-in-depth.html#string-literals-1 */

@ -24,7 +24,7 @@
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
} }
.main-panel, .info-panel { .main-panel, .info-panel, .doc-panel {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
} }
@ -66,6 +66,9 @@
.input-mode-switch { .input-mode-switch {
margin: .5em 0; margin: .5em 0;
float: right; float: right;
display: flex;
flex-direction: row;
gap: 1em;
} }
.doc-panel li { .doc-panel li {

Loading…
Cancel
Save