Skip to content

Commit e92e9c0

Browse files
committed
Handle Stimulus array values
1 parent 9a0c4b3 commit e92e9c0

File tree

2 files changed

+131
-33
lines changed

2 files changed

+131
-33
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script>
2+
import NestedValue from "./NestedValue.svelte"
3+
import ValueEditor from "./ValueEditor.svelte"
4+
5+
let { data, path = [], editingStates, onEdit, onSave, onCancel } = $props()
6+
7+
const isObject = (val) => typeof val === "object" && val !== null && !Array.isArray(val)
8+
const isArray = (val) => Array.isArray(val)
9+
const isPrimitive = (val) => !isObject(val) && !isArray(val)
10+
</script>
11+
12+
{#if isObject(data)}
13+
{#each Object.entries(data) as [key, value]}
14+
<wa-tree-item>
15+
{#if isPrimitive(value)}
16+
<div class="d-flex code-value">
17+
<span class="code-key">{key}:</span>
18+
<ValueEditor
19+
{value}
20+
isEditing={editingStates[[...path, key].join(".")] || false}
21+
onEdit={() => onEdit([...path, key])}
22+
onSave={(newVal) => onSave([...path, key], newVal)}
23+
onCancel={() => onCancel([...path, key])}
24+
/>
25+
</div>
26+
{:else}
27+
<span class="code-key">{key}:</span>
28+
{#if isArray(value)}
29+
<span class="text-muted ms-1">{value.length}</span>
30+
{/if}
31+
<NestedValue data={value} path={[...path, key]} {editingStates} {onEdit} {onSave} {onCancel} />
32+
{/if}
33+
</wa-tree-item>
34+
{/each}
35+
{:else if isArray(data)}
36+
{#each data as item, i}
37+
<wa-tree-item>
38+
{#if isPrimitive(item)}
39+
<div class="d-flex code-value">
40+
<span class="code-key">{i}:</span>
41+
<ValueEditor
42+
value={item}
43+
isEditing={editingStates[[...path, i].join(".")] || false}
44+
onEdit={() => onEdit([...path, i])}
45+
onSave={(newVal) => onSave([...path, i], newVal)}
46+
onCancel={() => onCancel([...path, i])}
47+
/>
48+
</div>
49+
{:else}
50+
<span class="code-key">{i}:</span>
51+
{#if isArray(item)}
52+
<span class="text-muted ms-1">Array [{item.length}]</span>
53+
{/if}
54+
<NestedValue data={item} path={[...path, i]} {editingStates} {onEdit} {onSave} {onCancel} />
55+
{/if}
56+
</wa-tree-item>
57+
{/each}
58+
{/if}

src/components/Stimulus/ValueTreeItem.svelte

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,100 @@
11
<script>
22
import ValueEditor from "./ValueEditor.svelte"
3+
import NestedValue from "./NestedValue.svelte"
34
import CopyButton from "$components/CopyButton.svelte"
45
import { updateDataAttribute } from "../../browser_panel/messaging"
56
67
let { valueObject, selected, dataAttribute } = $props()
78
89
let editingStates = $state({})
910
10-
const isObject = typeof valueObject.value === "object" && valueObject.value !== null
11+
const isComplex = (val) => typeof val === "object" && val !== null
12+
const isArray = (val) => Array.isArray(val)
1113
12-
const handleSave = (key, newValue) => {
13-
if (isObject) {
14-
const updatedObject = { ...valueObject.value, [key]: newValue }
15-
updateDataAttribute(`[data-hotwire-dev-tools-uuid="${selected.uuid}"]`, dataAttribute, JSON.stringify(updatedObject))
14+
// Deep set value in nested object/array using path array
15+
const setNestedValue = (obj, path, value) => {
16+
if (path.length === 0) return value
17+
18+
const newObj = JSON.parse(JSON.stringify(obj))
19+
let current = newObj
20+
21+
for (let i = 0; i < path.length - 1; i++) {
22+
current = current[path[i]]
23+
}
24+
25+
// Try to parse as number, boolean, or keep as string
26+
let parsedValue = value
27+
if (value === "true") parsedValue = true
28+
else if (value === "false") parsedValue = false
29+
else if (value === "null") parsedValue = null
30+
else if (!isNaN(value) && value !== "") parsedValue = Number(value)
31+
32+
current[path[path.length - 1]] = parsedValue
33+
34+
return newObj
35+
}
36+
37+
const serializeValue = (value, type) => {
38+
// For Array and Object types, Stimulus expects JSON
39+
if (type === "array" || type === "object") {
40+
return JSON.stringify(value)
41+
}
42+
43+
// For other types, convert to string
44+
return String(value)
45+
}
46+
47+
const handleSave = (path, newValue) => {
48+
let updatedValue
49+
50+
if (isComplex(valueObject.value)) {
51+
updatedValue = setNestedValue(valueObject.value, path, newValue)
1652
} else {
17-
updateDataAttribute(`[data-hotwire-dev-tools-uuid="${selected.uuid}"]`, dataAttribute, newValue)
53+
// For primitive values at root level
54+
if (valueObject.type === "Number") {
55+
updatedValue = Number(newValue)
56+
} else if (valueObject.type === "Boolean") {
57+
updatedValue = newValue === "true" || newValue === true
58+
} else {
59+
updatedValue = newValue
60+
}
1861
}
19-
editingStates[key || "main"] = false
62+
63+
const serializedValue = serializeValue(updatedValue, valueObject.type)
64+
65+
updateDataAttribute(`[data-hotwire-dev-tools-uuid="${selected.uuid}"]`, dataAttribute, serializedValue)
66+
67+
editingStates[path.join(".")] = false
2068
}
2169
22-
const handleEdit = (key) => {
23-
editingStates[key || "main"] = true
70+
const handleEdit = (path) => {
71+
editingStates[path.join(".")] = true
2472
}
2573
26-
const handleCancel = (key) => {
27-
editingStates[key || "main"] = false
74+
const handleCancel = (path) => {
75+
editingStates[path.join(".")] = false
2876
}
2977
</script>
3078

3179
<div class="d-flex gap-2 mb-2">
3280
<wa-tree>
3381
<wa-tree-item expanded>
34-
{#if isObject}
82+
{#if isComplex(valueObject.value)}
3583
{valueObject.name}
36-
{#each Object.entries(valueObject.value) as [key, value]}
37-
<wa-tree-item>
38-
<div class="d-flex code-value">
39-
<span class="code-key">{key}:</span>
40-
<ValueEditor {value} valueType={"string"} isEditing={editingStates[key] || false} onEdit={() => handleEdit(key)} onSave={(newVal) => handleSave(key, newVal)} onCancel={() => handleCancel(key)} />
41-
</div>
42-
</wa-tree-item>
43-
{/each}
84+
{#if isArray(valueObject.value)}
85+
<span class="text-muted ms-1">Array ({valueObject.value.length})</span>
86+
{/if}
87+
<NestedValue data={valueObject.value} {editingStates} onEdit={handleEdit} onSave={handleSave} onCancel={handleCancel} />
4488
{:else}
4589
<span class="code-key">{valueObject.name}:</span>
4690
<div class="d-flex code-value">
4791
<ValueEditor
48-
value={valueObject.value}
92+
value={String(valueObject.value)}
4993
valueType={valueObject.type}
50-
isEditing={editingStates["main"] || false}
51-
onEdit={() => handleEdit(null)}
52-
onSave={(newVal) => handleSave(null, newVal)}
53-
onCancel={() => handleCancel(null)}
94+
isEditing={editingStates["root"] || false}
95+
onEdit={() => handleEdit(["root"])}
96+
onSave={(newVal) => handleSave(["root"], newVal)}
97+
onCancel={() => handleCancel(["root"])}
5498
/>
5599
</div>
56100
{/if}
@@ -69,16 +113,12 @@
69113
<CopyButton value={dataAttribute} />
70114
</div>
71115
<div class="d-flex justify-content-between align-items-center">
72-
<span>{`this.${valueObject.name}`}</span>
73-
<CopyButton value={`this.${valueObject.name}`} />
74-
</div>
75-
<div class="d-flex justify-content-between align-items-center">
76-
<span>{`this.${valueObject.name}s`}</span>
77-
<CopyButton value={`this.${valueObject.name}s`} />
116+
<span>{`this.${valueObject.name}Value`}</span>
117+
<CopyButton value={`this.${valueObject.name}Value`} />
78118
</div>
79119
<div class="d-flex justify-content-between align-items-center">
80-
<span>{`this.has${valueObject.name[0].toUpperCase() + valueObject.name.slice(1)}`}</span>
81-
<CopyButton value={`this.has${valueObject.name[0].toUpperCase() + valueObject.name.slice(1)}`} />
120+
<span>{`this.has${valueObject.name[0].toUpperCase() + valueObject.name.slice(1)}Value`}</span>
121+
<CopyButton value={`this.has${valueObject.name[0].toUpperCase() + valueObject.name.slice(1)}Value`} />
82122
</div>
83123
</div>
84124
</wa-tooltip>

0 commit comments

Comments
 (0)