Skip to content

Commit 30da6c7

Browse files
committed
Display a checkmark in the name entry of input groups whose name matches their output
1 parent 4f6c77d commit 30da6c7

File tree

5 files changed

+80
-46
lines changed

5 files changed

+80
-46
lines changed

src/latest/scripts/interpreter/inputs.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export type Input = { id: string, input: string };
2-
export type InputGroup = { name: string, inputs: Input[] };
2+
export type InputGroup = { name: string, inputs: Input[], succeeded: boolean };
33
export type Inputs = InputGroup[];
44
export type InputsReducerAction = {
55
type: "add-group",
@@ -24,12 +24,17 @@ export type InputsReducerAction = {
2424
group: number,
2525
input: number,
2626
content: string,
27+
} | {
28+
type: "set-group-succeeded",
29+
group: number,
30+
} | {
31+
type: "reset-successes",
2732
};
2833

2934
export function inputsReducer(draft: Inputs, action: InputsReducerAction) {
3035
switch (action.type) {
3136
case "add-group": {
32-
draft.push({ name: `Group ${draft.length + 1}`, inputs: [{ id: crypto.randomUUID(), input: "" }] });
37+
draft.push({ name: `Group ${draft.length + 1}`, inputs: [{ id: crypto.randomUUID(), input: "" }], succeeded: false });
3338
break;
3439
}
3540
case "duplicate-group": {
@@ -58,6 +63,15 @@ export function inputsReducer(draft: Inputs, action: InputsReducerAction) {
5863
}
5964
case "set-input": {
6065
draft[action.group].inputs[action.input].input = action.content;
66+
break;
67+
}
68+
case "set-group-succeeded": {
69+
draft[action.group].succeeded = true;
70+
break;
71+
}
72+
case "reset-successes": {
73+
draft.forEach((group) => group.succeeded = false);
74+
break;
6175
}
6276
}
6377
}

src/latest/scripts/interpreter/runner.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@ export enum TerminateReason {
1313
export type VyRunnerEvents = {
1414
ready: Event,
1515
runningGroupChanged: CustomEvent<{ group: number | null }>,
16+
groupSucceeded: CustomEvent<{ group: number }>,
1617
};
1718

1819
export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
1920
private terminal: Terminal | null;
2021
private fit: FitAddon | null;
2122
private worker: Promise<Worker>;
2223
private workerCounter = 0;
23-
private outputBuffer: string[] = [];
24+
private outputBuffer: string[][] = [];
2425
private _state: "idle" | "booting" | "running" = "booting";
2526
private splashes: string[];
2627
private version: string;
@@ -122,8 +123,9 @@ export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
122123
}
123124
case "stdout":{
124125
this.terminal.write(data.text);
125-
this.outputBuffer.push(data.text);
126-
this.outputBuffer.length = Math.min(this.outputBuffer.length, MAX_BUFFER_SIZE);
126+
const buffer = this.outputBuffer.at(-1)!;
127+
buffer.push(data.text);
128+
buffer.length = Math.min(buffer.length, MAX_BUFFER_SIZE);
127129
break;
128130
}
129131
case "stderr":{
@@ -140,6 +142,11 @@ export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
140142
}
141143
const now = performance.now();
142144
this.terminal?.writeln(`\n\x1b[0G\x1b[0;2mFinished in ${Math.round((now - this.groupStartedAt)) / 1000} seconds\x1b[0m`);
145+
if (this.outputBuffer.at(-1)!.join("").trim() == this.inputs[this.currentGroup].name) {
146+
this.dispatchTypedEvent("groupSucceeded", new CustomEvent(
147+
"groupSucceeded", { detail: { group: this.currentGroup } },
148+
));
149+
}
143150
if (this.runAllGroups && this.inputs.length > 0 && ++this.currentGroup != this.inputs.length && this._state == "running") {
144151
this.runNextGroup(this.code, this.flags);
145152
} else {
@@ -159,6 +166,7 @@ export class VyRunner extends TypedEventTarget<VyRunnerEvents> {
159166
if (group != undefined) {
160167
this.terminal?.writeln(`\x1b[92mRunning group: ${group.name}\x1b[0m`);
161168
}
169+
this.outputBuffer.push([]);
162170
return this.worker.then((worker) => {
163171
this.groupStartedAt = performance.now();
164172
if (this.timeout != null) {

src/latest/scripts/ui/Inputs.tsx

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -39,74 +39,77 @@ function InputElement({ group, input, index, onInputChange, onInputDelete }: Inp
3939
}
4040

4141
type InputGroupElementProps = {
42-
group: number,
43-
inputs: InputGroup,
42+
groupIndex: number,
43+
group: InputGroup,
4444
dispatchInputs: Dispatch<InputsReducerAction>,
4545
state: RunState,
4646
run(): unknown,
4747
scrollTo(group: number): unknown,
4848
};
4949

50-
function InputGroupElement({ group, inputs: { name, inputs }, dispatchInputs, state, run, scrollTo }: InputGroupElementProps) {
50+
function InputGroupElement({ groupIndex, group: { name, inputs, succeeded }, dispatchInputs, state, run, scrollTo }: InputGroupElementProps) {
5151
return <div className="d-flex flex-column border rounded mb-2 mx-2">
5252
<div className="hstack bg-body-secondary p-2 border-bottom rounded-top">
53-
<BsInputGroup>
54-
<Button
55-
variant={state.name == "running" && state.group == group ? "danger" : "success"}
56-
onClick={() => run()}
57-
disabled={(state.name == "running" && state.group != group) || state.name == "starting"}
58-
>
59-
{
60-
(state.name == "running" && state.group == group) ? (
61-
<Spinner as="span" animation="border" role="status" className="spinner-border-sm">
62-
<span className="visually-hidden">Running program</span>
63-
</Spinner>
64-
) : (
65-
<i className="bi bi-play-fill"></i>
66-
)
67-
}
68-
</Button>
69-
<FormControl
70-
type="text"
71-
value={name}
72-
onInput={(e) => dispatchInputs({ type: "rename-group", group, name: e.currentTarget.value })}
73-
style={{ maxWidth: "200px" }}
74-
/>
75-
</BsInputGroup>
76-
<Button variant="secondary-bg" className="ms-auto me-2" onClick={() => dispatchInputs({ type: "delete-group", group })}>
53+
<div className="position-relative">
54+
<BsInputGroup className="w-auto">
55+
<Button
56+
variant={state.name == "running" && state.group == groupIndex ? "danger" : "success"}
57+
onClick={() => run()}
58+
disabled={(state.name == "running" && state.group != groupIndex) || state.name == "starting"}
59+
>
60+
{
61+
(state.name == "running" && state.group == groupIndex) ? (
62+
<Spinner as="span" animation="border" role="status" className="spinner-border-sm">
63+
<span className="visually-hidden">Running program</span>
64+
</Spinner>
65+
) : (
66+
<i className="bi bi-play-fill"></i>
67+
)
68+
}
69+
</Button>
70+
<FormControl
71+
type="text"
72+
value={name}
73+
onInput={(e) => dispatchInputs({ type: "rename-group", group: groupIndex, name: e.currentTarget.value })}
74+
style={{ maxWidth: "200px" }}
75+
/>
76+
</BsInputGroup>
77+
{succeeded && <i className="bi bi-check-square text-success position-absolute top-50 end-0 translate-middle-y me-2 bg-body"></i>}
78+
</div>
79+
<Button variant="secondary-bg" className="ms-auto me-2" onClick={() => dispatchInputs({ type: "delete-group", group: groupIndex })}>
7780
<i className="bi bi-trash2"></i>
7881
</Button>
7982
<Button
8083
variant="secondary-bg"
8184
className="me-2"
8285
onClick={() => {
8386
flushSync(() => {
84-
dispatchInputs({ type: "duplicate-group", group });
87+
dispatchInputs({ type: "duplicate-group", group: groupIndex });
8588
});
86-
scrollTo(group + 1);
89+
scrollTo(groupIndex + 1);
8790
}}
8891
>
8992
<i className="bi bi-copy"></i>
9093
</Button>
91-
<Button variant="primary" onClick={() => dispatchInputs({ type: "append-input", group })}>
94+
<Button variant="primary" onClick={() => dispatchInputs({ type: "append-input", group: groupIndex })}>
9295
<i className="bi bi-plus-circle"></i>
9396
</Button>
9497
</div>
9598
{inputs.length == 0 ? (
9699
<div className="m-2 text-center fs-6 info-text">
97100
<small>No inputs. Click <i className="bi bi-plus-circle"></i> to add one.</small>
98101
</div>
99-
) : <Droppable droppableId={group.toString()} type={`group-${group}`}>
102+
) : <Droppable droppableId={groupIndex.toString()} type={`group-${groupIndex}`}>
100103
{(provided) => {
101104
return <div ref={provided.innerRef} className="vstack mt-2" {...provided.droppableProps}>
102105
{inputs.map((input, index) => (
103106
<InputElement
104107
key={input.id}
105-
group={group}
108+
group={groupIndex}
106109
input={input}
107110
index={index}
108-
onInputChange={(input) => dispatchInputs({ type: "set-input", group, input: index, content: input })}
109-
onInputDelete={() => dispatchInputs({ type: "delete-input", group, input: index })}
111+
onInputChange={(input) => dispatchInputs({ type: "set-input", group: groupIndex, input: index, content: input })}
112+
onInputDelete={() => dispatchInputs({ type: "delete-input", group: groupIndex, input: index })}
110113
/>
111114
))}
112115
{provided.placeholder}
@@ -149,8 +152,8 @@ export function InputList({ inputs, dispatchInputs, state, run }: InputListProps
149152
{inputs.length > 0 ? inputs.map((inputs, index) => (
150153
<InputGroupElement
151154
key={index}
152-
group={index}
153-
inputs={inputs}
155+
groupIndex={index}
156+
group={inputs}
154157
dispatchInputs={dispatchInputs}
155158
run={() => run(index)}
156159
scrollTo={scrollTo}

src/latest/scripts/ui/Theseus.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function Theseus({ permalink }: TheseusProps) {
6363
const [header, setHeader] = useState(permalink?.header ?? "");
6464
const [code, setCode] = useState(permalink?.code ?? "");
6565
const [footer, setFooter] = useState(permalink?.footer ?? "");
66-
const [inputs, dispatchInputs] = useImmerReducer(inputsReducer, permalink?.inputs?.map(([name, inputs]) => ({ name, inputs: inputs.map((input) => ({ id: crypto.randomUUID(), input })) })) ?? []);
66+
const [inputs, dispatchInputs] = useImmerReducer(inputsReducer, permalink?.inputs?.map(([name, inputs]) => ({ name, inputs: inputs.map((input) => ({ id: crypto.randomUUID(), input })), succeeded: false })) ?? []);
6767
const [bytecount, setBytecount] = useState("...");
6868
const autorun = (header + code + footer).length > 0;
6969

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

141141
const onRunClicked = useCallback((group: number | null) => {
142+
dispatchInputs({ type: "reset-successes" });
142143
if (runnerRef.current != null) {
143144
switch (state.name) {
144145
case "starting":
@@ -151,7 +152,7 @@ export function Theseus({ permalink }: TheseusProps) {
151152
break;
152153
}
153154
}
154-
}, [code, flags, inputs, timeout, runnerRef, state]);
155+
}, [code, flags, inputs, timeout, runnerRef, state, dispatchInputs]);
155156

156157
return <>
157158
<SettingsDialog
@@ -249,6 +250,9 @@ export function Theseus({ permalink }: TheseusProps) {
249250
setState({ name: "idle" });
250251
}
251252
}, [])}
253+
onGroupSucceeded={useCallback((group) => {
254+
dispatchInputs({ type: "set-group-succeeded", group });
255+
}, [dispatchInputs])}
252256
onReady={useCallback(() => {
253257
if (autorun) {
254258
onRunClicked(null);

src/latest/scripts/ui/VyTerminal.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@ export interface VyTerminalRef {
1414

1515
type VyTerminalProps = {
1616
onRunningGroupChanged: (group: number | null) => unknown,
17+
onGroupSucceeded: (group: number) => unknown,
1718
onReady?: () => unknown,
1819
};
1920

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

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

30+
const groupSucceededCallback = useCallback((e: VyRunnerEvents["groupSucceeded"]) => {
31+
onGroupSucceeded(e.detail.group);
32+
}, [onGroupSucceeded]);
33+
3034
useImperativeHandle(ref, () => {
3135
return {
3236
start(code, flags, inputs, group, timeout) {
@@ -46,11 +50,12 @@ const VyTerminal = forwardRef(function VyTerminal({ onRunningGroupChanged, onRea
4650

4751
useEffect(() => {
4852
runner.addEventListener("runningGroupChanged", runningGroupChangedCallback);
53+
runner.addEventListener("groupSucceeded", groupSucceededCallback);
4954
runner.addEventListener("ready", () => onReady?.() as void, { once: true });
5055
return () => {
5156
runner.removeEventListener("runningGroupChanged", runningGroupChangedCallback);
5257
};
53-
}, [onReady, runner, runningGroupChangedCallback]);
58+
}, [onReady, runner, runningGroupChangedCallback, groupSucceededCallback]);
5459

5560
useEffect(() => {
5661
runner.attach(wrapperRef.current!);

0 commit comments

Comments
 (0)