diff --git a/src/client/App.tsx b/src/client/App.tsx index d440302..4c1392e 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -181,7 +181,7 @@ export const App = () => { Coder { {/* EDITOR */} - + diff --git a/src/client/Editor.tsx b/src/client/Editor.tsx index 87c5fc8..b7068e5 100644 --- a/src/client/Editor.tsx +++ b/src/client/Editor.tsx @@ -14,8 +14,7 @@ import { TooltipTrigger, } from "@/client/components/Tooltip"; import { useTheme } from "@/client/contexts/theme"; -import { multiSelect, radio, switchInput, textInput } from "@/client/snippets"; -import type { ParameterFormType } from "@/gen/types"; +import { snippets, type SnippetFunc } from "@/client/snippets"; import { cn } from "@/utils/cn"; import { Editor as MonacoEditor } from "@monaco-editor/react"; import { @@ -23,47 +22,48 @@ import { ChevronDownIcon, CopyIcon, FileJsonIcon, - RadioIcon, SettingsIcon, - SquareMousePointerIcon, - TextCursorInputIcon, - ToggleLeftIcon, ZapIcon, } from "lucide-react"; import { type FC, useEffect, useRef, useState } from "react"; import { useEditor } from "@/client/contexts/editor"; +import type { ParameterWithSource } from "@/gen/types"; type EditorProps = { code: string; setCode: React.Dispatch>; + parameters: ParameterWithSource[]; }; -export const Editor: FC = ({ code, setCode }) => { +export const Editor: FC = ({ code, setCode, parameters }) => { const { appliedTheme } = useTheme(); const editorRef = useEditor(); + const [tab, setTab] = useState(() => "code"); + const [codeCopied, setCodeCopied] = useState(() => false); const copyTimeoutId = useRef | undefined>( undefined, ); - const [tab, setTab] = useState(() => "code"); - const onCopy = () => { navigator.clipboard.writeText(code); setCodeCopied(() => true); }; - const onAddSnippet = (formType: ParameterFormType) => { - if (formType === "input") { - setCode(`${code.trimEnd()}\n\n${textInput}\n`); - } else if (formType === "radio") { - setCode(`${code.trimEnd()}\n\n${radio}\n`); - } else if (formType === "multi-select") { - setCode(`${code.trimEnd()}\n\n${multiSelect}\n`); - } else if (formType === "switch") { - setCode(`${code.trimEnd()}\n\n${switchInput}\n`); - } + const onAddSnippet = (name: string, snippet: SnippetFunc) => { + const nextInOrder = + parameters.reduce( + (highestOrder, parameter) => + highestOrder < parameter.order ? parameter.order : highestOrder, + 0, + ) + 1; + + const nameCount = parameters.filter((p) => p.name.startsWith(name)).length; + + setCode( + `${code.trimEnd()}\n\n${snippet(nameCount > 0 ? `${name}-${nameCount}` : name, nextInOrder)}\n`, + ); }; useEffect(() => { @@ -116,23 +116,17 @@ export const Editor: FC = ({ code, setCode }) => { - onAddSnippet("input")}> - - Text input - - onAddSnippet("multi-select")} - > - - Multi-select - - onAddSnippet("radio")}> - - Radio - - onAddSnippet("switch")}> - Switches - + {snippets.map( + ({ name, label, icon: Icon, snippet }, index) => ( + onAddSnippet(name, snippet)} + > + + {label} + + ), + )} diff --git a/src/client/Preview.tsx b/src/client/Preview.tsx index 9ca2d27..3e5e514 100644 --- a/src/client/Preview.tsx +++ b/src/client/Preview.tsx @@ -239,7 +239,9 @@ const PreviewEmptyState = () => {

Read the docs diff --git a/src/client/snippets.ts b/src/client/snippets.ts index 26b8fef..4c94dbe 100644 --- a/src/client/snippets.ts +++ b/src/client/snippets.ts @@ -1,3 +1,14 @@ +import { + LetterTextIcon, + RadioIcon, + Rows3Icon, + Settings2Icon, + SquareMousePointerIcon, + TagIcon, + TextCursorInputIcon, + ToggleLeftIcon, +} from "lucide-react"; + export const defaultCode = `terraform { required_providers { coder = { @@ -7,25 +18,104 @@ export const defaultCode = `terraform { } }`; -export const textInput = `data "coder_parameter" "project-name" { - display_name = "An input" - name = "an-input" - description = "What is the name of your project?" - order = 4 +export type SnippetFunc = (name?: string, order?: number) => string; +type Snippet = { + name: string; + label: string; + icon: typeof RadioIcon; + snippet: SnippetFunc; +}; + +export const input: SnippetFunc = ( + name = "input", + order = 1, +) => `data "coder_parameter" "text-input" { + name = "${name}" + display_name = "A text input" + description = "This parameter can be used to input text." + order = ${order} + + styling = jsonencode({ + placeholder = "A placeholder that will appear if the input value is empty" + }) form_type = "input" type = "string" default = "An input value" }`; -export const radio = `data "coder_parameter" "radio" { - name = "radio" - display_name = "An example of a radio input" - description = "The next parameter supports a single value." - type = "string" - form_type = "radio" - order = 1 - default = "option-1" +export const textarea: SnippetFunc = ( + name = "textarea", + order = 1, +) => `data "coder_parameter" "textarea" { + name = "${name}" + display_name = "A textarea input" + description = "This parameter can be used to input multiple lines of text" + order = ${order} + + styling = jsonencode({ + placeholder = "A placeholder that will appear if the input value is empty" + }) + + form_type = "textarea" + type = "string" + default = "An input value" +}`; + +export const radio: SnippetFunc = ( + name = "radio", + order = 1, +) => `data "coder_parameter" "radio" { + name = "${name}" + display_name = "A radio input" + description = "This parameter supports selecting a single value out of a list of options" + order = ${order} + + type = "string" + form_type = "radio" + default = "option-1" + + option { + name = "Option 1" + value = "option-1" + description = "A description for Option 1" + } + + option { + name = "Option 2" + value = "option-2" + description = "A description for Option 2" + } + + option { + name = "Option 3" + value = "option-3" + description = "A description for Option 3" + } + + option { + name = "Option 4" + value = "option-4" + description = "A description for Option 4" + } +}`; + +export const dropdown: SnippetFunc = ( + name = "dropdown", + order = 1, +) => `data "coder_parameter" "dropdown" { + name = "${name}" + display_name = "A dropdown input" + description = "This parameter supports selecting a single value out of a list of options. Especially useful when you have a lot of options." + order = ${order} + + styling = jsonencode({ + placeholder = "A placeholder that will appear if the input value is empty" + }) + + type = "string" + form_type = "dropdown" + default = "option-1" option { name = "Option 1" @@ -52,13 +142,17 @@ export const radio = `data "coder_parameter" "radio" { } }`; -export const multiSelect = `data "coder_parameter" "multi-select" { - name = "multi-select" - display_name = "An example of a multi-select" - description = "The next parameter supports multiple values." +export const multiSelect: SnippetFunc = ( + name = "multi-select", + order = 1, +) => `data "coder_parameter" "multi-select" { + name = "${name}" + display_name = "A multi-select input" + description = "This parameter supports selecting multiple values from a list of options" + order = ${order} + type = "list(string)" form_type = "multi-select" - order = 1 option { name = "Option 1" @@ -85,15 +179,101 @@ export const multiSelect = `data "coder_parameter" "multi-select" { } }`; -export const switchInput = `data "coder_parameter" "switch" { - name = "switch" - display_name = "An example of a switch" - description = "The next parameter can be on or off" +export const tagSelect: SnippetFunc = ( + name = "tag-select", + order = 1, +) => `data "coder_parameter" "tag-select" { + name = "${name}" + display_name = "A tag-select input" + description = "This parameter supports selecting multiple user inputed values at once" + order = ${order} + + type = "list(string)" + form_type = "tag-select" +}`; + +export const switchInput: SnippetFunc = ( + name = "switch", + order = 1, +) => `data "coder_parameter" "switch" { + name = "${name}" + display_name = "A switch input" + description = "This parameter can be toggled between true and false" + order = ${order} + type = "bool" form_type = "switch" default = true - order = 1 -}` +}`; + +export const slider: SnippetFunc = ( + name = "slider", + order = 1, +) => `data "coder_parameter" "slider" { + name = "${name}" + display_name = "A slider input" + description = "This parameter supports selecting a number within a given range" + type = "number" + form_type = "slider" + default = 6 + order = ${order} + + validation { + min = 1 + max = 10 + } +}`; + +export const snippets: Snippet[] = [ + { + name: "text-input", + label: "Text Input", + icon: TextCursorInputIcon, + snippet: input, + }, + { + name: "textarea", + label: "Textarea", + icon: LetterTextIcon, + snippet: textarea, + }, + { + name: "radio", + label: "Radio", + icon: RadioIcon, + snippet: radio, + }, + { + name: "switch", + label: "Multi-select", + icon: SquareMousePointerIcon, + snippet: multiSelect, + }, + { + name: "tag-select", + label: "Tag-select", + icon: TagIcon, + snippet: tagSelect, + }, + { + name: "switch", + label: "Switch", + icon: ToggleLeftIcon, + snippet: switchInput, + }, + { + name: "dropdown", + label: "Dropdown", + icon: Rows3Icon, + snippet: dropdown, + }, + { + name: "slider", + label: "Slider", + icon: Settings2Icon, + snippet: slider, + }, +]; export const checkerModule = ` variable "solutions" {