diff --git a/internal/components/components.templ b/internal/components/components.templ index 2ccf054..7675d32 100644 --- a/internal/components/components.templ +++ b/internal/components/components.templ @@ -41,6 +41,31 @@ templ title() { } +// Hacky function to replace the textarea with formatted Jsonnet. +// +// TODO(jdockerty): there may be a nicer way to do this with htmx, but disabling the +// functionality of the hx-post and replacing the textarea value did not work +// in the same way as the other htmx swaps, it had very odd behaviour instead. +script handleFormat() { + var data = new FormData(); + data.append("jsonnet-input", jsonnetInput.value); + fetch("/api/format", { + // Using URLSearchParams means we send the expected www-form-url-encoded data. + // https://developer.mozilla.org/en-US/docs/Web/API/FormData + body: new URLSearchParams(data), + method: 'POST' + }).then(async (resp) => { + if (resp.status === 400) { + // Use the same area for share errors to avoid issues with interacting + // with the regular jsonnet output box. + document.getElementById('share-output').innerText = await resp.text(); + } else { + document.getElementById('jsonnet-input').value = await resp.text(); + } + htmx.process(document.body); + }); +} + // Allow tab/shift-tab for (de)indentation within the input textarea. script allowTabs() { var textarea = document.getElementById('jsonnet-input'); @@ -71,7 +96,7 @@ templ jsonnetDisplay(sharedHash string) { if sharedHash != "" { hx-get={ fmt.Sprintf("/api/share/%s", sharedHash) } hx-trigger="load" } else { - autofocus id="jsonnet-input" placeholder="Type your Jsonnet here..." + autofocus placeholder="Type your Jsonnet here..." } > @@ -89,6 +114,12 @@ templ jsonnetDisplay(sharedHash string) { > Share +

diff --git a/internal/components/components_templ.go b/internal/components/components_templ.go index 368a651..6a128da 100644 --- a/internal/components/components_templ.go +++ b/internal/components/components_templ.go @@ -132,6 +132,37 @@ func title() templ.Component { }) } +// Hacky function to replace the textarea with formatted Jsonnet. +// +// TODO(jdockerty): there may be a nicer way to do this with htmx, but disabling the +// functionality of the hx-post and replacing the textarea value did not work +// in the same way as the other htmx swaps, it had very odd behaviour instead. +func handleFormat() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_handleFormat_31cd`, + Function: `function __templ_handleFormat_31cd(){var data = new FormData(); + data.append("jsonnet-input", jsonnetInput.value); + fetch("/api/format", { + // Using URLSearchParams means we send the expected www-form-url-encoded data. + // https://developer.mozilla.org/en-US/docs/Web/API/FormData + body: new URLSearchParams(data), + method: 'POST' + }).then(async (resp) => { + if (resp.status === 400) { + // Use the same area for share errors to avoid issues with interacting + // with the regular jsonnet output box. + document.getElementById('share-output').innerText = await resp.text(); + } else { + document.getElementById('jsonnet-input').value = await resp.text(); + } + htmx.process(document.body); + }); +}`, + Call: templ.SafeScript(`__templ_handleFormat_31cd`), + CallInline: templ.SafeScriptInline(`__templ_handleFormat_31cd`), + } +} + // Allow tab/shift-tab for (de)indentation within the input textarea. func allowTabs() templ.ComponentScript { return templ.ComponentScript{ @@ -203,7 +234,7 @@ func jsonnetDisplay(sharedHash string) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/api/share/%s", sharedHash)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/components/components.templ`, Line: 72, Col: 69} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/components/components.templ`, Line: 97, Col: 69} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -214,12 +245,29 @@ func jsonnetDisplay(sharedHash string) templ.Component { return templ_7745c5c3_Err } } else { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" autofocus id=\"jsonnet-input\" placeholder=\"Type your Jsonnet here...\"") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" autofocus placeholder=\"Type your Jsonnet here...\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(">

") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("> ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, handleFormat()) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -238,9 +286,9 @@ func RootPage() templ.Component { defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var8 := templ.GetChildren(ctx) - if templ_7745c5c3_Var8 == nil { - templ_7745c5c3_Var8 = templ.NopComponent + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") diff --git a/internal/server/routes/backend.go b/internal/server/routes/backend.go index e9b70cb..09fbc13 100644 --- a/internal/server/routes/backend.go +++ b/internal/server/routes/backend.go @@ -103,3 +103,31 @@ func HandleGetShare(state *state.State) http.HandlerFunc { w.Write([]byte(snippet)) } } + +// Format the input Jsonnet according to the standard jsonnetfmt rules. +func HandleFormat(state *state.State) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "must be POST", 400) + return + } + log.Println("Formatting snippet") + + err := r.ParseForm() + if err != nil { + http.Error(w, "unable to parse form", 400) + return + } + + incomingJsonnet := r.FormValue("jsonnet-input") + log.Println("Incoming:", incomingJsonnet) + formattedJsonnet, err := state.FormatSnippet(incomingJsonnet) + if err != nil { + http.Error(w, "Format is not available for invalid Jsonnet. Run your snippet to see the result.", 400) + return + } + log.Println("Formatted:", formattedJsonnet) + w.Write([]byte(formattedJsonnet)) + return + } +} diff --git a/internal/server/server.go b/internal/server/server.go index adb92f6..47c67f3 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -34,6 +34,7 @@ func (srv *PlaygroundServer) Routes() error { // Backend/API routes http.HandleFunc("/api/health", routes.Health()) http.HandleFunc("/api/run", routes.HandleRun(srv.State)) + http.HandleFunc("/api/format", routes.HandleFormat(srv.State)) http.HandleFunc("/api/share", routes.HandleCreateShare(srv.State)) http.HandleFunc("/api/share/{shareHash}", routes.HandleGetShare(srv.State)) return nil diff --git a/internal/server/state/state.go b/internal/server/state/state.go index b72d184..a32b9b2 100644 --- a/internal/server/state/state.go +++ b/internal/server/state/state.go @@ -6,6 +6,7 @@ import ( "hash" "github.com/google/go-jsonnet" + "github.com/google/go-jsonnet/formatter" "github.com/kubecfg/kubecfg/pkg/kubecfg" ) @@ -44,6 +45,20 @@ func (s *State) EvaluateSnippet(snippet string) (string, error) { return evaluated, nil } +func (s *State) FormatSnippet(snippet string) (string, error) { + _, err := s.EvaluateSnippet(snippet) + if err != nil { + return "", err + } + + opts := formatter.DefaultOptions() + output, err := formatter.Format(PlaygroundFile, snippet, opts) + if err != nil { + return "", err + } + return output, nil +} + // Config contains server configuration type Config struct { ShareDomain string diff --git a/internal/server/state/state_test.go b/internal/server/state/state_test.go index 2877c64..bc539fc 100644 --- a/internal/server/state/state_test.go +++ b/internal/server/state/state_test.go @@ -28,3 +28,10 @@ func TestEvaluateKubecfg(t *testing.T) { eval, _ := s.EvaluateSnippet(string(f)) assert.Equal(t, string(expected), eval) } + +func TestFormat(t *testing.T) { + s := state.New("") + + eval, _ := s.FormatSnippet(`{hello:"world"}`) + assert.Equal(t, eval, "{ hello: 'world' }\n") +}