Skip to content

Commit 629063d

Browse files
committed
chore: move code and outputfrom zustand
1 parent e7f44ee commit 629063d

File tree

8 files changed

+235
-162
lines changed

8 files changed

+235
-162
lines changed

preview/main.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import (
1616
"github.com/hashicorp/hcl/v2"
1717
"github.com/spf13/afero"
1818

19+
"github.com/coder/parameters-playground/preview/apitypes"
1920
"github.com/coder/preview"
2021
"github.com/coder/preview/types"
21-
"github.com/coder/parameters-playground/preview/apitypes"
2222
)
2323

2424
func main() {
@@ -60,25 +60,29 @@ func tfpreview(this js.Value, p []js.Value) (output any) {
6060
return err
6161
}
6262

63-
owner, err := workspaceOwner(p[1])
64-
if err != nil {
65-
return err
66-
}
67-
6863
handler := slog.NewJSONHandler(l, nil)
6964
logger := slog.New(handler)
7065

7166
var parameters map[string]string
72-
if len(p) >= 3 {
73-
params, err := jsValueToStringMap(p[2])
67+
if len(p) >= 2 {
68+
params, err := jsValueToStringMap(p[1])
7469
if err != nil {
7570
logger.Error("Unable to convert second prameter into map[string]string", "err", err)
7671
}
7772

7873
parameters = params
7974
} else {
8075
logger.Error(fmt.Sprintf("Expected 2 arguments but got %v", len(p)))
76+
}
77+
78+
owner := apitypes.WorkspaceOwner{}
79+
if len(p) >= 3 {
80+
o, err := workspaceOwner(p[2])
81+
if err != nil {
82+
logger.Error("Unable to convert third parameter into WorkspaceOwner", "err", err)
83+
}
8184

85+
owner = o
8286
}
8387

8488
pOutput, diags := preview.Preview(context.Background(), preview.Input{

public/build/preview.wasm

495 Bytes
Binary file not shown.

src/client/App.tsx

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,14 @@ import {
55
ResizableHandle,
66
ResizablePanelGroup,
77
} from "@/client/components/Resizable";
8-
import { useStore } from "@/client/store";
98
import {
109
DropdownMenu,
1110
DropdownMenuContent,
1211
DropdownMenuItem,
1312
DropdownMenuPortal,
1413
DropdownMenuTrigger,
1514
} from "@/client/components/DropdownMenu";
16-
import {
17-
type FC,
18-
useCallback,
19-
useEffect,
20-
useMemo,
21-
useRef,
22-
useState,
23-
} from "react";
15+
import { type FC, useEffect, useMemo, useRef, useState } from "react";
2416
import { useTheme } from "@/client/contexts/theme";
2517
import { MoonIcon, ShareIcon, SunIcon, SunMoonIcon } from "lucide-react";
2618
import { Button } from "@/client/components/Button";
@@ -31,7 +23,14 @@ import {
3123
} from "@/client/components/Tooltip";
3224
import { rpc } from "@/utils/rpc";
3325
import { useLoaderData, type LoaderFunctionArgs } from "react-router";
34-
import { initWasm, type WasmLoadState } from "@/utils/wasm";
26+
import {
27+
getDynamicParametersOutput,
28+
initWasm,
29+
type WasmLoadState,
30+
} from "@/utils/wasm";
31+
import { defaultCode } from "@/client/snippets";
32+
import type { PreviewOutput } from "@/gen/types";
33+
import { useDebouncedValue } from "./hooks/debounce";
3534

3635
/**
3736
* Load the shared code if present.
@@ -61,16 +60,35 @@ export const App = () => {
6160
}
6261
return "loading";
6362
});
64-
const $setCode = useStore((store) => store.setCode);
65-
const code = useLoaderData<typeof loader>();
63+
const loadedCode = useLoaderData<typeof loader>();
64+
const [code, setCode] = useState(loadedCode ?? defaultCode);
65+
const [debouncedCode, isDebouncing] = useDebouncedValue(code, 1000);
66+
const [parameterValues, setParameterValues] = useState<
67+
Record<string, string>
68+
>({});
69+
const [output, setOutput] = useState<PreviewOutput | null>(null);
6670

67-
useEffect(() => {
68-
if (!code) {
69-
return;
70-
}
71+
const onDownloadOutput = () => {
72+
const blob = new Blob([JSON.stringify(output, null, 2)], {
73+
type: "application/json",
74+
});
75+
76+
const url = URL.createObjectURL(blob);
7177

72-
$setCode(code);
73-
}, [code, $setCode]);
78+
const link = document.createElement("a");
79+
link.href = url;
80+
link.download = "output.json";
81+
document.body.appendChild(link);
82+
link.click();
83+
document.body.removeChild(link);
84+
85+
// Give the click event enough time to fire and then revoke the URL.
86+
// This method of doing it doesn't seem great but I'm not sure if there is a
87+
// better way.
88+
setTimeout(() => {
89+
URL.revokeObjectURL(url);
90+
}, 100);
91+
};
7492

7593
useEffect(() => {
7694
if (!window.go_preview) {
@@ -84,6 +102,19 @@ export const App = () => {
84102
}
85103
}, []);
86104

105+
useEffect(() => {
106+
getDynamicParametersOutput(debouncedCode, parameterValues)
107+
.catch((e) => {
108+
console.error(e);
109+
setWasmLoadingState("error");
110+
111+
return null;
112+
})
113+
.then((output) => {
114+
setOutput(output);
115+
});
116+
}, [debouncedCode, parameterValues]);
117+
87118
return (
88119
<main className="flex h-dvh w-screen flex-col items-center bg-surface-primary">
89120
{/* NAV BAR */}
@@ -96,7 +127,7 @@ export const App = () => {
96127
</p>
97128
</div>
98129

99-
<ShareButton />
130+
<ShareButton code={code} />
100131
</div>
101132

102133
<div className="flex items-center gap-3">
@@ -130,12 +161,17 @@ export const App = () => {
130161

131162
<ResizablePanelGroup direction={"horizontal"}>
132163
{/* EDITOR */}
133-
<Editor />
164+
<Editor code={code} setCode={setCode} />
134165

135166
<ResizableHandle className="bg-surface-quaternary" />
136167

137168
{/* PREVIEW */}
138-
<Preview wasmLoadState={wasmLoadState} />
169+
<Preview
170+
wasmLoadState={wasmLoadState}
171+
isDebouncing={isDebouncing}
172+
onDownloadOutput={onDownloadOutput}
173+
output={output}
174+
/>
139175
</ResizablePanelGroup>
140176
</main>
141177
);
@@ -180,15 +216,17 @@ const ThemeSelector: FC = () => {
180216
);
181217
};
182218

183-
const ShareButton: FC = () => {
184-
const $code = useStore((state) => state.code);
219+
type ShareButtonProps = {
220+
code: string;
221+
};
222+
const ShareButton: FC<ShareButtonProps> = ({ code }) => {
185223
const [isCopied, setIsCopied] = useState(() => false);
186224
const timeoutId = useRef<ReturnType<typeof setTimeout>>(undefined);
187225

188-
const onShare = useCallback(async () => {
226+
const onShare = async () => {
189227
try {
190228
const { id } = await rpc.parameters
191-
.$post({ json: { code: $code } })
229+
.$post({ json: { code } })
192230
.then((res) => res.json());
193231

194232
const { protocol, host } = window.location;
@@ -200,7 +238,7 @@ const ShareButton: FC = () => {
200238
} catch (e) {
201239
console.error(e);
202240
}
203-
}, [$code]);
241+
};
204242

205243
useEffect(() => {
206244
if (!isCopied) {

src/client/Editor.tsx

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import {
1313
TooltipContent,
1414
TooltipTrigger,
1515
} from "@/client/components/Tooltip";
16+
import { useTheme } from "@/client/contexts/theme";
17+
import { multiSelect, radio, switchInput, textInput } from "@/client/snippets";
1618
import { useStore } from "@/client/store";
19+
import type { ParameterFormType } from "@/gen/types";
20+
import { cn } from "@/utils/cn";
21+
import { Editor as MonacoEditor } from "@monaco-editor/react";
1722
import {
1823
CheckIcon,
1924
ChevronDownIcon,
@@ -26,19 +31,16 @@ import {
2631
ToggleLeftIcon,
2732
ZapIcon,
2833
} from "lucide-react";
29-
import { type FC, useCallback, useEffect, useRef, useState } from "react";
30-
import { Editor as MonacoEditor } from "@monaco-editor/react";
34+
import { type FC, useEffect, useRef, useState } from "react";
3135

32-
import { cn } from "@/utils/cn";
33-
import type { ParameterFormType } from "@/gen/types";
34-
import { multiSelect, radio, switchInput, textInput } from "@/client/snippets";
35-
import { useTheme } from "@/client/contexts/theme";
36+
type EditorProps = {
37+
code: string;
38+
setCode: React.Dispatch<React.SetStateAction<string>>;
39+
};
3640

37-
export const Editor: FC = () => {
41+
export const Editor: FC<EditorProps> = ({ code, setCode }) => {
3842
const { appliedTheme } = useTheme();
3943

40-
const $code = useStore((state) => state.code);
41-
const $setCode = useStore((state) => state.setCode);
4244
const $setEditor = useStore((state) => state.setEditor);
4345

4446
const [codeCopied, setCodeCopied] = useState(() => false);
@@ -49,24 +51,21 @@ export const Editor: FC = () => {
4951
const [tab, setTab] = useState(() => "code");
5052

5153
const onCopy = () => {
52-
navigator.clipboard.writeText($code);
54+
navigator.clipboard.writeText(code);
5355
setCodeCopied(() => true);
5456
};
5557

56-
const onAddSnippet = useCallback(
57-
(formType: ParameterFormType) => {
58-
if (formType === "input") {
59-
$setCode(`${$code.trimEnd()}\n\n${textInput}\n`);
60-
} else if (formType === "radio") {
61-
$setCode(`${$code.trimEnd()}\n\n${radio}\n`);
62-
} else if (formType === "multi-select") {
63-
$setCode(`${$code.trimEnd()}\n\n${multiSelect}\n`);
64-
} else if (formType === "switch") {
65-
$setCode(`${$code.trimEnd()}\n\n${switchInput}\n`);
66-
}
67-
},
68-
[$code, $setCode],
69-
);
58+
const onAddSnippet = (formType: ParameterFormType) => {
59+
if (formType === "input") {
60+
setCode(`${code.trimEnd()}\n\n${textInput}\n`);
61+
} else if (formType === "radio") {
62+
setCode(`${code.trimEnd()}\n\n${radio}\n`);
63+
} else if (formType === "multi-select") {
64+
setCode(`${code.trimEnd()}\n\n${multiSelect}\n`);
65+
} else if (formType === "switch") {
66+
setCode(`${code.trimEnd()}\n\n${switchInput}\n`);
67+
}
68+
};
7069

7170
useEffect(() => {
7271
if (!codeCopied) {
@@ -161,11 +160,11 @@ export const Editor: FC = () => {
161160
<Tabs.Content value="code" asChild={true}>
162161
<div className="h-full w-full bg-surface-secondary font-mono">
163162
<MonacoEditor
164-
value={$code}
163+
value={code}
165164
onMount={(editor) => $setEditor(editor)}
166165
onChange={(code) => {
167166
if (code !== undefined) {
168-
$setCode(code);
167+
setCode(code);
169168
}
170169
}}
171170
theme={appliedTheme === "dark" ? "vs-dark" : "vs-light"}

0 commit comments

Comments
 (0)