@picked/svelte 0.0.1-dev.1788925875333 → 0.0.1-next.1
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.
- package/dist/component.d.ts +5 -5
- package/dist/component.js +1 -1
- package/dist/context.js +1 -1
- package/dist/form/index.d.ts +15 -3
- package/dist/form/index.js +1 -1
- package/dist/hooks/client.d.ts +1 -1
- package/dist/hooks/client.js +1 -1
- package/dist/hooks/field.d.ts +5 -16
- package/dist/hooks/field.js +1 -1
- package/dist/hooks/index.d.ts +1 -9
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/step.d.ts +15 -93
- package/dist/hooks/step.js +1 -1
- package/dist/hooks/storage.d.ts +2 -2
- package/dist/hooks/storage.js +1 -1
- package/dist/hooks/stores.d.ts +1 -1
- package/dist/hooks/stores.js +1 -1
- package/dist/index.d.ts +1 -8
- package/dist/index.js +1 -1
- package/dist/plugins/devtools/DevToolsExperience.svelte +53 -355
- package/dist/plugins/devtools/PickedLogo.svelte +58 -0
- package/dist/plugins/devtools/components/ExperienceInstanceListItem.svelte +4 -17
- package/dist/plugins/devtools/components/ExperienceInstancesTable.svelte +19 -26
- package/dist/plugins/devtools/components/Tabs.svelte +12 -69
- package/dist/plugins/devtools/experience.d.ts +1 -0
- package/dist/plugins/devtools/experience.js +1 -0
- package/dist/plugins/devtools/index.d.ts +0 -2
- package/dist/plugins/devtools/index.js +1 -1
- package/dist/plugins/devtools/plugin.d.ts +5 -37
- package/dist/plugins/devtools/plugin.js +1 -1
- package/dist/plugins/devtools/screens/components/ComponentsListScreen.svelte +14 -18
- package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +3 -1
- package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +33 -31
- package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +3 -1
- package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +103 -293
- package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +1 -2
- package/dist/plugins/devtools/screens/frameworks/FrameworksListScreen.svelte +13 -15
- package/dist/plugins/devtools/screens/history/HistoryEntriesTable.svelte +16 -22
- package/dist/plugins/devtools/screens/history/HistoryEntryListItem.svelte +1 -7
- package/dist/plugins/devtools/screens/predicates/PredicatesListScreen.svelte +13 -15
- package/dist/plugins/devtools/screens/project/ProjectScreen.svelte +1 -46
- package/dist/plugins/framework/index.js +1 -1
- package/dist/plugins/framework/plugin.d.ts +4 -4
- package/dist/plugins/framework/plugin.js +1 -1
- package/dist/render/browser.d.ts +2 -35
- package/dist/render/browser.js +1 -1
- package/dist/render/server.d.ts +8 -22
- package/dist/render/server.js +1 -1
- package/dist/router/Step.svelte +4 -6
- package/dist/router/StepRouter.svelte +33 -14
- package/dist/router/context.d.ts +3 -3
- package/dist/router/context.js +1 -1
- package/dist/router/index.js +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +1 -1
- package/dist/utils/array.js +1 -1
- package/dist/vite-env.d.js +1 -0
- package/package.json +33 -106
- package/dist/.tsbuildinfo +0 -1
- package/dist/_virtual/_vite/preload-helper.js +0 -1
- package/dist/components/LazyImage.svelte +0 -192
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.js +0 -1
- package/dist/experience/ExperienceHost.svelte +0 -112
- package/dist/experience/ExperienceSlot.svelte +0 -154
- package/dist/experience/HtmlRender.svelte +0 -52
- package/dist/experience/set-picked-context.d.ts +0 -29
- package/dist/experience/set-picked-context.js +0 -1
- package/dist/form/__tests__/CreateFormHarness.svelte +0 -30
- package/dist/form/create-form.js +0 -1
- package/dist/form/create-form.svelte.d.ts +0 -40
- package/dist/form/create-form.svelte.js +0 -1
- package/dist/form/get-form.d.ts +0 -30
- package/dist/form/get-form.js +0 -1
- package/dist/hooks/action.d.ts +0 -40
- package/dist/hooks/action.js +0 -1
- package/dist/hooks/content.d.ts +0 -18
- package/dist/hooks/content.js +0 -1
- package/dist/hooks/create-query/index.d.ts +0 -145
- package/dist/hooks/create-query/index.js +0 -1
- package/dist/hooks/events.d.ts +0 -2
- package/dist/hooks/events.js +0 -1
- package/dist/hooks/feature.d.ts +0 -47
- package/dist/hooks/feature.js +0 -1
- package/dist/hooks/predicate.d.ts +0 -3
- package/dist/hooks/predicate.js +0 -1
- package/dist/hooks/reference.d.ts +0 -61
- package/dist/hooks/reference.js +0 -1
- package/dist/plugins/devtools/PickedIcon.svelte +0 -19
- package/dist/plugins/devtools/components/AdaptedToolHost.svelte +0 -103
- package/dist/plugins/devtools/customTools.d.ts +0 -27
- package/dist/plugins/devtools/customTools.js +0 -1
- package/dist/plugins/devtools/screens/config/ConfigScreen.svelte +0 -160
- package/dist/plugins/devtools/screens/content/ContentListScreen.svelte +0 -89
- package/dist/plugins/devtools/screens/custom-tools/CustomToolsScreen.svelte +0 -29
- package/dist/plugins/devtools/screens/events/EventListItem.svelte +0 -216
- package/dist/plugins/devtools/screens/events/EventsListScreen.svelte +0 -18
- package/dist/plugins/devtools/screens/events/EventsTable.svelte +0 -76
- package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +0 -239
- package/dist/plugins/devtools/screens/fields/FieldTableRow.svelte +0 -276
- package/dist/plugins/devtools/screens/fields/FieldsListScreen.svelte +0 -12
- package/dist/plugins/devtools/screens/fields/FieldsTable.svelte +0 -78
- package/dist/plugins/devtools/screens/identity/IdentityScreen.svelte +0 -66
- package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +0 -280
- package/dist/plugins/devtools/screens/responses/ResponsesListScreen.svelte +0 -12
- package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +0 -88
- package/dist/plugins/devtools/ui/actions/Button.svelte +0 -175
- package/dist/plugins/devtools/ui/actions/CopyButton.svelte +0 -88
- package/dist/plugins/devtools/ui/data/Badge.svelte +0 -62
- package/dist/plugins/devtools/ui/data/CodeBlock.svelte +0 -38
- package/dist/plugins/devtools/ui/data/DataTable.svelte +0 -144
- package/dist/plugins/devtools/ui/data/EmptyState.svelte +0 -26
- package/dist/plugins/devtools/ui/data/ExpandableRow.svelte +0 -66
- package/dist/plugins/devtools/ui/data/VisualizationContainer.svelte +0 -55
- package/dist/plugins/devtools/ui/feedback/Banner.svelte +0 -116
- package/dist/plugins/devtools/ui/feedback/Spinner.svelte +0 -79
- package/dist/plugins/devtools/ui/forms/Checkbox.svelte +0 -139
- package/dist/plugins/devtools/ui/forms/RadioGroup.svelte +0 -218
- package/dist/plugins/devtools/ui/forms/Select.svelte +0 -137
- package/dist/plugins/devtools/ui/forms/Switch.svelte +0 -158
- package/dist/plugins/devtools/ui/forms/TextField.svelte +0 -125
- package/dist/plugins/devtools/ui/index.d.ts +0 -22
- package/dist/plugins/devtools/ui/index.js +0 -1
- package/dist/plugins/devtools/ui/layout/Card.svelte +0 -55
- package/dist/plugins/devtools/ui/layout/Divider.svelte +0 -34
- package/dist/plugins/devtools/ui/layout/Spacer.svelte +0 -11
- package/dist/plugins/devtools/ui/layout/Stack.svelte +0 -129
- package/dist/plugins/devtools/ui/overlays/Tooltip.svelte +0 -90
- package/dist/plugins/devtools/ui/tokens.css +0 -144
- package/dist/plugins/devtools/ui/typography/Heading.svelte +0 -55
- package/dist/plugins/devtools/ui/typography/Text.svelte +0 -93
- package/dist/utils/ErrorBoundary.svelte +0 -27
|
@@ -1,42 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
/** @file D3-based tree visualization of an experience's node graph in the devtools panel. */
|
|
3
|
-
import { onDestroy } from 'svelte'
|
|
4
2
|
import * as d3 from 'd3'
|
|
5
3
|
import type { ExperienceEdge, ExperienceNode, ExperienceStore } from '@picked/client'
|
|
6
|
-
import { Badge, Button, Spinner, Stack, Text, VisualizationContainer } from '../../ui'
|
|
7
4
|
|
|
8
5
|
export let experience: ExperienceStore
|
|
9
6
|
|
|
10
7
|
let svgEl: SVGSVGElement
|
|
11
8
|
let zoom = 1
|
|
12
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- reserved for future node details panel
|
|
13
9
|
let selectedNode = null
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars, sonarjs/no-unused-collection -- reserved for future path highlighting
|
|
15
10
|
let highlightedPath: string[][] = []
|
|
16
|
-
let error
|
|
11
|
+
let error = null
|
|
17
12
|
const loading = false
|
|
18
13
|
|
|
19
14
|
let treeRenderingDone = false
|
|
20
|
-
let tooltipEl: d3.Selection<HTMLDivElement, unknown, null, undefined> | null = null
|
|
21
|
-
|
|
22
|
-
onDestroy(() => {
|
|
23
|
-
tooltipEl?.remove()
|
|
24
|
-
tooltipEl = null
|
|
25
|
-
})
|
|
26
15
|
|
|
27
16
|
const tree = experience.treeStore
|
|
28
17
|
|
|
18
|
+
$: steps = $tree.steps
|
|
29
19
|
$: nodes = $tree.nodes
|
|
30
20
|
$: edges = $tree.edges
|
|
31
|
-
$:
|
|
32
|
-
|
|
21
|
+
$: conditionals = $tree.conditionals
|
|
33
22
|
|
|
34
23
|
let zoomBehavior: d3.ZoomBehavior<SVGSVGElement, unknown>
|
|
35
24
|
|
|
36
25
|
interface ProcessedNode {
|
|
37
26
|
id: string
|
|
38
27
|
name: string
|
|
39
|
-
tooltip?: string
|
|
40
28
|
type: string
|
|
41
29
|
children: ProcessedNode[]
|
|
42
30
|
_originalNode: ExperienceNode
|
|
@@ -46,98 +34,10 @@
|
|
|
46
34
|
_referenceTo?: string
|
|
47
35
|
}
|
|
48
36
|
|
|
49
|
-
/**
|
|
50
|
-
* Type guard that checks if a value is a non-null, non-array object.
|
|
51
|
-
*
|
|
52
|
-
* @param val - The value to check.
|
|
53
|
-
* @example
|
|
54
|
-
* ```typescript
|
|
55
|
-
* isRecord({ key: 'value' }) // true
|
|
56
|
-
* isRecord([1, 2]) // false
|
|
57
|
-
* ```
|
|
58
|
-
*/
|
|
59
|
-
function isRecord(val: unknown): val is Record<string, unknown> {
|
|
60
|
-
return val !== null && typeof val === 'object' && !Array.isArray(val)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Recursively formats a JSON Logic rule into a human-readable string.
|
|
65
|
-
*
|
|
66
|
-
* @param rule - The JSON Logic rule object to format.
|
|
67
|
-
* @example
|
|
68
|
-
* ```typescript
|
|
69
|
-
* formatLogicRule({ '==': [{ var: 'age' }, 18] }) // 'age = 18'
|
|
70
|
-
* ```
|
|
71
|
-
*/
|
|
72
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- recursive logic rule formatter handles all operator types
|
|
73
|
-
function formatLogicRule(rule: unknown): string {
|
|
74
|
-
if (rule === null || rule === undefined) return '?'
|
|
75
|
-
if (typeof rule === 'string') return rule
|
|
76
|
-
if (typeof rule === 'number' || typeof rule === 'boolean') return String(rule)
|
|
77
|
-
if (Array.isArray(rule)) {
|
|
78
|
-
return rule.map(formatLogicRule).join(', ')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (isRecord(rule)) {
|
|
82
|
-
const keys = Object.keys(rule)
|
|
83
|
-
if (keys.length === 0) return 'Condition'
|
|
84
|
-
const op = keys[0]
|
|
85
|
-
const args = rule[op]
|
|
86
|
-
|
|
87
|
-
if (op === 'var') {
|
|
88
|
-
return String(Array.isArray(args) ? args[0] : args)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const compOps: Record<string, string> = {
|
|
92
|
-
'==': '=',
|
|
93
|
-
'===': '=',
|
|
94
|
-
'!=': '≠',
|
|
95
|
-
'!==': '≠',
|
|
96
|
-
'>': '>',
|
|
97
|
-
'<': '<',
|
|
98
|
-
'>=': '≥',
|
|
99
|
-
'<=': '≤',
|
|
100
|
-
}
|
|
101
|
-
if (op in compOps && Array.isArray(args) && args.length >= 2) {
|
|
102
|
-
return `${formatLogicRule(args[0])} ${compOps[op]} ${formatLogicRule(args[1])}`
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (op === 'and' && Array.isArray(args)) {
|
|
106
|
-
return args.map(formatLogicRule).join(' & ')
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (op === 'or' && Array.isArray(args)) {
|
|
110
|
-
return args.map(formatLogicRule).join(' | ')
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if ((op === '!' || op === 'not') && Array.isArray(args)) {
|
|
114
|
-
return `!${formatLogicRule(args[0])}`
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (op === 'in' && Array.isArray(args) && args.length >= 2) {
|
|
118
|
-
return `${formatLogicRule(args[0])} in ${formatLogicRule(args[1])}`
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return `${op}(?)`
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return '?'
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Renders the experience node graph as an interactive D3 tree visualization.
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```typescript
|
|
132
|
-
* renderGraph() // builds and renders the SVG tree
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
135
37
|
function renderGraph() {
|
|
136
38
|
d3.select(svgEl).selectAll('*').remove()
|
|
137
39
|
|
|
138
|
-
|
|
139
|
-
const conditionsMap = new Map(conditions.map((c) => [c.id, c]))
|
|
140
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
|
|
40
|
+
const stepsMap = new Map(steps.map((s) => [s.id, s]))
|
|
141
41
|
const outgoingEdges = new Map<string, ExperienceEdge[]>()
|
|
142
42
|
edges.forEach((e) => {
|
|
143
43
|
if (!outgoingEdges.has(e.sourceNodeId)) outgoingEdges.set(e.sourceNodeId, [])
|
|
@@ -146,32 +46,17 @@
|
|
|
146
46
|
|
|
147
47
|
const getNodeLabel = (node: ExperienceNode): string => {
|
|
148
48
|
if (node.type === 'origin') return 'Start'
|
|
149
|
-
if (node.type === 'step'
|
|
150
|
-
|
|
151
|
-
if (node.type === '
|
|
152
|
-
const conditionItem = node.conditionId ? conditionsMap.get(node.conditionId) : undefined
|
|
153
|
-
return conditionItem?.name ?? 'Condition'
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (node.type === 'action') return node.name ?? node.actionKey ?? 'Action'
|
|
157
|
-
|
|
49
|
+
if (node.type === 'step' && node.stepId)
|
|
50
|
+
return stepsMap.get(node.stepId)?.label ?? 'Unknown Step'
|
|
51
|
+
if (node.type === 'conditional' && node.conditionalId) return `Condition'}`
|
|
158
52
|
return 'Unknown Node'
|
|
159
53
|
}
|
|
160
54
|
|
|
161
|
-
const getNodeTooltip = (node: ExperienceNode): string | undefined => {
|
|
162
|
-
if (node.type !== 'condition') return undefined
|
|
163
|
-
const conditionItem = node.conditionId ? conditionsMap.get(node.conditionId) : undefined
|
|
164
|
-
if (conditionItem?.logic) return formatLogicRule(conditionItem.logic)
|
|
165
|
-
return undefined
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
|
|
169
55
|
const processedNodes = new Map<string, ProcessedNode>()
|
|
170
56
|
nodes.forEach((n) => {
|
|
171
57
|
processedNodes.set(n.id, {
|
|
172
58
|
id: n.id,
|
|
173
59
|
name: getNodeLabel(n),
|
|
174
|
-
tooltip: getNodeTooltip(n),
|
|
175
60
|
type: n.type,
|
|
176
61
|
children: [],
|
|
177
62
|
_originalNode: n,
|
|
@@ -195,17 +80,15 @@
|
|
|
195
80
|
}
|
|
196
81
|
|
|
197
82
|
if (rootNode) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const convertGraphToTree = (node: ProcessedNode): ProcessedNode => {
|
|
204
|
-
if (globalVisited.has(node.id)) {
|
|
83
|
+
const convertGraphToTree = (
|
|
84
|
+
node: ProcessedNode,
|
|
85
|
+
visited = new Set<string>(),
|
|
86
|
+
): ProcessedNode => {
|
|
87
|
+
if (visited.has(node.id)) {
|
|
205
88
|
return {
|
|
206
89
|
...node,
|
|
207
90
|
id: `${node.id}_ref`,
|
|
208
|
-
name:
|
|
91
|
+
name: `${node.name} (ref)`,
|
|
209
92
|
type: `${node.type}_ref`,
|
|
210
93
|
children: [],
|
|
211
94
|
_isReference: true,
|
|
@@ -213,8 +96,9 @@
|
|
|
213
96
|
}
|
|
214
97
|
}
|
|
215
98
|
|
|
216
|
-
|
|
217
|
-
|
|
99
|
+
const newVisited = new Set(visited)
|
|
100
|
+
newVisited.add(node.id)
|
|
101
|
+
return { ...node, children: node.children.map((c) => convertGraphToTree(c, newVisited)) }
|
|
218
102
|
}
|
|
219
103
|
|
|
220
104
|
const treeData = convertGraphToTree(rootNode)
|
|
@@ -243,143 +127,50 @@
|
|
|
243
127
|
|
|
244
128
|
const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`)
|
|
245
129
|
|
|
246
|
-
const linkGenerator = d3
|
|
247
|
-
.linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, d3.HierarchyPointNode<ProcessedNode>>()
|
|
248
|
-
.x((d) => d.y)
|
|
249
|
-
.y((d) => d.x)
|
|
250
|
-
|
|
251
130
|
g.selectAll('.link')
|
|
252
131
|
.data(treeRoot.links())
|
|
253
132
|
.enter()
|
|
254
133
|
.append('path')
|
|
255
134
|
.attr('class', 'link')
|
|
256
135
|
.attr('fill', 'none')
|
|
257
|
-
|
|
258
|
-
.attr('stroke', (d: any) => {
|
|
259
|
-
if (d.source.data.type !== 'condition') return '#94a3b8'
|
|
260
|
-
const targetId = d.target.data._isReference
|
|
261
|
-
? d.target.data._referenceTo
|
|
262
|
-
: d.target.data.id
|
|
263
|
-
const edge = d.source.data._originalEdges.find(
|
|
264
|
-
(e: ExperienceEdge) => e.destinationNodeId === targetId,
|
|
265
|
-
)
|
|
266
|
-
if (edge?.sourceHandle === 'true') return '#10b981'
|
|
267
|
-
if (edge?.sourceHandle === 'false') return '#ef4444'
|
|
268
|
-
return '#94a3b8'
|
|
269
|
-
})
|
|
136
|
+
.attr('stroke', '#94a3b8')
|
|
270
137
|
.attr('stroke-width', 2)
|
|
271
|
-
.attr(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
.append('text')
|
|
279
|
-
.attr('class', 'edge-label')
|
|
280
|
-
.attr('text-anchor', 'middle')
|
|
281
|
-
.attr('font-size', '10px')
|
|
282
|
-
.attr('font-weight', 'bold')
|
|
283
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
284
|
-
.attr('x', (d: any) => (d.source.y + d.target.y) / 2)
|
|
285
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
286
|
-
.attr('y', (d: any) => (d.source.x + d.target.x) / 2 - 6)
|
|
287
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
288
|
-
.attr('fill', (d: any) => {
|
|
289
|
-
const targetId = d.target.data._isReference
|
|
290
|
-
? d.target.data._referenceTo
|
|
291
|
-
: d.target.data.id
|
|
292
|
-
const edge = d.source.data._originalEdges.find(
|
|
293
|
-
(e: ExperienceEdge) => e.destinationNodeId === targetId,
|
|
294
|
-
)
|
|
295
|
-
if (edge?.sourceHandle === 'true') return '#10b981'
|
|
296
|
-
if (edge?.sourceHandle === 'false') return '#ef4444'
|
|
297
|
-
return '#64748b'
|
|
298
|
-
})
|
|
299
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
300
|
-
.text((d: any) => {
|
|
301
|
-
const targetId = d.target.data._isReference
|
|
302
|
-
? d.target.data._referenceTo
|
|
303
|
-
: d.target.data.id
|
|
304
|
-
const edge = d.source.data._originalEdges.find(
|
|
305
|
-
(e: ExperienceEdge) => e.destinationNodeId === targetId,
|
|
306
|
-
)
|
|
307
|
-
if (edge?.sourceHandle === 'true') return 'Yes'
|
|
308
|
-
if (edge?.sourceHandle === 'false') return 'No'
|
|
309
|
-
return ''
|
|
310
|
-
})
|
|
138
|
+
.attr(
|
|
139
|
+
'd',
|
|
140
|
+
d3
|
|
141
|
+
.linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, ProcessedNode>()
|
|
142
|
+
.x((d) => d.y)
|
|
143
|
+
.y((d) => d.x),
|
|
144
|
+
)
|
|
311
145
|
|
|
312
|
-
const
|
|
146
|
+
const nodes = g
|
|
313
147
|
.selectAll('.node')
|
|
314
148
|
.data(treeRoot.descendants())
|
|
315
149
|
.enter()
|
|
316
150
|
.append('g')
|
|
317
151
|
.attr('class', 'node')
|
|
318
|
-
|
|
319
|
-
.attr('transform', (d: any) => `translate(${d.y},${d.x})`)
|
|
152
|
+
.attr('transform', (d: any) => `translate(${d.y},${d.x})`) // d3.HierarchyPointNode uses x, y
|
|
320
153
|
|
|
321
|
-
|
|
154
|
+
nodes
|
|
322
155
|
.append('circle')
|
|
323
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
324
156
|
.attr('r', (d: any) => (d.data.type === 'origin' ? 15 : 10))
|
|
325
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
326
157
|
.attr('fill', (d: any) => {
|
|
327
158
|
if (d.data._isReference) return '#38bdf8'
|
|
328
|
-
if (d.data.type === 'origin') return '#
|
|
329
|
-
if (d.data.type === '
|
|
330
|
-
|
|
331
|
-
return '#3b82f6'
|
|
159
|
+
if (d.data.type === 'origin') return '#00cd90'
|
|
160
|
+
if (d.data.type === 'conditional') return '#f59e0b'
|
|
161
|
+
return '#1748ff'
|
|
332
162
|
})
|
|
333
163
|
.attr('stroke', '#fff')
|
|
334
164
|
.attr('stroke-width', 2)
|
|
335
165
|
|
|
336
|
-
|
|
337
|
-
tooltipEl = d3
|
|
338
|
-
.select(svgEl.parentElement)
|
|
339
|
-
.append('div')
|
|
340
|
-
|
|
341
|
-
const tooltip = tooltipEl
|
|
342
|
-
.style('position', 'absolute')
|
|
343
|
-
.style('pointer-events', 'none')
|
|
344
|
-
.style('background', '#1e293b')
|
|
345
|
-
.style('color', '#f8fafc')
|
|
346
|
-
.style('padding', '6px 10px')
|
|
347
|
-
.style('border-radius', '6px')
|
|
348
|
-
.style('font-size', '11px')
|
|
349
|
-
.style('max-width', '260px')
|
|
350
|
-
.style('white-space', 'pre-wrap')
|
|
351
|
-
.style('opacity', '0')
|
|
352
|
-
.style('z-index', '10')
|
|
353
|
-
|
|
354
|
-
circles
|
|
355
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
356
|
-
.filter((d: any) => d.data.tooltip)
|
|
357
|
-
.style('cursor', 'default')
|
|
358
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
359
|
-
.on('mouseenter', function showTooltip(event: MouseEvent, d: any) {
|
|
360
|
-
const container = svgEl.parentElement
|
|
361
|
-
if (!container || !d.data.tooltip) return
|
|
362
|
-
const rect = container.getBoundingClientRect()
|
|
363
|
-
tooltip
|
|
364
|
-
.text(d.data.tooltip)
|
|
365
|
-
.style('left', `${event.clientX - rect.left + 12}px`)
|
|
366
|
-
.style('top', `${event.clientY - rect.top - 8}px`)
|
|
367
|
-
.style('opacity', '1')
|
|
368
|
-
})
|
|
369
|
-
.on('mouseleave', function hideTooltip() {
|
|
370
|
-
tooltip.style('opacity', '0')
|
|
371
|
-
})
|
|
372
|
-
|
|
373
|
-
nodesSelection
|
|
166
|
+
nodes
|
|
374
167
|
.append('text')
|
|
375
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
376
168
|
.attr('dy', (d: any) => (d.children ? -15 : 25))
|
|
377
169
|
.attr('text-anchor', 'middle')
|
|
378
170
|
.attr('font-size', '12px')
|
|
379
171
|
.attr('fill', '#1e293b')
|
|
380
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
|
|
381
172
|
.text((d: any) =>
|
|
382
|
-
d.data.name.length >
|
|
173
|
+
d.data.name.length > 25 ? `${d.data.name.slice(0, 22)}...` : d.data.name,
|
|
383
174
|
)
|
|
384
175
|
|
|
385
176
|
svg.on('dblclick.zoom', null) // Remove default double click zoom
|
|
@@ -408,27 +199,11 @@
|
|
|
408
199
|
treeRenderingDone = true
|
|
409
200
|
}
|
|
410
201
|
|
|
411
|
-
/**
|
|
412
|
-
* Zooms the tree visualization in by 20%.
|
|
413
|
-
*
|
|
414
|
-
* @example
|
|
415
|
-
* ```typescript
|
|
416
|
-
* handleZoomIn() // increases zoom level
|
|
417
|
-
* ```
|
|
418
|
-
*/
|
|
419
202
|
function handleZoomIn() {
|
|
420
203
|
if (!svgEl || !zoomBehavior) return
|
|
421
204
|
d3.select(svgEl).transition().duration(250).call(zoomBehavior.scaleBy, 1.2)
|
|
422
205
|
}
|
|
423
206
|
|
|
424
|
-
/**
|
|
425
|
-
* Zooms the tree visualization out by 20%.
|
|
426
|
-
*
|
|
427
|
-
* @example
|
|
428
|
-
* ```typescript
|
|
429
|
-
* handleZoomOut() // decreases zoom level
|
|
430
|
-
* ```
|
|
431
|
-
*/
|
|
432
207
|
function handleZoomOut() {
|
|
433
208
|
if (!svgEl || !zoomBehavior) return
|
|
434
209
|
d3.select(svgEl)
|
|
@@ -437,14 +212,6 @@
|
|
|
437
212
|
.call(zoomBehavior.scaleBy, 1 / 1.2)
|
|
438
213
|
}
|
|
439
214
|
|
|
440
|
-
/**
|
|
441
|
-
* Resets the tree visualization zoom and position to the default centered view.
|
|
442
|
-
*
|
|
443
|
-
* @example
|
|
444
|
-
* ```typescript
|
|
445
|
-
* handleReset() // resets zoom to default
|
|
446
|
-
* ```
|
|
447
|
-
*/
|
|
448
215
|
function handleReset() {
|
|
449
216
|
if (!svgEl || !zoomBehavior) return
|
|
450
217
|
const width = svgEl.clientWidth
|
|
@@ -458,15 +225,6 @@
|
|
|
458
225
|
d3.select(svgEl).transition().duration(750).call(zoomBehavior.transform, initialTransform)
|
|
459
226
|
}
|
|
460
227
|
|
|
461
|
-
/**
|
|
462
|
-
* Closes the node details panel and resets all link/node highlights.
|
|
463
|
-
*
|
|
464
|
-
* @example
|
|
465
|
-
* ```typescript
|
|
466
|
-
* closeNodeDetails() // resets selection and highlighting
|
|
467
|
-
* ```
|
|
468
|
-
*/
|
|
469
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- reserved for future node details panel
|
|
470
228
|
function closeNodeDetails() {
|
|
471
229
|
selectedNode = null
|
|
472
230
|
if (svgEl) {
|
|
@@ -479,22 +237,74 @@
|
|
|
479
237
|
}
|
|
480
238
|
</script>
|
|
481
239
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
<
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
<
|
|
489
|
-
|
|
490
|
-
{
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
240
|
+
<div class="graph">
|
|
241
|
+
{#if loading}
|
|
242
|
+
<div class="loading-container">Loading visualization...</div>
|
|
243
|
+
{:else if error}
|
|
244
|
+
<div class="error-container">{error}</div>
|
|
245
|
+
{:else}
|
|
246
|
+
<div class="tree-container">
|
|
247
|
+
<div class="controls">
|
|
248
|
+
<button on:click={handleZoomIn} title="Zoom In">
|
|
249
|
+
<span>Zoom in</span>
|
|
250
|
+
</button>
|
|
251
|
+
<button on:click={handleZoomOut} title="Zoom Out">
|
|
252
|
+
<span>Zoom out</span>
|
|
253
|
+
</button>
|
|
254
|
+
<button on:click={handleReset} title="Reset View">
|
|
255
|
+
<span>Reset</span>
|
|
256
|
+
</button>
|
|
257
|
+
<span class="zoom-level">{Math.round(zoom * 100)}%</span>
|
|
258
|
+
</div>
|
|
259
|
+
<svg bind:this={svgEl} class="tree-svg"></svg>
|
|
260
|
+
</div>
|
|
261
|
+
{/if}
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<style>
|
|
265
|
+
.graph {
|
|
266
|
+
border: 1px solid lightgray;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.loading-container,
|
|
270
|
+
.error-container {
|
|
271
|
+
display: flex;
|
|
272
|
+
height: 100%;
|
|
273
|
+
align-items: center;
|
|
274
|
+
justify-content: center;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.error-container {
|
|
278
|
+
color: #ef4444;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.tree-container {
|
|
282
|
+
position: relative;
|
|
283
|
+
height: 100%;
|
|
284
|
+
width: 100%;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.controls {
|
|
288
|
+
position: absolute;
|
|
289
|
+
right: 1rem;
|
|
290
|
+
top: 1rem;
|
|
291
|
+
z-index: 10;
|
|
292
|
+
display: flex;
|
|
293
|
+
gap: 0.5rem;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.zoom-level {
|
|
297
|
+
display: flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
border-radius: 0.25rem;
|
|
300
|
+
background-color: rgba(255, 255, 255, 0.8);
|
|
301
|
+
padding: 0.25rem 0.5rem;
|
|
302
|
+
font-size: 0.75rem;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.tree-svg {
|
|
306
|
+
height: 100%;
|
|
307
|
+
width: 100%;
|
|
308
|
+
background-color: white;
|
|
309
|
+
}
|
|
310
|
+
</style>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
/** @file Screen listing all registered experiences as tabs in the devtools panel. */
|
|
3
2
|
import Tabs from '../../components/Tabs.svelte'
|
|
4
3
|
import { getPicked } from '../../../../hooks'
|
|
5
4
|
import ExperienceScreen from './ExperienceScreen.svelte'
|
|
@@ -10,7 +9,7 @@
|
|
|
10
9
|
$: tabs = $experiences.map((experience) => {
|
|
11
10
|
return {
|
|
12
11
|
id: experience.id,
|
|
13
|
-
label: experience.
|
|
12
|
+
label: experience.id,
|
|
14
13
|
component: ExperienceScreen,
|
|
15
14
|
props: {
|
|
16
15
|
experience,
|
|
@@ -1,26 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getPicked } from '../../../../hooks'
|
|
3
|
-
import { Card, DataTable, EmptyState, Stack } from '../../ui'
|
|
4
3
|
|
|
5
4
|
const client = getPicked()
|
|
6
5
|
|
|
7
6
|
$: frameworks = client.frameworks.listStore
|
|
8
|
-
|
|
9
|
-
const columns = [{ key: 'name', header: 'Name' }]
|
|
10
7
|
</script>
|
|
11
8
|
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
<div>
|
|
10
|
+
<table>
|
|
11
|
+
<thead>
|
|
12
|
+
<tr>
|
|
13
|
+
<th>Name</th>
|
|
14
|
+
</tr>
|
|
15
|
+
</thead>
|
|
16
|
+
<tbody>
|
|
17
|
+
{#each $frameworks as framework (framework.name)}
|
|
17
18
|
<tr>
|
|
18
19
|
<td>{framework.name}</td>
|
|
19
20
|
</tr>
|
|
20
|
-
{/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
</DataTable>
|
|
25
|
-
</Card>
|
|
26
|
-
</Stack>
|
|
21
|
+
{/each}
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
</div>
|
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HistoryEntry } from '@picked/client'
|
|
3
3
|
import HistoryEntryListItem from './HistoryEntryListItem.svelte'
|
|
4
|
-
import { Card, DataTable, EmptyState, Stack } from '../../ui'
|
|
5
4
|
|
|
6
5
|
export let historyEntries: HistoryEntry[]
|
|
7
|
-
|
|
8
|
-
const columns = [
|
|
9
|
-
{ key: 'id', header: 'ID' },
|
|
10
|
-
{ key: 'experienceId', header: 'Experience ID' },
|
|
11
|
-
{ key: 'nodeId', header: 'Node ID' },
|
|
12
|
-
{ key: 'nodeType', header: 'Node Type' },
|
|
13
|
-
{ key: 'at', header: 'At' },
|
|
14
|
-
]
|
|
15
6
|
</script>
|
|
16
7
|
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
8
|
+
<table>
|
|
9
|
+
<thead>
|
|
10
|
+
<tr>
|
|
11
|
+
<th>ID</th>
|
|
12
|
+
<th>Experience ID</th>
|
|
13
|
+
<th>Node ID</th>
|
|
14
|
+
<th>Node Type</th>
|
|
15
|
+
<th>At</th>
|
|
16
|
+
</tr>
|
|
17
|
+
</thead>
|
|
18
|
+
<tbody>
|
|
19
|
+
{#each historyEntries as historyEntry (historyEntry.id)}
|
|
20
|
+
<HistoryEntryListItem {historyEntry} />
|
|
21
|
+
{/each}
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
/** @file List item row displaying a navigation history entry in the devtools history screen. */
|
|
3
2
|
import type { HistoryEntry } from '@picked/client'
|
|
4
|
-
import { getPicked } from '../../../../hooks'
|
|
5
3
|
|
|
6
4
|
export let historyEntry: HistoryEntry
|
|
7
|
-
|
|
8
|
-
const client = getPicked()
|
|
9
|
-
const experienceStore = client.experiences.getById(historyEntry.experienceId)
|
|
10
|
-
$: experienceName = $experienceStore?.name ?? historyEntry.experienceId
|
|
11
5
|
</script>
|
|
12
6
|
|
|
13
7
|
<tr>
|
|
14
8
|
<td>{historyEntry.id}</td>
|
|
15
|
-
<td>{
|
|
9
|
+
<td>{historyEntry.experienceId}</td>
|
|
16
10
|
<td>{historyEntry.node.id}</td>
|
|
17
11
|
<td>{historyEntry.node.type}</td>
|
|
18
12
|
<td>{historyEntry.at}</td>
|