gardenjs 1.7.2 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +1 -0
  2. package/bin/servegarden.js +22 -18
  3. package/dist/assets/{frame-BnV1YnGW.css → frame-DNQctfZb.css} +1 -1
  4. package/dist/assets/frame-Z1Hax3or.js +9 -0
  5. package/dist/assets/index-6DOENHaB.js +46 -0
  6. package/dist/assets/index-xKFU1UAQ.css +19 -0
  7. package/dist/assets/{props-BQOViwFg.js → props-BlmngvIZ.js} +1 -1
  8. package/dist/frame.html +3 -3
  9. package/dist/index.html +3 -3
  10. package/package.json +1 -1
  11. package/src/client/GardenApp.svelte +7 -4
  12. package/src/client/GardenFrame.svelte +11 -4
  13. package/src/client/components/sidebar/Sidebar.svelte +0 -1
  14. package/src/client/components/stage/BackgroundGrid.svelte +0 -11
  15. package/src/client/components/stage/DistanceMeasure.svelte +137 -0
  16. package/src/client/components/stage/Stage.svelte +15 -4
  17. package/src/client/components/stage/panel/PanelExamplesNav.svelte +10 -2
  18. package/src/client/components/stage/panel/ParamsPane.svelte +251 -124
  19. package/src/client/components/stage/panel/controls/ArrayControl.svelte +202 -124
  20. package/src/client/components/stage/panel/controls/NumberControl.svelte +1 -1
  21. package/src/client/components/stage/panel/controls/ObjectControl.svelte +139 -51
  22. package/src/client/components/stage/panel/controls/button.scss +8 -5
  23. package/src/client/components/stage/panel/controls/button_unset.scss +0 -1
  24. package/src/client/components/topbar/Topbar.svelte +7 -0
  25. package/src/client/logic/stage.js +92 -31
  26. package/src/codegenerator/base_generator.js +13 -20
  27. package/dist/assets/frame-DZrm9TF0.js +0 -9
  28. package/dist/assets/index-D3v3wg7J.js +0 -46
  29. package/dist/assets/index-qFmggI7l.css +0 -19
@@ -12,11 +12,11 @@
12
12
  componentName,
13
13
  das = {},
14
14
  devmodus,
15
- gridSettings,
16
15
  panelExpanded,
17
16
  selectedExample,
18
17
  showGrid,
19
18
  showInspector,
19
+ showDistanceMeasure,
20
20
  stageContainerHeight,
21
21
  stageContainerMaxHeight,
22
22
  stageHeight,
@@ -25,7 +25,7 @@
25
25
  stageSize,
26
26
  stageStyle,
27
27
  stageWidth,
28
- theme,
28
+ activeTheme,
29
29
  onSetStageContainerHeight,
30
30
  onSetStageContainerMaxHeight,
31
31
  onSetStageContainerWidth,
@@ -104,6 +104,16 @@
104
104
  }
105
105
  })
106
106
 
