Early app setup handling

This commit is contained in:
Austin Mroz
2026-02-14 20:56:35 -08:00
parent f851c3189f
commit d6c6ad6da2
4 changed files with 180 additions and 0 deletions

View File

@@ -20,6 +20,7 @@ import type { RightSidePanelTab } from '@/stores/workspace/rightSidePanelStore'
import { resolveNodeDisplayName } from '@/utils/nodeTitleUtil'
import { cn } from '@/utils/tailwindUtil'
import TabApp from './TabApp.vue'
import TabError from './TabError.vue'
import TabInfo from './info/TabInfo.vue'
import TabGlobalParameters from './parameters/TabGlobalParameters.vue'
@@ -109,6 +110,10 @@ const tabs = computed<RightSidePanelTabList>(() => {
icon: 'icon-[lucide--octagon-alert] bg-node-stroke-error ml-1'
})
}
list.push({
label: () => 'app',
value: 'app'
})
list.push({
label: () =>
@@ -298,6 +303,7 @@ function handleProxyWidgetsUpdate(value: ProxyWidgetsProperty) {
<!-- Panel Content -->
<div class="scrollbar-thin flex-1 overflow-y-auto">
<template v-if="!hasSelection">
<TabApp v-if="activeTab === 'app'" />
<TabGlobalParameters v-if="activeTab === 'parameters'" />
<TabNodes v-else-if="activeTab === 'nodes'" />
<TabGlobalSettings v-else-if="activeTab === 'settings'" />

View File

@@ -0,0 +1,148 @@
<script setup lang="ts">
import { useElementBounding, useEventListener } from '@vueuse/core'
import { computed, reactive, toValue } from 'vue'
import type { MaybeRef } from 'vue'
import { useI18n } from 'vue-i18n'
import type { LGraphNode, NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useHoveredStore } from '@/stores/hoveredStore'
const canvasStore = useCanvasStore()
const hoveredStore = useHoveredStore()
const settingStore = useSettingStore()
const { t } = useI18n()
type BoundStyle = { top: string; left: string; width: string; height: string }
const selectedInputs = reactive<Record<string, MaybeRef<BoundStyle>>>({})
const selectedOutputs = reactive<Record<string, MaybeRef<BoundStyle>>>({})
function hoveredKey() {
return `Hovered: ${hoveredStore.hoveredNodeId}-${hoveredStore.hoveredWidgetName}`
}
const eventTarget = [
document.getElementById('graph-canvas')!,
document.querySelector('[data-testid="transform-pane"]')!
]
function getHovered():
| undefined
| [LGraphNode, undefined]
| [LGraphNode, IBaseWidget] {
const canvas = canvasStore.getCanvas()
const { graph } = canvas
if (!canvas || !graph) return
if (settingStore.get('Comfy.VueNodes.Enabled')) {
const node = graph.getNodeById(hoveredStore.hoveredNodeId)
if (!node) return
const widget = node.widgets?.find(
(w) => w.name === hoveredStore.hoveredWidgetName
)
return [node, widget]
}
const [x, y] = canvas.graph_mouse
const node = graph.getNodeOnPos(x, y, graph.nodes)
if (!node) return
return [node, node.getWidgetOnPos(x, y, false)]
}
function elementPosition(e: HTMLElement) {
const bounding = useElementBounding(e)
return computed(() => ({
width: `${bounding.width.value}px`,
height: `${bounding.height.value}px`,
left: `${bounding.left.value}px`,
top: `${bounding.top.value}px`
}))
}
function getBounding(nodeId: NodeId, widgetName?: string) {
if (settingStore.get('Comfy.VueNodes.Enabled')) {
const element = document.querySelector(
widgetName
? `[data-node-id="${nodeId}"] [data-widget-name="${widgetName}"`
: `[data-node-id="${nodeId}"]`
)
return element instanceof HTMLElement ? elementPosition(element) : undefined
}
}
useEventListener(
eventTarget,
'pointerdown',
(e) => {
e.preventDefault()
e.stopPropagation()
},
{ capture: true }
)
useEventListener(
eventTarget,
'pointerup',
(e) => {
e.preventDefault()
e.stopPropagation()
},
{ capture: true }
)
useEventListener(
eventTarget,
'click',
(e) => {
e.preventDefault()
e.stopPropagation()
const [node, widget] = getHovered() ?? []
if (!node) return
if (!widget) {
const key = `${node.id}: ${node.title}`
if (!node.constructor.nodeData?.output_node) return
const bounding = getBounding(node.id)
if (!(key in selectedOutputs) && bounding) selectedOutputs[key] = bounding
else delete selectedOutputs[key]
return
}
const key = `${node.id}: ${widget.name}`
const bounding = getBounding(node.id, widget.name)
if (!(key in selectedInputs) && bounding) selectedInputs[key] = bounding
else delete selectedInputs[key]
},
{ capture: true }
)
</script>
<template>
<div class="m-4">
{{
hoveredStore.hoveredWidgetName ? hoveredKey() : t('[PH]Nothing selected')
}}
</div>
<div class="h-5" />
{{ t('[PH]Inputs:') }}
<div v-for="(_, key) in selectedInputs" :key v-text="key" />
<div class="h-5" />
{{ t('[PH]Outputs:') }}
<div v-for="(_, key) in selectedOutputs" :key v-text="key" />
<Teleport to="body">
<div class="absolute w-full h-full pointer-events-none">
<div
v-for="(style, key) in selectedInputs"
:key
:style="toValue(style)"
class="fixed bg-blue-400/50 rounded-lg"
/>
<div
v-for="(style, key) in selectedOutputs"
:key
:style="toValue(style)"
class="fixed bg-orange-400/50 rounded-lg"
/>
</div>
</Teleport>
</template>

View File

@@ -0,0 +1,25 @@
import { useElementByPoint, useMouse } from '@vueuse/core'
import { defineStore } from 'pinia'
import { computed } from 'vue'
export const useHoveredStore = defineStore('hovered', () => {
const { x, y } = useMouse({ type: 'client' })
const { element } = useElementByPoint({ x, y })
const hoveredWidgetName = computed(() => {
const widgetEl = element.value?.closest('.lg-node-widget')
if (!(widgetEl instanceof HTMLElement)) return
return widgetEl.dataset.widgetName
})
const hoveredNodeId = computed(() => {
const nodeEl = element.value?.closest('.lg-node')
if (!(nodeEl instanceof HTMLElement)) return
return nodeEl.dataset.nodeId
})
return {
hoveredNodeId,
hoveredWidgetName
}
})

View File

@@ -10,6 +10,7 @@ export type RightSidePanelTab =
| 'settings'
| 'info'
| 'subgraph'
| 'app'
type RightSidePanelSection = 'advanced-inputs' | string