parent
167a7747db
commit
17422fc1e6
@ -1,7 +1,86 @@
|
||||
import {createEffect, createSignal, createMemo, untrack} from "solid-js";
|
||||
|
||||
import Tile from "./Tile.jsx";
|
||||
import Signal from "./Signal.jsx";
|
||||
import styles from "./RightPane.module.css";
|
||||
|
||||
export default function RightPane(props) {
|
||||
return (<div id="right-pane">
|
||||
let {settings, setSettings, world, setWorld} = props;
|
||||
|
||||
let selected = createMemo((old) => {
|
||||
if (old?.ptr) {
|
||||
old.free();
|
||||
}
|
||||
|
||||
if (settings.selected) {
|
||||
return world().get(...settings.selected) ?? null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, {equals: false});
|
||||
|
||||
let signal = createMemo((old) => {
|
||||
if (old?.ptr) {
|
||||
old.free();
|
||||
}
|
||||
|
||||
if (selected()) {
|
||||
return selected().signal ?? null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, {equals: false});
|
||||
|
||||
function setSignal(new_signal) {
|
||||
setWorld((world) => {
|
||||
let [x, y] = settings.selected;
|
||||
let full_tile = world.get(x, y);
|
||||
if (typeof new_signal === "function") {
|
||||
full_tile.signal = new_signal(full_tile.signal);
|
||||
} else {
|
||||
full_tile.signal = new_signal;
|
||||
}
|
||||
world.set(x, y, full_tile);
|
||||
return world;
|
||||
});
|
||||
}
|
||||
|
||||
function set_full_tile(tile) {
|
||||
if (settings.selected) {
|
||||
setWorld((world) => {
|
||||
let [x, y ] = settings.selected;
|
||||
if (typeof tile === "function") {
|
||||
// world.get(...) is passed by ownership, and an owned value is expected back
|
||||
world.set(x, y, tile(world.get(x, y)));
|
||||
} else {
|
||||
world.set(x, y, tile);
|
||||
}
|
||||
return world;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function cleanup() {
|
||||
let sel = selected();
|
||||
if (sel.ptr) {
|
||||
sel.free();
|
||||
}
|
||||
|
||||
let sig = signal();
|
||||
if (sig.ptr) {
|
||||
sig.free();
|
||||
}
|
||||
}
|
||||
|
||||
return (<div class={styles.RightPane} onCleanup={cleanup}>
|
||||
<Show when={selected() !== null} fallback={<i class={styles.gray}>Nothing selected</i>}>
|
||||
<h2 class={styles.h2}>Coordinates:</h2>
|
||||
({settings.selected[0]}, {settings.selected[1]})
|
||||
<h2 class={styles.h2}>State:</h2>
|
||||
{selected().state}
|
||||
<h2 class={styles.h2}>Signal:</h2>
|
||||
<Signal signal={signal} setSignal={setSignal} />
|
||||
<Tile full_tile={selected} set_full_tile={set_full_tile} />
|
||||
</Show>
|
||||
</div>);
|
||||
}
|
||||
|
@ -0,0 +1,66 @@
|
||||
.RightPane {
|
||||
width: 24em;
|
||||
max-width: 25vw;
|
||||
background: #18181b;
|
||||
color: #d0d0d0;
|
||||
padding: 1.5em 1em;
|
||||
font-family: monospace;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.gray, .empty {
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
.h2 {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.h3 {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.indent {
|
||||
margin-left: 0.5em;
|
||||
padding-left: calc(0.5em - 1px);
|
||||
border-left: 1px solid #808080;
|
||||
}
|
||||
|
||||
.stack {
|
||||
list-style: none;
|
||||
padding-left: 0em;
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
.stack > li {
|
||||
margin-top: .2em;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.stack > li::before {
|
||||
content: "\25b6";
|
||||
margin-right: 0.5em;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: block;
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.properties {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.properties > li {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
import {createEffect} from "solid-js";
|
||||
|
||||
import Direction from "./input/Direction.jsx";
|
||||
import Value from "./input/Value.jsx";
|
||||
|
||||
import styles from "./RightPane.module.css";
|
||||
import input_styles from "./input/input.module.css";
|
||||
|
||||
|
||||
export const DIRECTIONS = [
|
||||
"Up",
|
||||
"Right",
|
||||
"Down",
|
||||
"Left"
|
||||
];
|
||||
|
||||
export default function Signal(props) {
|
||||
let {signal, setSignal} = props;
|
||||
|
||||
return (<Show when={signal()} fallback={<i class={styles.gray}>No signal</i>}>
|
||||
<div class={styles.indent}>
|
||||
<h3 class={styles.h3}>Direction:</h3>
|
||||
<Show when={setSignal} fallback={signal()?.direction}>
|
||||
<Direction value={() => signal()?.direction} setValue={(dir) => {
|
||||
setSignal((signal) => {
|
||||
signal.direction = dir;
|
||||
return signal;
|
||||
});
|
||||
}} />
|
||||
</Show>
|
||||
<h3 class={styles.h3}>Stack:</h3>
|
||||
<ol class={styles.stack}>
|
||||
<For each={signal()?.stack} fallback={<i class={styles.empty}>(Empty)</i>}>
|
||||
{(item, index) => {
|
||||
let setValue = setSignal ? (new_value) => {
|
||||
if (typeof new_value === "function") {
|
||||
new_value = new_value(item);
|
||||
}
|
||||
setSignal((signal) => {
|
||||
signal.stack[index()] = new_value;
|
||||
return signal;
|
||||
});
|
||||
} : null;
|
||||
|
||||
return <li><Value value={() => item} setValue={setValue} /></li>;
|
||||
|
||||
// if (setSignal) {
|
||||
// if ("Number" in item) {
|
||||
// // Return number input for the `index()`-th element
|
||||
// return <li>
|
||||
// <input
|
||||
// class={input_styles.input}
|
||||
// type="number"
|
||||
// value={item["Number"]}
|
||||
// onChange={(evt) => {
|
||||
// console.log("input");
|
||||
// setSignal((signal) => {
|
||||
// signal.stack[index()] = {"Number": +evt.currentTarget.value};
|
||||
// return signal;
|
||||
// });
|
||||
// }}
|
||||
// />
|
||||
// </li>;
|
||||
// } else if ("String" in item) {
|
||||
// // Return string input for the `index()`-th element
|
||||
// return <li>"
|
||||
// <input
|
||||
// class={input_styles.input}
|
||||
// type="string"
|
||||
// value={item["String"]}
|
||||
// onChange={(evt) => {
|
||||
// setSignal((signal) => {
|
||||
// signal.stack[index()] = {"String": evt.currentTarget.value};
|
||||
// return signal;
|
||||
// });
|
||||
// }}
|
||||
// />
|
||||
// "</li>;
|
||||
// }
|
||||
// } else {
|
||||
// if (item?.["Number"]) {
|
||||
// return <li>{item["Number"]}</li>;
|
||||
// } else if (item?.["String"]) {
|
||||
// return <li>"{item["String"]}"</li>;
|
||||
// }
|
||||
// }
|
||||
}}
|
||||
</For>
|
||||
<Show when={setSignal}>
|
||||
<div>
|
||||
<button
|
||||
class={input_styles.button}
|
||||
aria-label="Remove a value from the stack"
|
||||
title="Pop value"
|
||||
disabled={!signal() || signal().stack.length == 0}
|
||||
onClick={(evt) => {
|
||||
setSignal((signal) => {
|
||||
signal.stack.pop();
|
||||
return signal;
|
||||
});
|
||||
}}
|
||||
>-</button>
|
||||
<button
|
||||
class={input_styles.button}
|
||||
aria-label="Add a value to the stack"
|
||||
title="Push value"
|
||||
onClick={(evt) => {
|
||||
setSignal((signal) => {
|
||||
signal.stack.push({"Number": 0.0});
|
||||
return signal;
|
||||
});
|
||||
}}
|
||||
>+</button>
|
||||
</div>
|
||||
</Show>
|
||||
</ol>
|
||||
</div>
|
||||
</Show>);
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
import {createEffect, createSignal, createMemo, untrack} from "solid-js";
|
||||
|
||||
import styles from "./RightPane.module.css";
|
||||
|
||||
import Signal from "./Signal.jsx";
|
||||
import Direction from "./input/Direction.jsx";
|
||||
import Orientation from "./input/Orientation.jsx";
|
||||
import Number from "./input/Number.jsx";
|
||||
import Value from "./input/Value.jsx";
|
||||
|
||||
export const COMPONENTS = new Map();
|
||||
|
||||
COMPONENTS.set("Signal", (props) => <Signal signal={props.value} setSignal={props.setValue} />);
|
||||
COMPONENTS.set("Direction", Direction);
|
||||
COMPONENTS.set("Orientation", Orientation);
|
||||
COMPONENTS.set("Uint", (props) => <Number value={props.value} setValue={props.setValue} min={0} />);
|
||||
COMPONENTS.set("Int", Number);
|
||||
COMPONENTS.set("Value", Value);
|
||||
|
||||
export default function Tile(props) {
|
||||
let {full_tile, set_full_tile} = props;
|
||||
|
||||
let tile = createMemo(() => {
|
||||
return full_tile()?.tile;
|
||||
});
|
||||
|
||||
let schema = createMemo(() => {
|
||||
return full_tile()?.schema();
|
||||
});
|
||||
|
||||
let tile_name = createMemo(() => {
|
||||
if (tile()) {
|
||||
return Object.keys(tile())[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// This abstraction is needed to propagate the modifications of the Tile up to the FullTile
|
||||
function bindSetValue(label, value) {
|
||||
return function setValue(new_value) {
|
||||
set_full_tile((full_tile) => {
|
||||
let tile = full_tile.tile;
|
||||
let name = Object.keys(tile)[0];
|
||||
if (typeof new_value === "function") {
|
||||
value = new_value(value);
|
||||
} else {
|
||||
value = new_value;
|
||||
}
|
||||
tile[name] = schema_recurse(schema(), tile[name], label, value);
|
||||
full_tile.tile = tile;
|
||||
return full_tile;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2 class={styles.h2}>Tile:</h2>
|
||||
<Show when={tile()} fallback={<i class={styles.gray}>No tile</i>}>
|
||||
<div class={styles.indent}>
|
||||
<ol class={styles.properties}>
|
||||
<For each={[...schema_iter(schema(), tile()[tile_name()])]} fallback={<li><i class={styles.gray}>No properties</i></li>}>
|
||||
{([label, type, value]) => {
|
||||
if (COMPONENTS.has(type)) {
|
||||
let setValue = bindSetValue(label, value);
|
||||
return <li><b>{label}: </b>{COMPONENTS.get(type)({value: () => value, setValue})}</li>
|
||||
} else {
|
||||
return <li><b>{label}: </b>{value?.toString()}</li>
|
||||
}
|
||||
}}
|
||||
</For>
|
||||
</ol>
|
||||
</div>
|
||||
</Show>
|
||||
</>);
|
||||
}
|
||||
|
||||
export function* schema_iter(schema, tile) {
|
||||
if (Array.isArray(schema)) {
|
||||
for (let n = 0; n < schema.length; n++) {
|
||||
yield* schema_iter(schema[n], tile[n]);
|
||||
}
|
||||
} else if (typeof schema === "object") {
|
||||
for (let name in schema) {
|
||||
yield* schema_iter(schema[name], tile[name]);
|
||||
}
|
||||
} else if (typeof schema === "string") {
|
||||
let res = schema.split(":");
|
||||
res.push(tile);
|
||||
yield res;
|
||||
}
|
||||
}
|
||||
|
||||
export function schema_recurse(schema, tile, label, value) {
|
||||
if (Array.isArray(schema)) {
|
||||
for (let n = 0; n < schema.length; n++) {
|
||||
tile[n] = schema_recurse(schema[n], tile[n], label, value);
|
||||
}
|
||||
return tile;
|
||||
} else if (typeof schema === "object") {
|
||||
for (let name in schema) {
|
||||
tile[name] = schema_recurse(schema[name], tile[name], label, value);
|
||||
}
|
||||
return tile;
|
||||
} else if (typeof schema === "string") {
|
||||
if (schema.split(":")[0] === label) {
|
||||
return value;
|
||||
} else {
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import {createEffect} from "solid-js";
|
||||
|
||||
import styles from "./input.module.css";
|
||||
|
||||
export default function Direction(props) {
|
||||
let {value, setValue} = props;
|
||||
let select;
|
||||
|
||||
createEffect(() => {
|
||||
select.value = value();
|
||||
});
|
||||
|
||||
return (<select class={styles.select} title="Direction" ref={select} onChange={() => setValue(select.value)}>
|
||||
<option value="Up" default>Up</option>
|
||||
<option value="Right">Right</option>
|
||||
<option value="Down">Down</option>
|
||||
<option value="Left">Left</option>
|
||||
</select>);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import {createEffect} from "solid-js";
|
||||
|
||||
import styles from "./input.module.css";
|
||||
|
||||
export default function Number(props) {
|
||||
let {value, setValue} = props;
|
||||
let select;
|
||||
|
||||
return (<input
|
||||
type="number"
|
||||
class={styles.input}
|
||||
value={value()}
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
onChange={(evt) => setValue(+evt.currentTarget.value)}
|
||||
/>);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import {createEffect} from "solid-js";
|
||||
|
||||
import styles from "./input.module.css";
|
||||
|
||||
export default function Orientation(props) {
|
||||
let {value, setValue} = props;
|
||||
let select;
|
||||
|
||||
createEffect(() => {
|
||||
select.value = value();
|
||||
});
|
||||
|
||||
return (<select class={styles.select} title="Orientation" ref={select} onChange={() => setValue(select.value)}>
|
||||
<Show when={!props.no_any}><option value="Any" default>Any</option></Show>
|
||||
<option value="Vertical">Vertical</option>
|
||||
<option value="Horizontal">Horizontal</option>
|
||||
</select>);
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import {createEffect} from "solid-js";
|
||||
|
||||
import styles from "./input.module.css";
|
||||
|
||||
export default function String(props) {
|
||||
let {value, setValue} = props;
|
||||
|
||||
return (<>"<input
|
||||
type="string"
|
||||
class={styles.input}
|
||||
value={value()}
|
||||
onChange={(evt) => setValue(evt.currentTarget.value)}
|
||||
/>"</>);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import {createEffect} from "solid-js";
|
||||
import styles from "./input.module.css";
|
||||
|
||||
export default function Value(props) {
|
||||
let {value, setValue} = props;
|
||||
|
||||
let select;
|
||||
|
||||
createEffect(() => {
|
||||
if (select) {
|
||||
select.value = Object.keys(value())[0];
|
||||
}
|
||||
});
|
||||
|
||||
return (<>
|
||||
<Show when={setValue}>
|
||||
<select title="Type" ref={select} class={styles.select_type} onChange={(evt) => {
|
||||
if (!(select.value in value())) {
|
||||
if (select.value === "Number") {
|
||||
setValue({"Number": 0.0});
|
||||
} else if (select.value === "String") {
|
||||
setValue({"String": ""});
|
||||
}
|
||||
}
|
||||
}}>
|
||||
<option value="Number" title="Number" default>N</option>
|
||||
<option value="String" title="String">S</option>
|
||||
</select>
|
||||
<Switch>
|
||||
<Match when={"Number" in value()}>
|
||||
<input
|
||||
class={styles.input}
|
||||
type="number"
|
||||
value={value()["Number"]}
|
||||
onChange={(evt) => {
|
||||
setValue({"Number": +evt.currentTarget.value});
|
||||
}}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={"String" in value()}>
|
||||
"<input
|
||||
class={styles.input}
|
||||
type="string"
|
||||
value={value()["String"]}
|
||||
onChange={(evt) => {
|
||||
setValue({"String": evt.currentTarget.value});
|
||||
}}
|
||||
/>
|
||||
"
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
<Show when={!setValue}>
|
||||
<Switch>
|
||||
<Match when={"Number" in value()}>
|
||||
{value()["Number"]}
|
||||
</Match>
|
||||
<Match when={"String" in value()}>
|
||||
"{value()["String"]}"
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
</>);
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
.select, .input, .select_type {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: inherit;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
border: 1px solid #808080;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.select {
|
||||
min-width: 6em;
|
||||
max-width: 12em;
|
||||
}
|
||||
|
||||
.select_type {
|
||||
width: 2.5em;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.input[type="number"] {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
.input[type="string"] {
|
||||
min-width: 6em;
|
||||
max-width: 12em;
|
||||
}
|
||||
|
||||
.select:hover, .input:hover, .select_type:hover {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid #a0a0a0;
|
||||
}
|
||||
|
||||
.input:active {
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.button {
|
||||
min-width: 1.5em;
|
||||
background: rgb(64, 68, 96, 0.5);
|
||||
color: white;
|
||||
border: 1px solid #d0d0d0;
|
||||
border-radius: 2px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
background: transparent;
|
||||
border: 1px solid #a0a0a0;
|
||||
color: #d0d0d0;
|
||||
}
|
||||
|
||||
.button:not([disabled]):hover {
|
||||
background: rgb(64, 68, 96, 0.7);
|
||||
cursor: pointer;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.button:not([disabled]):active {
|
||||
background: rgb(64, 68, 96, 1.0);
|
||||
}
|
Loading…
Reference in new issue