improve layout, store program in url

* fix the command list being way too tall when there are few states
* move the output to the top, maybe where it's like this (wrapping):
+-----+
|1 2 3|
|4 15 |
|16 32|
+-----+
* store and load program from url (maybe make own hook?)
This commit is contained in:
mehbark 2023-04-17 16:33:51 -04:00
parent 1b54cd0d12
commit 4cb13e0a16
5 changed files with 15527 additions and 15256 deletions

30682
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -40,5 +40,8 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"react-use": "^17.4.0"
} }
} }

View file

@ -22,44 +22,65 @@ html {
color: var(--fg); color: var(--fg);
background-color: var(--bg); background-color: var(--bg);
resize: none; resize: none;
outline: 1px solid var(--fg); border: 1px solid var(--fg);
border: none; }
.program:focus-within {
filter: brightness(1.2);
outline: none;
} }
.info { .info {
z-index: -413; z-index: -413;
display: grid; display: grid;
width: 50%;
grid-template-areas: "ops stack" "states states";
grid-template-rows: max-content;
} }
.ops, /* .ops,
.states { .states {
width: 50vw; width: 50vw;
} } */
.ops { .ops {
/* position: fixed; */ /* position: fixed; */
/* top: 0; /* top: 0;
left: 50%; */ left: 50%; */
min-height: 25vh;
margin-bottom: 0;
background-color: var(--bg); background-color: var(--bg);
border: 1px solid var(--fg); border: 1px solid var(--fg);
border-left: none;
grid-area: ops;
padding: 5px;
} }
.states { .states {
overflow: scroll; overflow: scroll;
grid-area: states;
} }
.state { .state {
border-right: 1px solid var(--fg); border-right: 1px solid var(--fg);
border-bottom: 1px solid var(--fg); border-bottom: 1px solid var(--fg);
/* margin: 5px; */ /* margin: 5px; */
padding-left: 5px;
} }
.state > * { /* .state > * {
margin-left: 5px; margin-left: 5px;
} } */
.stack { .stack {
display: block; border: 1px solid var(--fg);
border-left: none;
grid-area: stack;
padding: 5px;
}
.stack::before {
content: "Stack: ";
} }
/* later i can do .op.active and .state.active if necessary */ /* later i can do .op.active and .state.active if necessary */
@ -76,6 +97,10 @@ html {
white-space: pre; white-space: pre;
} }
.output {
display: flex;
}
/* https://www.joshwcomeau.com/css/custom-css-reset/ */ /* https://www.joshwcomeau.com/css/custom-css-reset/ */
/* /*
1. Use a more-intuitive box-sizing model. 1. Use a more-intuitive box-sizing model.

View file

@ -1,16 +1,24 @@
/* eslint-disable eqeqeq */ /* eslint-disable eqeqeq */
import React, { useState } from "react"; import React, { useState } from "react";
import { useHash } from "react-use";
import TextArea from "./TextArea";
import "./App.css"; import "./App.css";
export default function App() { export default function App() {
let [input, setInput] = useState(""); let [hash, setHash] = useHash();
let input = decodeURI(hash.slice(1));
function setInput(s: string) {
setHash(encodeURI(s));
}
let [activeStateIdx, setActiveStateIdx] = useState(0); let [activeStateIdx, setActiveStateIdx] = useState(0);
let parsed = somes(input.split(/[^a-z0-9-]+/).map(parse_op)); let parsed = somes(input.split(/[^a-z0-9-]+/).map(parse_op));
let states = steps(INITIAL_STATE, parsed); let states = steps(INITIAL_STATE, parsed);
return ( return (
<> <>
<textarea <TextArea
onChange={e => setInput(e.target.value)} onChange={(e: any) => setInput(e.target.value)}
value={input} value={input}
className="program" className="program"
title={`\ title={`\
@ -26,14 +34,15 @@ type Op =
| "del" | "del"
| "cmp" | "cmp"
| "swp";`} | "swp";`}
></textarea> ></TextArea>
<div className="info"> <div className="info">
<Ops <Ops
parsed={parsed} parsed={parsed}
ip={(states[activeStateIdx] ?? INITIAL_STATE).ip} ip={states[activeStateIdx]?.ip ?? 0}
// // hack // // hack
// top={document.getElementById("input")?.clientHeight ?? 0} // top={document.getElementById("input")?.clientHeight ?? 0}
/> />
<Stack stack={states[activeStateIdx]?.stack ?? []} />
<States <States
states={states} states={states}
activeStateIdx={activeStateIdx} activeStateIdx={activeStateIdx}
@ -131,13 +140,13 @@ function State({
{...props} {...props}
> >
<div className="ip">Ip: {state.ip}</div> <div className="ip">Ip: {state.ip}</div>
<span>Stack:</span> <Stack stack={state.stack} /> {/* <span>Stack:</span> <Stack stack={state.stack} /> */}
<span>Output:</span> <Output output={state.output} /> <span>Output:</span> <Output output={state.output} />
</div> </div>
); );
} }
function Stack({ stack }: { stack: number[] }): JSX.Element { function Stack({ stack, ...props }: { stack: number[] }): JSX.Element {
let ns = [...stack]; let ns = [...stack];
ns.reverse(); ns.reverse();
return ( return (
@ -146,6 +155,7 @@ function Stack({ stack }: { stack: number[] }): JSX.Element {
children={ns.map((item, i) => ( children={ns.map((item, i) => (
<StackItem item={item} key={i} /> <StackItem item={item} key={i} />
))} ))}
{...props}
/> />
); );
} }
@ -164,7 +174,7 @@ function Output({ output }: { output: number[] }): JSX.Element {
<div <div
className="output" className="output"
children={output.map((item, i) => ( children={output.map((item, i) => (
<StackItem item={item} key={i} /> <span key={i}>{String.fromCharCode(item)}</span>
))} ))}
></div> ></div>
); );
@ -219,8 +229,9 @@ function parse_int(n: string, radix?: number): Option<number> {
} }
function parse_op(op: string): Option<Op> { function parse_op(op: string): Option<Op> {
if (parse_int(op)) { let maybe_int = parse_int(op);
return parse_int(op); if (is_some(maybe_int)) {
return maybe_int;
} }
switch (op) { switch (op) {
case "add": case "add":
@ -235,6 +246,7 @@ function parse_op(op: string): Option<Op> {
case "swp": case "swp":
return op; return op;
} }
console.log(op);
} }
type State = { type State = {
@ -249,7 +261,7 @@ const INITIAL_STATE: State = {
output: [], output: [],
}; };
function steps(state: State, ops: Op[], limit = 100, num_steps = 0): State[] { function steps(state: State, ops: Op[], limit = 300, num_steps = 0): State[] {
if (!ops[state.ip] || num_steps > limit) { if (!ops[state.ip] || num_steps > limit) {
return [state]; return [state];
} }

25
src/TextArea.tsx Normal file
View file

@ -0,0 +1,25 @@
// @ts-nocheck
// https://stackoverflow.com/a/68928267/18571336
import React, { useEffect, useRef, useState } from "react";
function TextArea(props) {
const { value, onChange, ...rest } = props;
const [cursor, setCursor] = useState(null);
const ref = useRef(null);
useEffect(() => {
const input = ref.current;
if (input) input.setSelectionRange(cursor, cursor);
}, [ref, cursor, value]);
const handleChange = e => {
setCursor(e.target.selectionStart);
onChange && onChange(e);
};
return (
<textarea ref={ref} value={value} onChange={handleChange} {...rest} />
);
}
export default TextArea;