107
+ const valuesChanged = $derived.by(() => {
108
+ try {
109
+ return (
110
+ JSON.stringify(paramValues) !== JSON.stringify(selectedExampleInput)
111
+ )
112
+ } catch {
113
+ return false
114
+ }
115
+ })
116
+
107
117
  const resizeObserver = new ResizeObserver((entries) => {
108
118
  entries.forEach((entry) => {
109
119
  onUpdateStageRect(entry.contentRect)
@@ -152,6 +162,7 @@
152
162
  props: {
153
163
  params,
154
164
  values: paramValues,
165
+ valuesChanged,
155
166
  onChange: (prop, value) => {
156
167
  paramValues = { ...paramValues, [prop]: value }
157
168
  },
@@ -196,11 +207,11 @@
196
207
  selectedExample,
197
208
  componentName,
198
209
  stageSize,
199
- theme,
210
+ activeTheme,
200
211
  appTheme,
201
212
  showInspector,
213
+ showDistanceMeasure,
202
214
  showGrid,
203
- gridSettings,
204
215
  paramValues: paramValuesForPostMessage,
205
216
  },
206
217
  window.location
@@ -5,6 +5,7 @@
5
5
  examples = [],
6
6
  params,
7
7
  values,
8
+ valuesChanged = false,
8
9
  onChange,
9
10
  onReset,
10
11
  selected,
@@ -17,13 +18,17 @@
17
18
  <VerticalSplitPane
18
19
  {leftWidth}
19
20
  {maxWidth}
21
+ hideDragBar={false}
20
22
  onSetLeftWidth={(newLeftWidth) => (leftWidth = newLeftWidth)}
21
23
  onSetMaxWidth={(newMaxWidth) => (maxWidth = newMaxWidth)}
22
24
  >
23
25
  {#snippet left()}
24
26
  <ul class="examples">
25
27
  {#each examples as example, index (index)}
26
- <li class:active={selected == example}>
28
+ <li
29
+ class:active={selected == example}
30
+ class:modified={selected == example && valuesChanged}
31
+ >
27
32
  <button onclick={() => onSelectExample(example)}>
28
33
  <span class="dot"></span>
29
34
  {example}
@@ -33,7 +38,7 @@
33
38
  </ul>
34
39
  {/snippet}
35
40
  {#snippet right()}
36
- <ParamsPane {params} {values} {onChange} {onReset} />
41
+ <ParamsPane {params} {values} {valuesChanged} {onChange} {onReset} />
37
42
  {/snippet}
38
43
  </VerticalSplitPane>
39
44
 
@@ -81,6 +86,9 @@
81
86
  font-weight: 500;
82
87
  background-color: var(--c-primary-bg);
83
88
  }
89
+ .examples li.active.modified button {
90
+ background-color: var(--c-basic-100);
91
+ }
84
92
  .examples li.active button:focus-visible {
85
93
  background-color: var(--c-basic-150);
86
94
  }
@@ -14,7 +14,13 @@
14
14
  import TextInputControl from './controls/TextInputControl.svelte'
15
15
  import TimeControl from './controls/TimeControl.svelte'
16
16
 
17
- let { params = [], values = {}, onChange, onReset } = $props()
17
+ let {
18
+ params = [],
19
+ values = {},
20
+ valuesChanged = false,
21
+ onChange,
22
+ onReset,
23
+ } = $props()
18
24
 
19
25
  const CONTROL_TYPES = [
20
26
  'checkbox',
@@ -60,21 +66,111 @@
60
66
 
61
67
  let openDescriptionKeys = new SvelteSet()
62
68
 
69
+ const paramsWithDescription = $derived(params.filter((p) => p.description))
70
+ const allDescriptionKeys = $derived(paramsWithDescription.map((p) => p.name))
71
+ const allDescriptionsOpen = $derived.by(() => {
72
+ const keys = allDescriptionKeys
73
+ if (keys.length === 0) return false
74
+ void openDescriptionKeys.size
75
+ return keys.every((k) => openDescriptionKeys.has(k))
76
+ })
77
+
63
78
  function toggleDescription(name) {
64
79
  if (openDescriptionKeys.has(name)) openDescriptionKeys.delete(name)
65
80
  else openDescriptionKeys.add(name)
66
81
  }
82
+
83
+ function toggleAllDescriptions() {
84
+ if (allDescriptionsOpen) {
85
+ allDescriptionKeys.forEach((k) => openDescriptionKeys.delete(k))
86
+ } else {
87
+ allDescriptionKeys.forEach((k) => openDescriptionKeys.add(k))
88
+ }
89
+ }
67
90
  </script>
68
91
 
69
92
  <div class="pane">
70
93
  <div class="header">
71
94
  <div class="title">Parameters</div>
72
- <button
73
- class="btn btn_reset"
74
- title="Reset all parameters"
75
- aria-label="Reset all parameters"
76
- onclick={() => onReset?.()}>Reset</button
77
- >
95
+ <div class="header-actions">
96
+ {#if paramsWithDescription.length > 0}
97
+ <button
98
+ type="button"
99
+ class="btn btn_descriptions"
100
+ title={allDescriptionsOpen
101
+ ? 'Collapse all descriptions'
102
+ : 'Expand all descriptions'}
103
+ aria-label={allDescriptionsOpen
104
+ ? 'Collapse all descriptions'
105
+ : 'Expand all descriptions'}
106
+ onclick={toggleAllDescriptions}
107
+ >
108
+ {#if allDescriptionsOpen}
109
+ <svg
110
+ xmlns="http://www.w3.org/2000/svg"
111
+ width="14"
112
+ height="14"
113
+ viewBox="0 0 24 24"
114
+ fill="none"
115
+ stroke="currentColor"
116
+ stroke-width="2"
117
+ stroke-linecap="round"
118
+ stroke-linejoin="round"
119
+ aria-hidden="true"
120
+ >
121
+ <path
122
+ d="M4 19.5v-15A2.5 2.5 0 016.5 2H19a1 1 0 011 1v18a1 1 0 01-1 1H6.5a1 1 0 010-5H20"
123
+ />
124
+ </svg>
125
+ {:else}
126
+ <svg
127
+ xmlns="http://www.w3.org/2000/svg"
128
+ width="14"
129
+ height="14"
130
+ viewBox="0 0 24 24"
131
+ fill="none"
132
+ stroke="currentColor"
133
+ stroke-width="2"
134
+ stroke-linecap="round"
135
+ stroke-linejoin="round"
136
+ aria-hidden="true"
137
+ >
138
+ <path
139
+ d="M12 7v14m-9-3a1 1 0 01-1-1V4a1 1 0 011-1h5a4 4 0 014 4 4 4 0 014-4h5a1 1 0 011 1v13a1 1 0 01-1 1h-6a3 3 0 00-3 3 3 3 0 00-3-3z"
140
+ />
141
+ </svg>
142
+ {/if}
143
+ <span class="btn-label"
144
+ >{allDescriptionsOpen ? 'Descriptions' : 'Descriptions'}</span
145
+ >
146
+ </button>
147
+ {/if}
148
+ {#if valuesChanged}
149
+ <button
150
+ class="btn"
151
+ title="Reset all parameters"
152
+ aria-label="Reset all parameters"
153
+ onclick={() => onReset?.()}
154
+ >
155
+ <svg
156
+ xmlns="http://www.w3.org/2000/svg"
157
+ width="12"
158
+ height="12"
159
+ viewBox="0 0 24 24"
160
+ fill="none"
161
+ stroke="currentColor"
162
+ stroke-width="2"
163
+ stroke-linecap="round"
164
+ stroke-linejoin="round"
165
+ aria-hidden="true"
166
+ >
167
+ <path d="M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8" />
168
+ <path d="M3 3v5h5" />
169
+ </svg>
170
+ <span class="btn-label">Reset</span>
171
+ </button>
172
+ {/if}
173
+ </div>
78
174
  </div>
79
175
 
80
176
  {#if params.length === 0}
@@ -84,128 +180,141 @@
84
180
  {#each params as param (param.name)}
85
181
  {@const controlType = getControlType(param)}
86
182
  <div class="label-cell">
87
- <span class="label">{param.label || param.name}</span>
88
- {#if param.description}
89
- <button
90
- type="button"
91
- class="info-btn"
92
- title={openDescriptionKeys.has(param.name)
93
- ? 'Close description'
94
- : 'Show description'}
95
- aria-label={openDescriptionKeys.has(param.name)
96
- ? 'Close description'
97
- : 'Show description'}
98
- aria-expanded={openDescriptionKeys.has(param.name)}
99
- onclick={() => toggleDescription(param.name)}
100
- >
101
- <svg
102
- xmlns="http://www.w3.org/2000/svg"
103
- width="16"
104
- viewBox="0 0 24 24"
105
- height="16"
106
- fill="none"
107
- stroke="currentColor"
108
- stroke-width="1.5"
109
- stroke-linecap="round"
110
- stroke-linejoin="round"
111
- aria-hidden="true"
112
- ><circle cx="12" cy="12" r="10" /><path
113
- d="M12 16v-4m0-4h.01"
114
- /></svg
183
+ <div class="label-row">
184
+ <span class="label">{param.label || param.name}</span>
185
+ {#if param.description}
186
+ <button
187
+ type="button"
188
+ class="info-btn"
189
+ title={openDescriptionKeys.has(param.name)
190
+ ? 'Close description'
191
+ : 'Show description'}
192
+ aria-label={openDescriptionKeys.has(param.name)
193
+ ? 'Close description'
194
+ : 'Show description'}
195
+ aria-expanded={openDescriptionKeys.has(param.name)}
196
+ onclick={() => toggleDescription(param.name)}
115
197
  >
116
- </button>
117
- {/if}
198
+ <svg
199
+ width="16"
200
+ height="16"
201
+ viewBox="0 0 16 16"
202
+ xmlns="http://www.w3.org/2000/svg"
203
+ fill-rule="evenodd"
204
+ clip-rule="evenodd"
205
+ stroke-linecap="round"
206
+ stroke-linejoin="round"
207
+ ><circle
208
+ cx="12"
209
+ cy="12"
210
+ r="10"
211
+ fill="none"
212
+ stroke="#000"
213
+ stroke-width="1.5"
214
+ transform="scale(.66667)"
215
+ /><path
216
+ d="M8 10.667V8m0-2.667h.007"
217
+ fill="none"
218
+ stroke="#000"
219
+ stroke-width="1.5"
220
+ /></svg
221
+ >
222
+ </button>
223
+ {/if}
224
+ </div>
118
225
  </div>
119
- <div class="input">
120
- {#if controlType === 'checkbox' || controlType === 'toggle'}
121
- <BooleanControl
122
- value={values?.[param.name] ?? undefined}
123
- control={controlType}
124
- onChange={(v) => onChange?.(param.name, v)}
125
- />
126
- {:else if controlType === 'number'}
127
- <NumberControl
128
- value={values?.[param.name] ?? null}
129
- onChange={(v) => onChange?.(param.name, v)}
130
- />
131
- {:else if controlType === 'color'}
132
- <ColorPickerControl
133
- value={values?.[param.name] ?? undefined}
134
- onChange={(v) => onChange?.(param.name, v)}
135
- />
136
- {:else if controlType === 'date'}
137
- <DateControl
138
- value={values?.[param.name] ?? undefined}
139
- onChange={(v) => onChange?.(param.name, v)}
140
- />
141
- {:else if controlType === 'time'}
142
- <TimeControl
143
- value={values?.[param.name] ?? undefined}
144
- onChange={(v) => onChange?.(param.name, v)}
145
- />
146
- {:else if controlType === 'datetime'}
147
- <DatetimeControl
148
- value={values?.[param.name] ?? undefined}
149
- onChange={(v) => onChange?.(param.name, v)}
150
- />
151
- {:else if controlType === 'array'}
152
- <ArrayControl
153
- value={values?.[param.name] ?? []}
154
- schema={param.schema ?? {}}
155
- onChange={(v) => onChange?.(param.name, v)}
156
- />
157
- {:else if controlType === 'object'}
158
- <ObjectControl
159
- value={values?.[param.name] ?? {}}
160
- schema={param.schema ?? {}}
161
- onChange={(v) => onChange?.(param.name, v)}
162
- />
163
- {:else if controlType === 'multiselect' || controlType === 'checkboxes'}
164
- <MultiselectControl
165
- value={values?.[param.name] ?? []}
166
- options={param.options ?? []}
167
- control={controlType}
168
- onChange={(v) => onChange?.(param.name, v)}
169
- />
170
- {:else if controlType === 'range'}
171
- <RangeControl
172
- value={values?.[param.name] ?? null}
173
- min={param.min}
174
- max={param.max}
175
- step={param.step}
176
- onChange={(v) => onChange?.(param.name, v)}
177
- />
178
- {:else if controlType === 'select' || controlType === 'radio'}
179
- <SelectControl
180
- value={values?.[param.name] ?? undefined}
181
- options={param.options ?? []}
182
- control={controlType}
183
- onChange={(v) => onChange?.(param.name, v)}
184
- />
185
- {:else if controlType === 'textarea' || controlType === 'text'}
186
- <TextInputControl
187
- value={values?.[param.name] ?? ''}
188
- control={controlType}
189
- numberOfRows={param.numberOfRows}
190
- onChange={(v) => onChange?.(param.name, v)}
191
- />
192
- {:else}
193
- <JsonControl
194
- value={values?.[param.name] ?? null}
195
- onChange={(v) => onChange?.(param.name, v)}
196
- />
226
+ <div class="input-col">
227
+ {#if param.description && openDescriptionKeys.has(param.name)}
228
+ <div class="description">{param.description}</div>
197
229
  {/if}
230
+ <div class="input">
231
+ {#if controlType === 'checkbox' || controlType === 'toggle'}
232
+ <BooleanControl
233
+ value={values?.[param.name] ?? undefined}
234
+ control={controlType}
235
+ onChange={(v) => onChange?.(param.name, v)}
236
+ />
237
+ {:else if controlType === 'number'}
238
+ <NumberControl
239
+ value={values?.[param.name] ?? null}
240
+ onChange={(v) => onChange?.(param.name, v)}
241
+ />
242
+ {:else if controlType === 'color'}
243
+ <ColorPickerControl
244
+ value={values?.[param.name] ?? undefined}
245
+ onChange={(v) => onChange?.(param.name, v)}
246
+ />
247
+ {:else if controlType === 'date'}
248
+ <DateControl
249
+ value={values?.[param.name] ?? undefined}
250
+ onChange={(v) => onChange?.(param.name, v)}
251
+ />
252
+ {:else if controlType === 'time'}
253
+ <TimeControl
254
+ value={values?.[param.name] ?? undefined}
255
+ onChange={(v) => onChange?.(param.name, v)}
256
+ />
257
+ {:else if controlType === 'datetime'}
258
+ <DatetimeControl
259
+ value={values?.[param.name] ?? undefined}
260
+ onChange={(v) => onChange?.(param.name, v)}
261
+ />
262
+ {:else if controlType === 'array'}
263
+ <ArrayControl
264
+ value={values?.[param.name] ?? undefined}
265
+ schema={param.schema ?? {}}
266
+ onChange={(v) => onChange?.(param.name, v)}
267
+ />
268
+ {:else if controlType === 'object'}
269
+ <ObjectControl
270
+ value={values?.[param.name] ?? undefined}
271
+ schema={param.schema ?? {}}
272
+ onChange={(v) => onChange?.(param.name, v)}
273
+ />
274
+ {:else if controlType === 'multiselect' || controlType === 'checkboxes'}
275
+ <MultiselectControl
276
+ value={values?.[param.name] ?? []}
277
+ options={param.options ?? []}
278
+ control={controlType}
279
+ onChange={(v) => onChange?.(param.name, v)}
280
+ />
281
+ {:else if controlType === 'range'}
282
+ <RangeControl
283
+ value={values?.[param.name] ?? null}
284
+ min={param.min}
285
+ max={param.max}
286
+ step={param.step}
287
+ onChange={(v) => onChange?.(param.name, v)}
288
+ />
289
+ {:else if controlType === 'select' || controlType === 'radio'}
290
+ <SelectControl
291
+ value={values?.[param.name] ?? undefined}
292
+ options={param.options ?? []}
293
+ control={controlType}
294
+ onChange={(v) => onChange?.(param.name, v)}
295
+ />
296
+ {:else if controlType === 'textarea' || controlType === 'text'}
297
+ <TextInputControl
298
+ value={values?.[param.name] ?? ''}
299
+ control={controlType}
300
+ numberOfRows={param.numberOfRows}
301
+ onChange={(v) => onChange?.(param.name, v)}
302
+ />
303
+ {:else}
304
+ <JsonControl
305
+ value={values?.[param.name] ?? null}
306
+ onChange={(v) => onChange?.(param.name, v)}
307
+ />
308
+ {/if}
309
+ </div>
198
310
  </div>
199
- {#if param.description && openDescriptionKeys.has(param.name)}
200
- <div class="description">{param.description}</div>
201
- {/if}
202
311
  {/each}
203
312
  </div>
204
313
  {/if}
205
314
  </div>
206
315
 
207
- <style>
208
- @import './controls/button.scss';
316
+ <style lang="scss">
317
+ @use './controls/button.scss';
209
318
 
210
319
  .pane {
211
320
  width: 100%;
@@ -216,8 +325,15 @@
216
325
  display: flex;
217
326
  justify-content: space-between;
218
327
  align-items: center;
328
+ min-height: 2.125rem;
219
329
  margin-bottom: 1rem;
220
330
  }
331
+ .header-actions {
332
+ display: flex;
333
+ align-items: center;
334
+ min-height: 2.125rem;
335
+ gap: 0.5rem;
336
+ }
221
337
  .title {
222
338
  font-size: 0.95rem;
223
339
  font-weight: 600;
@@ -233,20 +349,32 @@
233
349
  gap: 0.75rem;
234
350
  align-items: start;
235
351
  }
352
+ .input-col {
353
+ display: flex;
354
+ flex-direction: column;
355
+ gap: 0.25rem;
356
+ min-width: 0;
357
+ }
236
358
  .label-cell {
359
+ display: flex;
360
+ flex-direction: column;
361
+ gap: 0.25rem;
362
+ }
363
+ .label-row {
237
364
  display: flex;
238
365
  align-items: center;
239
366
  gap: 0.35rem;
240
367
  }
241
368
  .label {
242
- margin: 0.313rem 0 0;
369
+ margin: 0;
370
+ line-height: 1.2;
243
371
  font-size: 0.938rem;
244
372
  font-weight: 500;
245
373
  color: var(--c-basic-900);
246
374
  }
247
375
  .info-btn {
248
376
  display: inline-flex;
249
- margin: -0.15rem 0;
377
+ margin: 0;
250
378
  padding: 0.15rem;
251
379
  color: var(--c-basic-900);
252
380
  }
@@ -257,8 +385,7 @@
257
385
  color: var(--c-primary);
258
386
  }
259
387
  .description {
260
- grid-column: 1 / -1;
261
- margin: -0.25rem 0 0;
388
+ margin: 0;
262
389
  padding: 0.5rem 0.75rem;
263
390
  font-size: 0.813rem;
264
391
  color: var(--c-basic-800);