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) {
|
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>);
|
</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