@picked/svelte 0.0.1-dev.1793776638554 → 0.0.1-dev.1793923852884

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 (50) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/experience/set-picked-context.js +1 -1
  3. package/dist/plugins/devtools/DevToolsExperience.svelte +155 -80
  4. package/dist/plugins/devtools/customTools.d.ts +2 -2
  5. package/dist/plugins/devtools/derive/answer-display/answer-display.d.ts +14 -0
  6. package/dist/plugins/devtools/derive/answer-display/answer-display.js +1 -1
  7. package/dist/plugins/devtools/derive/answer-display/index.d.ts +2 -2
  8. package/dist/plugins/devtools/derive/answer-display/index.js +1 -1
  9. package/dist/plugins/devtools/derive/guide-sections/guide-sections.js +1 -1
  10. package/dist/plugins/devtools/derive/index.d.ts +5 -5
  11. package/dist/plugins/devtools/derive/index.js +1 -1
  12. package/dist/plugins/devtools/derive/personas/index.d.ts +2 -2
  13. package/dist/plugins/devtools/derive/personas/index.js +1 -1
  14. package/dist/plugins/devtools/derive/personas/personas.d.ts +88 -7
  15. package/dist/plugins/devtools/derive/personas/personas.js +1 -1
  16. package/dist/plugins/devtools/derive/plain-language/index.d.ts +1 -1
  17. package/dist/plugins/devtools/derive/plain-language/index.js +1 -1
  18. package/dist/plugins/devtools/derive/plain-language/plain-language.d.ts +5 -5
  19. package/dist/plugins/devtools/derive/plain-language/plain-language.js +1 -1
  20. package/dist/plugins/devtools/index.d.ts +1 -1
  21. package/dist/plugins/devtools/launcher/Launcher.svelte +13 -4
  22. package/dist/plugins/devtools/options.d.ts +6 -4
  23. package/dist/plugins/devtools/options.js +1 -1
  24. package/dist/plugins/devtools/review/journey/ExperienceDetails.svelte +129 -0
  25. package/dist/plugins/devtools/review/journey/JourneyPanel.svelte +54 -8
  26. package/dist/plugins/devtools/review/personas/ChoiceButton.svelte +40 -6
  27. package/dist/plugins/devtools/review/personas/PersonaBuilder.svelte +69 -45
  28. package/dist/plugins/devtools/review/personas/PersonaCard.svelte +226 -43
  29. package/dist/plugins/devtools/review/personas/PersonasPanel.svelte +171 -74
  30. package/dist/plugins/devtools/review/personas/load-persona.d.ts +41 -20
  31. package/dist/plugins/devtools/review/personas/load-persona.js +1 -1
  32. package/dist/plugins/devtools/review/personas/persona-route.d.ts +87 -0
  33. package/dist/plugins/devtools/review/personas/persona-route.js +1 -0
  34. package/dist/plugins/devtools/review/personas/persona-session.d.ts +94 -0
  35. package/dist/plugins/devtools/review/personas/persona-session.js +1 -0
  36. package/dist/plugins/devtools/sidebar/Sidebar.svelte +14 -48
  37. package/dist/plugins/devtools/sidebar/SidebarRow.svelte +2 -2
  38. package/dist/plugins/devtools/sidebar/nav.d.ts +26 -39
  39. package/dist/plugins/devtools/sidebar/nav.js +1 -1
  40. package/package.json +2 -2
  41. package/dist/plugins/devtools/screens/experience-instances/ExperienceInstancesListScreen.svelte +0 -10
  42. package/dist/plugins/devtools/screens/experiences/ExperienceDetailsScreen.svelte +0 -44
  43. package/dist/plugins/devtools/screens/experiences/ExperienceExperienceInstancesListScreen.svelte +0 -13
  44. package/dist/plugins/devtools/screens/experiences/ExperienceScreen.svelte +0 -32
  45. package/dist/plugins/devtools/screens/experiences/ExperienceTreeScreen.svelte +0 -500
  46. package/dist/plugins/devtools/screens/experiences/ExperiencesListScreen.svelte +0 -22
  47. package/dist/plugins/devtools/screens/features/FeaturesListScreen.svelte +0 -239
  48. package/dist/plugins/devtools/screens/responses/ResponseTableRow.svelte +0 -286
  49. package/dist/plugins/devtools/screens/responses/ResponsesListScreen.svelte +0 -12
  50. package/dist/plugins/devtools/screens/responses/ResponsesTable.svelte +0 -88
