undo button
This commit is contained in:
+1
-9
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-4
@@ -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} /> Previous</Button>
|
><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}`}
|
to={`/world/${worldId}/level/${levelId + 1}`}
|
||||||
>Next <FontAwesomeIcon icon={faArrowRight} /></Button>
|
>Next <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">
|
||||||
<FormControlLabel control={<Switch onChange={(ev) => { setCommandLineMode(!commandLineMode) }} />} label="Editor mode" />
|
{commandLineMode && <button className="btn" onClick={handleUndo}><FontAwesomeIcon icon={faRotateLeft} /> Undo</button>}
|
||||||
</FormGroup>
|
<FormGroup>
|
||||||
|
<FormControlLabel control={<Switch onChange={(ev) => { setCommandLineMode(!commandLineMode) }} />} label="Editor mode" />
|
||||||
|
</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", [{
|
||||||
editor.getPosition(),
|
range: monaco.Selection.fromPositions(
|
||||||
editor.getModel().getFullModelRange().getEndPosition()
|
editor.getPosition(),
|
||||||
);
|
editor.getModel().getFullModelRange().getEndPosition()
|
||||||
var text = commandInput.current!.value + "\n";
|
),
|
||||||
var op = {range: selection, text: text, forceMoveMarkers: false};
|
text: commandInput.current!.value + "\n",
|
||||||
editor.executeEdits("my-source", [op], editor.getSelections());
|
forceMoveMarkers: false
|
||||||
|
}]);
|
||||||
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user