A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: format on save

Brooke 16cee306 091f849a

+42 -11
+4 -1
packages/panel/src/lib/component/ComposeEditor.svelte
··· 18 18 import { error } from "$lib"; 19 19 import Fa from "svelte-fa"; 20 20 21 - let { content = $bindable() }: { content: string } = $props(); 21 + let { content = $bindable(), format: f = $bindable() }: { content: string; format?: () => Promise<void> } = 22 + $props(); 22 23 23 24 let initial = $state.snapshot(content); 24 25 let focused = $state(false); ··· 144 145 changes: { from: 0, to: editor.state.doc.length, insert: content }, 145 146 }); 146 147 }); 148 + 149 + f = () => format(editor); 147 150 148 151 return () => { 149 152 editor.destroy();
+2 -1
packages/panel/src/lib/style.scss
··· 145 145 } 146 146 } 147 147 148 - *:has(input[type="radio"]) > label { 148 + *:has(input[type="radio"]) > label, 149 + *:has(input[type="checkbox"]) > label { 149 150 display: inline-block; 150 151 } 151 152
+8 -4
packages/panel/src/routes/(authenticated)/create/+page.svelte
··· 8 8 import { api } from "$lib"; 9 9 import Fa from "svelte-fa"; 10 10 11 + let format = $state(async () => {}); 12 + 11 13 // svelte-ignore state_referenced_locally 12 14 let project = $state({ 13 15 compose: placeholder, 14 16 name: "unnamed", 15 17 }); 16 18 17 - async function save() { 19 + async function create() { 20 + if (localStorage.getItem("luminary-format-on-save") == "true") await format(); 21 + 18 22 const response = await api.client.PATCH(`/api/project/{project}`, { 19 23 body: { compose: project.compose, creating: true }, 20 24 params: { path: { project: project.name } }, ··· 28 32 const saveKeybind = (event: KeyboardEvent) => { 29 33 if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") { 30 34 event.preventDefault(); 31 - save(); 35 + create(); 32 36 } 33 37 }; 34 38 ··· 51 55 </div> 52 56 </h1> 53 57 54 - <EditTabs bind:data={project} /> 58 + <EditTabs bind:format bind:data={project} /> 55 59 </div> 56 60 57 61 <div class="flexr gap-10"> 58 62 <div> 59 - <PromiseButton onclick={save} disabled={project.name.trim() === ""}> 63 + <PromiseButton onclick={create} disabled={project.name.trim() === ""}> 60 64 <div class="flexr gap-5 center"> 61 65 <Fa icon={faPlus} /> Create 62 66 </div>
+8 -3
packages/panel/src/routes/(authenticated)/projects/EditTabs.svelte
··· 7 7 let { 8 8 tabs = [], 9 9 data = $bindable(), 10 - }: { data: { name: string; compose: string }; tabs?: ComponentProps<typeof Tabs>["tabs"] } = $props(); 10 + format = $bindable(), 11 + }: { 12 + tabs?: ComponentProps<typeof Tabs>["tabs"]; 13 + data: { name: string; compose: string }; 14 + format?: () => Promise<void>; 15 + } = $props(); 11 16 </script> 12 17 13 18 <Tabs tabs={[...tabs, { label: "compose", icon: faPencil, content: compose }]} /> 14 19 15 20 {#snippet compose()} 16 21 <div> 17 - <label for="name">Name</label> 22 + <label for="name"><h2 style="display: inline-block;">Name</h2></label> 18 23 <div style="position: relative; display: flex; align-items: center;"> 19 24 {#if data.name.trim() === ""} 20 25 <div class="error">Name is required</div> ··· 24 29 </div> 25 30 26 31 <h2>Compose</h2> 27 - <ComposeEditor bind:content={data.compose} /> 32 + <ComposeEditor bind:content={data.compose} bind:format /> 28 33 {/snippet} 29 34 30 35 <style lang="scss">
+5 -2
packages/panel/src/routes/(authenticated)/projects/[project]/+page.svelte
··· 16 16 let project = $derived(getProjects()[page.params.project!]); 17 17 let { data } = $props(); 18 18 19 + let format = $state(async () => {}); 20 + 19 21 // svelte-ignore state_referenced_locally 20 22 let copy = $state({ 21 23 compose: data.compose ?? "", ··· 36 38 } 37 39 38 40 async function save() { 41 + if (localStorage.getItem("luminary-format-on-save") == "true") await format(); 42 + 39 43 const rename = copy.name !== project.name; 40 - 41 44 const response = await api.client.PATCH(`/api/project/{project}`, { 42 45 params: { path: { project: project.name } }, 43 46 body: { ··· 88 91 </div> 89 92 </h1> 90 93 91 - <EditTabs bind:data={copy} tabs={[{ label: "status", icon: faCircleInfo, content: status }]} /> 94 + <EditTabs bind:format bind:data={copy} tabs={[{ label: "status", icon: faCircleInfo, content: status }]} /> 92 95 </div> 93 96 94 97 {#snippet status()}
+15
packages/panel/src/routes/(authenticated)/settings/+page.svelte
··· 6 6 import { api } from "$lib"; 7 7 8 8 const THEME_KEY = "luminary-theme"; 9 + const FORMAT_ON_SAVE_KEY = "luminary-format-on-save"; 9 10 10 11 let { data } = $props(); 11 12 12 13 let theme = $state(localStorage.getItem(THEME_KEY) ?? "macchiato"); 14 + 13 15 $effect(() => { 14 16 localStorage.setItem(THEME_KEY, theme); 15 17 document.documentElement.setAttribute("class", theme); 18 + }); 19 + 20 + let formatOnSave = $state(localStorage.getItem(FORMAT_ON_SAVE_KEY) == "true"); 21 + 22 + $effect(() => { 23 + localStorage.setItem(FORMAT_ON_SAVE_KEY, String(formatOnSave)); 16 24 }); 17 25 </script> 18 26 ··· 36 44 37 45 {#snippet appearance()} 38 46 <div class="flexc gap-10"> 47 + <div class="fit"> 48 + <h2>Editor</h2> 49 + <div class="fit"> 50 + <input type="checkbox" id="format-on-save" bind:checked={formatOnSave} /> 51 + <label for="format-on-save">Format on save</label> 52 + </div> 53 + </div> 39 54 <div class="fit"> 40 55 <h2>Theme</h2> 41 56 <div>