@@ -1,500 +0,0 @@
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
- import * as d3 from 'd3'
5
- import type { ExperienceEdge, ExperienceNode, ExperienceStore } from '@picked/client'
6
- import { Badge, Button, Spinner, Stack, Text, VisualizationContainer } from '../../ui'
7
-
8
- export let experience: ExperienceStore
9
-
10
- let svgEl: SVGSVGElement
11
- let zoom = 1
12
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- reserved for future node details panel
13
- let selectedNode = null
14
- // eslint-disable-next-line @typescript-eslint/no-unused-vars, sonarjs/no-unused-collection -- reserved for future path highlighting
15
- let highlightedPath: string[][] = []
16
- let error: string | null = null
17
- const loading = false
18
-
19
- 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
-
27
- const tree = experience.treeStore
28
-
29
- $: nodes = $tree.nodes
30
- $: edges = $tree.edges
31
- $: conditions = $tree.conditions
32
-
33
-
34
- let zoomBehavior: d3.ZoomBehavior<SVGSVGElement, unknown>
35
-
36
- interface ProcessedNode {
37
- id: string
38
- name: string
39
- tooltip?: string
40
- type: string
41
- children: ProcessedNode[]
42
- _originalNode: ExperienceNode
43
- _originalEdges: ExperienceEdge[]
44
- _visited?: boolean
45
- _isReference?: boolean
46
- _referenceTo?: string
47
- }
48
-
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
- function renderGraph() {
136
- d3.select(svgEl).selectAll('*').remove()
137
-
138
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
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
141
- const outgoingEdges = new Map<string, ExperienceEdge[]>()
142
- edges.forEach((e) => {
143
- if (!outgoingEdges.has(e.sourceNodeId)) outgoingEdges.set(e.sourceNodeId, [])
144
- outgoingEdges.get(e.sourceNodeId)?.push(e)
145
- })
146
-
147
- const getNodeLabel = (node: ExperienceNode): string => {
148
- if (node.type === 'origin') return 'Start'
149
- if (node.type === 'step') return node.name ?? 'Unknown Step'
150
-
151
- if (node.type === 'condition') {
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
-
158
- return 'Unknown Node'
159
- }
160
-
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
- const processedNodes = new Map<string, ProcessedNode>()
170
- nodes.forEach((n) => {
171
- processedNodes.set(n.id, {
172
- id: n.id,
173
- name: getNodeLabel(n),
174
- tooltip: getNodeTooltip(n),
175
- type: n.type,
176
- children: [],
177
- _originalNode: n,
178
- _originalEdges: [],
179
- })
180
- })
181
- edges.forEach((e) => {
182
- const src = processedNodes.get(e.sourceNodeId)
183
- const tgt = processedNodes.get(e.destinationNodeId)
184
- if (src && tgt && !src.children.some((c) => c.id === tgt.id)) {
185
- src._originalEdges.push(e)
186
- src.children.push(tgt)
187
- }
188
- })
189
-
190
- const rootNode = processedNodes.get(nodes.find((n) => n.type === 'origin')?.id ?? '')
191
- if (!rootNode) {
192
- error = 'Origin node not found'
193
- } else {
194
- error = null
195
- }
196
-
197
- if (rootNode) {
198
- // Use a global visited set so join nodes (multiple incoming branches converging
199
- // to the same node) only appear once in the tree, with reference nodes on
200
- // subsequent visits to indicate the convergence point.
201
- // eslint-disable-next-line svelte/prefer-svelte-reactivity -- D3 visualization runs imperatively outside Svelte reactivity
202
- const globalVisited = new Set<string>()
203
- const convertGraphToTree = (node: ProcessedNode): ProcessedNode => {
204
- if (globalVisited.has(node.id)) {
205
- return {
206
- ...node,
207
- id: `${node.id}_ref`,
208
- name: `↩ ${node.name}`,
209
- type: `${node.type}_ref`,
210
- children: [],
211
- _isReference: true,
212
- _referenceTo: node.id,
213
- }
214
- }
215
-
216
- globalVisited.add(node.id)
217
- return { ...node, children: node.children.map((c) => convertGraphToTree(c)) }
218
- }
219
-
220
- const treeData = convertGraphToTree(rootNode)
221
- const width = svgEl.clientWidth
222
- const height = svgEl.clientHeight
223
- const margin = { top: 50, right: 120, bottom: 50, left: 120 }
224
- const innerWidth = width - margin.left - margin.right
225
- const innerHeight = height - margin.top - margin.bottom
226
-
227
- const treeLayout = d3
228
- .tree<ProcessedNode>()
229
- .size([innerHeight, innerWidth])
230
- .nodeSize([80, 200])
231
- const root = d3.hierarchy(treeData)
232
- const treeRoot = treeLayout(root)
233
-
234
- zoomBehavior = d3
235
- .zoom<SVGSVGElement, unknown>()
236
- .scaleExtent([0.1, 3])
237
- .on('zoom', (event) => {
238
- g.attr('transform', event.transform)
239
- zoom = event.transform.k
240
- })
241
-
242
- const svg = d3.select(svgEl).attr('width', width).attr('height', height).call(zoomBehavior)
243
-
244
- const g = svg.append('g').attr('transform', `translate(${margin.left},${margin.top})`)
245
-
246
- const linkGenerator = d3
247
- .linkHorizontal<d3.HierarchyPointLink<ProcessedNode>, d3.HierarchyPointNode<ProcessedNode>>()
248
- .x((d) => d.y)
249
- .y((d) => d.x)
250
-
251
- g.selectAll('.link')
252
- .data(treeRoot.links())
253
- .enter()
254
- .append('path')
255
- .attr('class', 'link')
256
- .attr('fill', 'none')
257
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
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
- })
270
- .attr('stroke-width', 2)
271
- .attr('d', linkGenerator)
272
-
273
- // Label true/false branches from condition nodes
274
- g.selectAll('.edge-label')
275
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
276
- .data(treeRoot.links().filter((d: any) => d.source.data.type === 'condition'))
277
- .enter()
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
- })
311
-
312
- const nodesSelection = g
313
- .selectAll('.node')
314
- .data(treeRoot.descendants())
315
- .enter()
316
- .append('g')
317
- .attr('class', 'node')
318
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
319
- .attr('transform', (d: any) => `translate(${d.y},${d.x})`)
320
-
321
- const circles = nodesSelection
322
- .append('circle')
323
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
324
- .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
- .attr('fill', (d: any) => {
327
- if (d.data._isReference) return '#38bdf8'
328
- if (d.data.type === 'origin') return '#6b7280'
329
- if (d.data.type === 'condition') return '#f59e0b'
330
- if (d.data.type === 'action') return '#22c55e'
331
- return '#3b82f6'
332
- })
333
- .attr('stroke', '#fff')
334
- .attr('stroke-width', 2)
335
-
336
- tooltipEl?.remove()
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
374
- .append('text')
375
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
376
- .attr('dy', (d: any) => (d.children ? -15 : 25))
377
- .attr('text-anchor', 'middle')
378
- .attr('font-size', '12px')
379
- .attr('fill', '#1e293b')
380
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- D3 callback types are untyped
381
- .text((d: any) =>
382
- d.data.name.length > 30 ? `${d.data.name.slice(0, 27)}...` : d.data.name,
383
- )
384
-
385
- svg.on('dblclick.zoom', null) // Remove default double click zoom
386
- svg.on('dblclick', () => {
387
- // Custom double click to reset highlights
388
- svg.selectAll('.link').attr('stroke-opacity', 1).attr('stroke-width', 2)
389
- svg.selectAll('.node').attr('opacity', 1)
390
- selectedNode = null
391
- highlightedPath = []
392
- })
393
-
394
- if (treeRoot.y !== undefined && treeRoot.x !== undefined) {
395
- const originX = treeRoot.y
396
- const originY = treeRoot.x
397
- const initialTransform = d3.zoomIdentity.translate(
398
- width / 2 - originX - margin.left,
399
- height / 2 - originY - margin.top,
400
- )
401
- svg.call(zoomBehavior.transform, initialTransform)
402
- }
403
- }
404
- }
405
-
406
- $: if (svgEl && !treeRenderingDone) {
407
- renderGraph()
408
- treeRenderingDone = true
409
- }
410
-
411
- /**
412
- * Zooms the tree visualization in by 20%.
413
- *
414
- * @example
415
- * ```typescript
416
- * handleZoomIn() // increases zoom level
417
- * ```
418
- */
419
- function handleZoomIn() {
420
- if (!svgEl || !zoomBehavior) return
421
- d3.select(svgEl).transition().duration(250).call(zoomBehavior.scaleBy, 1.2)
422
- }
423
-
424
- /**
425
- * Zooms the tree visualization out by 20%.
426
- *
427
- * @example
428
- * ```typescript
429
- * handleZoomOut() // decreases zoom level
430
- * ```
431
- */
432
- function handleZoomOut() {
433
- if (!svgEl || !zoomBehavior) return
434
- d3.select(svgEl)
435
- .transition()
436
- .duration(250)
437
- .call(zoomBehavior.scaleBy, 1 / 1.2)
438
- }
439
-
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
- function handleReset() {
449
- if (!svgEl || !zoomBehavior) return
450
- const width = svgEl.clientWidth
451
- const height = svgEl.clientHeight
452
- const margin = { top: 50, right: 120, bottom: 50, left: 120 }
453
-
454
- const originX = width / 2 - margin.left
455
- const originY = height / 2 - margin.top
456
- const initialTransform = d3.zoomIdentity.translate(originX, originY)
457
-
458
- d3.select(svgEl).transition().duration(750).call(zoomBehavior.transform, initialTransform)
459
- }
460
-
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
- function closeNodeDetails() {
471
- selectedNode = null
472
- if (svgEl) {
473
- const svg = d3.select(svgEl)
474
- svg.selectAll('.link').attr('stroke-opacity', 1).attr('stroke-width', 2)
475
- svg.selectAll('.node').attr('opacity', 1)
476
- }
477
-
478
- highlightedPath = []
479
- }
480
- </script>
481
-
482
- {#if loading}
483
- <Stack direction="horizontal" align="center" justify="center" fill>
484
- <Spinner label="Loading visualization" />
485
- </Stack>
486
- {:else if error}
487
- <Stack direction="horizontal" align="center" justify="center" fill>
488
- <Text color="error">{error}</Text>
489
- </Stack>
490
- {:else}
491
- <VisualizationContainer>
492
- {#snippet controls()}
493
- <Button variant="secondary" size="small" onclick={handleZoomIn}>Zoom in</Button>
494
- <Button variant="secondary" size="small" onclick={handleZoomOut}>Zoom out</Button>
495
- <Button variant="secondary" size="small" onclick={handleReset}>Reset</Button>
496
- <Badge status="default" size="small">{Math.round(zoom * 100)}%</Badge>
497
- {/snippet}
498
- <svg bind:this={svgEl}></svg>
499
- </VisualizationContainer>
500
- {/if}
@@ -1,22 +0,0 @@
1
- <script lang="ts">
2
- /** @file Screen listing all registered experiences as tabs in the devtools panel. */
3
- import Tabs from '../../components/Tabs.svelte'
4
- import { getPicked } from '../../../../hooks'
5
- import ExperienceScreen from './ExperienceScreen.svelte'
6
-
7
- const client = getPicked()
8
-
9
- const experiences = client.experiences.listStore
10
- $: tabs = $experiences.map((experience) => {
11
- return {
12
- id: experience.id,
13
- label: experience.name,
14
- component: ExperienceScreen,
15
- props: {
16
- experience,
17
- },
18
- }
19
- })
20
- </script>
21
-
22
- <Tabs {tabs} />