mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
Early app setup handling
This commit is contained in:
@@ -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'" />
|
||||
|
||||
148
src/components/rightSidePanel/TabApp.vue
Normal file
148
src/components/rightSidePanel/TabApp.vue
Normal 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>
|
||||
25
src/stores/hoveredStore.ts
Normal file
25
src/stores/hoveredStore.ts
Normal 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
|
||||
}
|
||||
})
|
||||
@@ -10,6 +10,7 @@ export type RightSidePanelTab =
|
||||
| 'settings'
|
||||
| 'info'
|
||||
| 'subgraph'
|
||||
| 'app'
|
||||
|
||||
type RightSidePanelSection = 'advanced-inputs' | string
|
||||
|
||||
|
||||
Reference in New Issue
Block a user