undo button
This commit is contained in:
+1
-9
@@ -10,18 +10,10 @@ import '@fontsource/roboto/700.css';
|
||||
import './reset.css';
|
||||
import './app.css';
|
||||
|
||||
type SetTitleType = {setTitle: (string) => void, setSubtitle: (string) => void}
|
||||
export const SetTitleContext = React.createContext<SetTitleType>({setTitle: () => {}, setSubtitle: () => {}})
|
||||
|
||||
function App() {
|
||||
const [title, setTitle] = useState("")
|
||||
const [subtitle, setSubtitle] = useState("")
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<SetTitleContext.Provider value={{setTitle, setSubtitle}}>
|
||||
<Outlet />
|
||||
</SetTitleContext.Provider>
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-4
@@ -35,15 +35,22 @@ code {
|
||||
}
|
||||
|
||||
.btn {
|
||||
line-height: inherit;
|
||||
text-decoration: none;
|
||||
color: var(--clr-primary);
|
||||
background: white;
|
||||
background: var(--clr-primary);
|
||||
color: white;
|
||||
display: inline-block;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.2em .6em;
|
||||
padding: 0.2rem .6em;
|
||||
font-size: 1rem;
|
||||
margin: 0 .2em;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-inverted {
|
||||
color: var(--clr-primary);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.btn-disabled, .btn[disabled] {
|
||||
@@ -63,7 +70,6 @@ code {
|
||||
}
|
||||
|
||||
.app-bar {
|
||||
z-index: 1500;
|
||||
flex: 0;
|
||||
background: var(--clr-primary);
|
||||
display: flex;
|
||||
|
||||
@@ -3,12 +3,13 @@ import { Link, LinkProps } from "react-router-dom";
|
||||
|
||||
export interface ButtonProps extends LinkProps {
|
||||
disabled?: boolean
|
||||
inverted?: boolean
|
||||
}
|
||||
|
||||
export function Button(props: ButtonProps) {
|
||||
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 {
|
||||
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 { 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 { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Markdown from './Markdown';
|
||||
import { SetTitleContext } from '../App';
|
||||
|
||||
import Split from 'react-split'
|
||||
|
||||
@@ -73,6 +70,40 @@ function Level() {
|
||||
introductionPanelRef.current!.scrollTo(0,0)
|
||||
}, [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 gameInfo = useGetGameInfoQuery()
|
||||
@@ -103,10 +134,10 @@ function Level() {
|
||||
<span className="app-bar-title">
|
||||
{levelId && `Level ${levelId}`}{level?.data?.title && `: ${level?.data?.title}`}
|
||||
</span>
|
||||
<Button disabled={levelId <= 1}
|
||||
<Button disabled={levelId <= 1} inverted={true}
|
||||
to={`/world/${worldId}/level/${levelId - 1}`}
|
||||
><FontAwesomeIcon icon={faArrowLeft} /> Previous</Button>
|
||||
<Button disabled={levelId >= gameInfo.data?.worldSize[worldId]}
|
||||
<Button disabled={levelId >= gameInfo.data?.worldSize[worldId]} inverted={true}
|
||||
to={`/world/${worldId}/level/${levelId + 1}`}
|
||||
>Next <FontAwesomeIcon icon={faArrowRight} /></Button>
|
||||
</div>
|
||||
@@ -125,9 +156,13 @@ function Level() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-panel">
|
||||
<FormGroup className="input-mode-switch">
|
||||
<FormControlLabel control={<Switch onChange={(ev) => { setCommandLineMode(!commandLineMode) }} />} label="Editor mode" />
|
||||
</FormGroup>
|
||||
<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" />
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
<EditorContext.Provider value={editorConnection}>
|
||||
<MonacoEditorContext.Provider value={editor}>
|
||||
<InputModeContext.Provider value={{commandLineMode, setCommandLineMode}}>
|
||||
|
||||
@@ -14,7 +14,6 @@ import { useGetGameInfoQuery } from '../state/api';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Markdown from './Markdown';
|
||||
import { selectCompleted } from '../state/progress';
|
||||
import { SetTitleContext } from '../App';
|
||||
|
||||
|
||||
function LevelIcon({ worldId, levelId, position }) {
|
||||
@@ -34,12 +33,9 @@ function Welcome() {
|
||||
|
||||
const { nodes, bounds }: any = gameInfo.data ? computeWorldLayout(gameInfo.data?.worlds) : {nodes: []}
|
||||
|
||||
const {setTitle, setSubtitle} = React.useContext(SetTitleContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (gameInfo.data?.title) {
|
||||
window.document.title = gameInfo.data.title
|
||||
setTitle(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 * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'
|
||||
import { DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol';
|
||||
import { MonacoEditorContext } from '../Level'
|
||||
import { InputModeContext, MonacoEditorContext } from '../Level'
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
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() {
|
||||
|
||||
const editor = React.useContext(MonacoEditorContext)
|
||||
const commandInput = useRef<HTMLInputElement>()
|
||||
const [processing, setProcessing] = useState(false)
|
||||
|
||||
// const allDiags = React.useContext(LspDiagnosticsContext)
|
||||
// const fileDiags = allDiags.get(editor.getModel().uri.toString())
|
||||
const { commandLineMode } = React.useContext(InputModeContext)
|
||||
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) => {
|
||||
ev.preventDefault()
|
||||
var selection = monaco.Selection.fromPositions(
|
||||
editor.getPosition(),
|
||||
editor.getModel().getFullModelRange().getEndPosition()
|
||||
);
|
||||
var text = commandInput.current!.value + "\n";
|
||||
var op = {range: selection, text: text, forceMoveMarkers: false};
|
||||
editor.executeEdits("my-source", [op], editor.getSelections());
|
||||
editor.executeEdits("command-line", [{
|
||||
range: monaco.Selection.fromPositions(
|
||||
editor.getPosition(),
|
||||
editor.getModel().getFullModelRange().getEndPosition()
|
||||
),
|
||||
text: commandInput.current!.value + "\n",
|
||||
forceMoveMarkers: false
|
||||
}]);
|
||||
setProcessing(true)
|
||||
}
|
||||
|
||||
useServerNotificationEffect('textDocument/publishDiagnostics', (params: PublishDiagnosticsParams) => {
|
||||
if (params.uri == editor.getModel().uri.toString()) {
|
||||
setProcessing(false)
|
||||
const hasErrorsOrWarnings = params.diagnostics.some(
|
||||
(d) =>
|
||||
!d.message.startsWith("unsolved goals") &&
|
||||
(d.severity == DiagnosticSeverity.Error || d.severity == DiagnosticSeverity.Warning)
|
||||
)
|
||||
if (!hasErrorsOrWarnings) {
|
||||
if (!hasErrorsOrWarnings(params.diagnostics)) {
|
||||
commandInput.current!.value = "";
|
||||
editor.setPosition(editor.getModel().getFullModelRange().getEndPosition())
|
||||
}
|
||||
@@ -50,7 +74,7 @@ export function CommandLine() {
|
||||
return <div className="command-line">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
|
||||
}), [selectedLocs])
|
||||
|
||||
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 */
|
||||
return <>
|
||||
{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>
|
||||
</div>}
|
||||
<LocationsContext.Provider value={locs}>
|
||||
{goals && goals.goals.length > 0 && <div className="goals-section">
|
||||
<div className="goals-section-title">Main Goal</div>
|
||||
<Goal filter={goalFilter} key='mainGoal' goal={goals.goals[0]} showHints={true} />
|
||||
</div>}
|
||||
<div className="goals-section">
|
||||
{ goals && goals.goals.length > 0
|
||||
? <><div className="goals-section-title">Main Goal</div>
|
||||
<Goal filter={goalFilter} key='mainGoal' goal={goals.goals[0]} showHints={true} /></>
|
||||
: <div className="goals-section-title">No Goals</div> }
|
||||
</div>
|
||||
</LocationsContext.Provider>
|
||||
{/* <FilteredGoals headerChildren='Expected type' key='term-goal'
|
||||
goals={termGoal !== undefined ? {goals: [{...termGoal, hints: []}]} : undefined} /> */}
|
||||
{userWidgets.map(widget =>
|
||||
<details key={`widget::${widget.id}::${widget.range?.toString()}`} open>
|
||||
<summary className='mv2 pointer'>{widget.name}</summary>
|
||||
@@ -135,14 +134,6 @@ const InfoDisplayContent = React.memo((props: InfoDisplayContentProps) => {
|
||||
selectedLocations={selectedLocs} widget={widget}/>
|
||||
</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 && (
|
||||
isPaused ?
|
||||
/* 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==');
|
||||
}
|
||||
|
||||
.main-panel, .info-panel {
|
||||
.main-panel, .info-panel, .doc-panel {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -66,6 +66,9 @@
|
||||
.input-mode-switch {
|
||||
margin: .5em 0;
|
||||
float: right;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.doc-panel li {
|
||||
|
||||
Reference in New Issue
Block a user