Skip to content

Commit

Permalink
Display a checkmark in the name entry of input groups whose name matc…
Browse files Browse the repository at this point in the history
…hes their output
  • Loading branch information
gingershaped committed Dec 2, 2024
1 parent 4f6c77d commit 30da6c7
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 46 deletions.
18 changes: 16 additions & 2 deletions src/latest/scripts/interpreter/inputs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type Input = { id: string, input: string };
export type InputGroup = { name: string, inputs: Input[] };
export type InputGroup = { name: string, inputs: Input[], succeeded: boolean };
export type Inputs = InputGroup[];
export type InputsReducerAction = {
type: "add-group",
Expand All @@ -24,12 +24,17 @@ export type InputsReducerAction = {
group: number,
input: number,
content: string,
} | {
type: "set-group-succeeded",
group: number,
} | {
type: "reset-successes",
};

export function inputsReducer(draft: Inputs, action: InputsReducerAction) {
switch (action.type) {
case "add-group": {
draft.push({ name: `Group ${draft.length + 1}`, inputs: [{ id: crypto.randomUUID(), input: "" }] });
draft.push({ name: `Group ${draft.length + 1}`, inputs: [{ id: crypto.randomUUID(), input: "" }], succeeded: false });
break;
}
case "duplicate-group": {
Expand Down Expand Up @@ -58,6 +63,15 @@ export function inputsReducer(draft: Inputs, action: InputsReducerAction) {
}
case "set-input": {
draft[action.group].inputs[action.input].input = action.content;
break;
}
case "set-group-succeeded": {
draft[action.group].succeeded = true;
break;
}
case "reset-successes": {
draft.forEach((group) => group.succeeded = false);
break;
}
}
}
14 changes: 11 additions & 3 deletions src/latest/scripts/interpreter/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ export enum TerminateReason {
export type VyRunnerEvents = {
ready: Event,
runningGroupChanged: CustomEvent<{ group: number | null }>,
groupSucceeded: CustomEvent<{ group: number }>,
};

export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
private terminal: Terminal | null;
private fit: FitAddon | null;
private worker: Promise<Worker>;
private workerCounter = 0;
private outputBuffer: string[] = [];
private outputBuffer: string[][] = [];
private _state: "idle" | "booting" | "running" = "booting";
private splashes: string[];
private version: string;
Expand Down Expand Up @@ -122,8 +123,9 @@ export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
}
case "stdout":{
this.terminal.write(data.text);
this.outputBuffer.push(data.text);
this.outputBuffer.length = Math.min(this.outputBuffer.length, MAX_BUFFER_SIZE);
const buffer = this.outputBuffer.at(-1)!;
buffer.push(data.text);
buffer.length = Math.min(buffer.length, MAX_BUFFER_SIZE);
break;
}
case "stderr":{
Expand All @@ -140,6 +142,11 @@ export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
}
const now = performance.now();
this.terminal?.writeln(`\n\x1b[0G\x1b[0;2mFinished in ${Math.round((now - this.groupStartedAt)) / 1000} seconds\x1b[0m`);
if (this.outputBuffer.at(-1)!.join("").trim() == this.inputs[this.currentGroup].name) {
this.dispatchTypedEvent("groupSucceeded", new CustomEvent(
"groupSucceeded", { detail: { group: this.currentGroup } },
));
}
if (this.runAllGroups && this.inputs.length > 0 && ++this.currentGroup != this.inputs.length && this._state == "running") {
this.runNextGroup(this.code, this.flags);
} else {
Expand All @@ -159,6 +166,7 @@ export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
if (group != undefined) {
this.terminal?.writeln(`\x1b[92mRunning group: ${group.name}\x1b[0m`);
}
this.outputBuffer.push([]);
return this.worker.then((worker) => {
this.groupStartedAt = performance.now();
if (this.timeout != null) {
Expand Down
75 changes: 39 additions & 36 deletions src/latest/scripts/ui/Inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,74 +39,77 @@ function InputElement({ group, input, index, onInputChange, onInputDelete }: Inp
}

type InputGroupElementProps = {
group: number,
inputs: InputGroup,
groupIndex: number,
group: InputGroup,
dispatchInputs: Dispatch<InputsReducerAction>,
state: RunState,
run(): unknown,
scrollTo(group: number): unknown,
};

function InputGroupElement({ group, inputs: { name, inputs }, dispatchInputs, state, run, scrollTo }: InputGroupElementProps) {
function InputGroupElement({ groupIndex, group: { name, inputs, succeeded }, dispatchInputs, state, run, scrollTo }: InputGroupElementProps) {
return <div className="d-flex flex-column border rounded mb-2 mx-2">
<div className="hstack bg-body-secondary p-2 border-bottom rounded-top">
<BsInputGroup>
<Button
variant={state.name == "running" && state.group == group ? "danger" : "success"}
onClick={() => run()}
disabled={(state.name == "running" && state.group != group) || state.name == "starting"}
>
{
(state.name == "running" && state.group == group) ? (
<Spinner as="span" animation="border" role="status" className="spinner-border-sm">
<span className="visually-hidden">Running program</span>
</Spinner>
) : (
<i className="bi bi-play-fill"></i>
)
}
</Button>
<FormControl
type="text"
value={name}
onInput={(e) => dispatchInputs({ type: "rename-group", group, name: e.currentTarget.value })}
style={{ maxWidth: "200px" }}
/>
</BsInputGroup>
<Button variant="secondary-bg" className="ms-auto me-2" onClick={() => dispatchInputs({ type: "delete-group", group })}>
<div className="position-relative">
<BsInputGroup className="w-auto">
<Button
variant={state.name == "running" && state.group == groupIndex ? "danger" : "success"}
onClick={() => run()}
disabled={(state.name == "running" && state.group != groupIndex) || state.name == "starting"}
>
{
(state.name == "running" && state.group == groupIndex) ? (
<Spinner as="span" animation="border" role="status" className="spinner-border-sm">
<span className="visually-hidden">Running program</span>
</Spinner>
) : (
<i className="bi bi-play-fill"></i>
)
}
</Button>
<FormControl
type="text"
value={name}
onInput={(e) => dispatchInputs({ type: "rename-group", group: groupIndex, name: e.currentTarget.value })}
style={{ maxWidth: "200px" }}
/>
</BsInputGroup>
{succeeded && <i className="bi bi-check-square text-success position-absolute top-50 end-0 translate-middle-y me-2 bg-body"></i>}
</div>
<Button variant="secondary-bg" className="ms-auto me-2" onClick={() => dispatchInputs({ type: "delete-group", group: groupIndex })}>
<i className="bi bi-trash2"></i>
</Button>
<Button
variant="secondary-bg"
className="me-2"
onClick={() => {
flushSync(() => {
dispatchInputs({ type: "duplicate-group", group });
dispatchInputs({ type: "duplicate-group", group: groupIndex });
});
scrollTo(group + 1);
scrollTo(groupIndex + 1);
}}
>
<i className="bi bi-copy"></i>
</Button>
<Button variant="primary" onClick={() => dispatchInputs({ type: "append-input", group })}>
<Button variant="primary" onClick={() => dispatchInputs({ type: "append-input", group: groupIndex })}>
<i className="bi bi-plus-circle"></i>
</Button>
</div>
{inputs.length == 0 ? (
<div className="m-2 text-center fs-6 info-text">
<small>No inputs. Click <i className="bi bi-plus-circle"></i> to add one.</small>
</div>
) : <Droppable droppableId={group.toString()} type={`group-${group}`}>
) : <Droppable droppableId={groupIndex.toString()} type={`group-${groupIndex}`}>
{(provided) => {
return <div ref={provided.innerRef} className="vstack mt-2" {...provided.droppableProps}>
{inputs.map((input, index) => (
<InputElement
key={input.id}
group={group}
group={groupIndex}
input={input}
index={index}
onInputChange={(input) => dispatchInputs({ type: "set-input", group, input: index, content: input })}
onInputDelete={() => dispatchInputs({ type: "delete-input", group, input: index })}
onInputChange={(input) => dispatchInputs({ type: "set-input", group: groupIndex, input: index, content: input })}
onInputDelete={() => dispatchInputs({ type: "delete-input", group: groupIndex, input: index })}
/>
))}
{provided.placeholder}
Expand Down Expand Up @@ -149,8 +152,8 @@ export function InputList({ inputs, dispatchInputs, state, run }: InputListProps
{inputs.length > 0 ? inputs.map((inputs, index) => (
<InputGroupElement
key={index}
group={index}
inputs={inputs}
groupIndex={index}
group={inputs}
dispatchInputs={dispatchInputs}
run={() => run(index)}
scrollTo={scrollTo}
Expand Down
8 changes: 6 additions & 2 deletions src/latest/scripts/ui/Theseus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function Theseus({ permalink }: TheseusProps) {
const [header, setHeader] = useState(permalink?.header ?? "");
const [code, setCode] = useState(permalink?.code ?? "");
const [footer, setFooter] = useState(permalink?.footer ?? "");
const [inputs, dispatchInputs] = useImmerReducer(inputsReducer, permalink?.inputs?.map(([name, inputs]) => ({ name, inputs: inputs.map((input) => ({ id: crypto.randomUUID(), input })) })) ?? []);
const [inputs, dispatchInputs] = useImmerReducer(inputsReducer, permalink?.inputs?.map(([name, inputs]) => ({ name, inputs: inputs.map((input) => ({ id: crypto.randomUUID(), input })), succeeded: false })) ?? []);
const [bytecount, setBytecount] = useState("...");
const autorun = (header + code + footer).length > 0;

Expand Down Expand Up @@ -139,6 +139,7 @@ export function Theseus({ permalink }: TheseusProps) {
}, [code, runnerRef, utilWorker]);

const onRunClicked = useCallback((group: number | null) => {
dispatchInputs({ type: "reset-successes" });
if (runnerRef.current != null) {
switch (state.name) {
case "starting":
Expand All @@ -151,7 +152,7 @@ export function Theseus({ permalink }: TheseusProps) {
break;
}
}
}, [code, flags, inputs, timeout, runnerRef, state]);
}, [code, flags, inputs, timeout, runnerRef, state, dispatchInputs]);

return <>
<SettingsDialog
Expand Down Expand Up @@ -249,6 +250,9 @@ export function Theseus({ permalink }: TheseusProps) {
setState({ name: "idle" });
}
}, [])}
onGroupSucceeded={useCallback((group) => {
dispatchInputs({ type: "set-group-succeeded", group });
}, [dispatchInputs])}
onReady={useCallback(() => {
if (autorun) {
onRunClicked(null);
Expand Down
11 changes: 8 additions & 3 deletions src/latest/scripts/ui/VyTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@ export interface VyTerminalRef {

type VyTerminalProps = {
onRunningGroupChanged: (group: number | null) => unknown,
onGroupSucceeded: (group: number) => unknown,
onReady?: () => unknown,
};

const VyTerminal = forwardRef(function VyTerminal({ onRunningGroupChanged, onReady }: VyTerminalProps, ref: ForwardedRef<VyTerminalRef>) {
const VyTerminal = forwardRef(function VyTerminal({ onRunningGroupChanged, onGroupSucceeded, onReady }: VyTerminalProps, ref: ForwardedRef<VyTerminalRef>) {
const wrapperRef = useRef(null);
const elementData = useContext(ElementDataContext)!;
const runner = useMemo(() => new VyRunner(splashes.trim().split("\n"), elementData!.version), [elementData]);

const runningGroupChangedCallback = useCallback((e: VyRunnerEvents["runningGroupChanged"]) => {
console.log(e.detail.group);
onRunningGroupChanged(e.detail.group);
}, [onRunningGroupChanged]);

const groupSucceededCallback = useCallback((e: VyRunnerEvents["groupSucceeded"]) => {
onGroupSucceeded(e.detail.group);
}, [onGroupSucceeded]);

useImperativeHandle(ref, () => {
return {
start(code, flags, inputs, group, timeout) {
Expand All @@ -46,11 +50,12 @@ const VyTerminal = forwardRef(function VyTerminal({ onRunningGroupChanged, onRea

useEffect(() => {
runner.addEventListener("runningGroupChanged", runningGroupChangedCallback);
runner.addEventListener("groupSucceeded", groupSucceededCallback);
runner.addEventListener("ready", () => onReady?.() as void, { once: true });
return () => {
runner.removeEventListener("runningGroupChanged", runningGroupChangedCallback);
};
}, [onReady, runner, runningGroupChangedCallback]);
}, [onReady, runner, runningGroupChangedCallback, groupSucceededCallback]);

useEffect(() => {
runner.attach(wrapperRef.current!);
Expand Down

0 comments on commit 30da6c7

Please sign in to comment.