Files
windmill/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte
T
Diego ImbertandClaude Opus 4.6 5d79f33590 Final Svelte 5 migration (#8211)
* Remove $$props.field usage

* Rename slots to ensure no hyphen

* _props

* _trigger

* OnSelectedIteration type correct capitalization

* rename _content

* Remove afterUpdate

* Migrate everything to svelte 5

* array bind

* Fix popover

* type never

* nit fixes

* Fixed many trivial errors

* onClick

* Fix errors

* use let:

* nit typing

* fix: wrap state_referenced_locally vars with untrack()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add untrack import

* Fix all syntax errors due to untrack migration

* Fix undefined errors

* Fix more undefined errors

* untrack(() => initialOpen)

* svelte-ignore

* Fix state_descriptors_fixed error in Chart.svelte

Use $state.snapshot() to pass plain copies of data/options to Chart.js
instead of $state proxies. Chart.js's listenArrayEvents tries to define
property descriptors on data arrays, which Svelte 5 proxies reject.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* nit typing

* Merge issue

* Fix "path is not set" error in resource picker / editor

* Fix InputTransformForm error when rerunning some flows

* fix npm run check

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-05 18:11:40 +01:00

150 lines
4.1 KiB
Svelte

<script lang="ts">
import { createBubbler, stopPropagation } from 'svelte/legacy'
const bubble = createBubbler()
import Dropdown from '$lib/components/DropdownV2.svelte'
import { classNames } from '$lib/utils'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import type { AppViewerContext } from '../types'
import type { DecisionTreeNode } from './component'
import { isDebugging } from './settingsPanel/decisionTree/utils'
import { X, Bug } from 'lucide-svelte'
interface Props {
nodes?: DecisionTreeNode[]
id: string
isSmall?: boolean
componentIsDebugging?: boolean
}
let { nodes = [], id, isSmall = false, componentIsDebugging = $bindable(false) }: Props = $props()
$effect(() => {
componentIsDebugging = isDebugging($debuggingComponents, id)
})
const { componentControl, debuggingComponents, worldStore } =
getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
let currentNodeId: string = $state($worldStore.outputsById[untrack(() => id)]?.currentNodeId?.peak() ?? 'a')
function subscribeToCurrentNode(id: string) {
return $worldStore.outputsById[id]?.currentNodeId?.subscribe(
{
id: `id-${id}-${currentNodeId}`,
next: (value) => {
currentNodeId = value
}
},
currentNodeId
)
}
let subscription = subscribeToCurrentNode(untrack(() => id))
function onDebugNode(debuggedNodeIndex: number | undefined) {
if (debuggedNodeIndex === undefined) {
return
}
if (debuggedNodeIndex !== undefined && nodes[debuggedNodeIndex]?.id === undefined) {
currentNodeId = nodes[0]?.id ?? ''
$componentControl?.[id]?.setTab?.(0)
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
}
}
$effect(() => {
onDebugNode($debuggingComponents[id])
})
let renderCount: number = $state(0)
let lastNodes: DecisionTreeNode[] = untrack(() => nodes)
function onNodesChange(newNodes: DecisionTreeNode[]) {
if (JSON.stringify(newNodes) !== JSON.stringify(lastNodes)) {
lastNodes = newNodes
if (subscription) {
subscription?.()
}
subscription = subscribeToCurrentNode(id)
renderCount++
}
}
$effect(() => {
onNodesChange(nodes)
})
async function getDropdownItems() {
return [
// Debug node items
...(nodes ?? []).map((node, index) => ({
displayName: `Debug node ${node.label}`,
action: () => {
$componentControl?.[id]?.setTab?.(index)
$debuggingComponents[id] = index
},
type: 'action' as const
})),
// Reset debug mode item
{
displayName: 'Reset debug mode',
action: () => {
$componentControl?.[id]?.setTab?.(0)
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
},
type: 'delete' as const
}
]
}
</script>
{#key renderCount}
<Dropdown items={getDropdownItems} class="w-fit h-auto" usePointerDownOutside={true}>
{#snippet buttonReplacement()}
<button
title={'Debug tabs'}
class={classNames(
'px-1 text-2xs font-bold rounded cursor-pointer w-fit h-full',
componentIsDebugging
? ' hover:bg-red-300 hover:text-red-800'
: 'text-blue-600 hover:bg-blue-300 hover:text-blue-800'
)}
onclick={() => dispatch('triggerInlineEditor')}
onpointerdown={stopPropagation(bubble('pointerdown'))}
>
{#if componentIsDebugging}
<div class="flex flex-row items-center gap-2">
{`${isSmall ? '' : 'Debugging node'} ${nodes[$debuggingComponents[id] ?? 0]?.id}`}
<!-- svelte-ignore node_invalid_placement_ssr -->
<button
onclick={() => {
$componentControl?.[id]?.setTab?.(0)
$debuggingComponents = Object.fromEntries(
Object.entries($debuggingComponents).filter(([key]) => key !== id)
)
}}
>
<X size={11} />
</button>
</div>
{:else if isSmall}
<div class="flex h-full w-fit items-center"><Bug size={11} /></div>
{:else}<div class="whitespace-nowrap h-full"
>{`Debug nodes (current node: ${currentNodeId})`}</div
>{/if}
</button>
{/snippet}
</Dropdown>
{/key}