Files
ComfyUI_frontend/src/components/NodeSearchBoxPopover.vue

171 lines
4.5 KiB
Vue

<template>
<div>
<Dialog
v-model:visible="visible"
pt:root="invisible-dialog-root"
pt:mask="node-search-box-dialog-mask"
modal
:dismissable-mask="dismissable"
@hide="clearFilters"
>
<template #container>
<NodeSearchBox
:filters="nodeFilters"
@add-filter="addFilter"
@remove-filter="removeFilter"
@add-node="addNode"
/>
</template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { app } from '@/scripts/app'
import { onMounted, onUnmounted, reactive, ref } from 'vue'
import NodeSearchBox from './NodeSearchBox.vue'
import Dialog from 'primevue/dialog'
import {
INodeSlot,
LiteGraphCanvasEvent,
LGraphNode,
LinkReleaseContext
} from '@comfyorg/litegraph'
import { FilterAndValue } from '@/services/nodeSearchService'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
interface LiteGraphPointerEvent extends Event {
canvasX: number
canvasY: number
}
const visible = ref(false)
const dismissable = ref(true)
const triggerEvent = ref<LiteGraphCanvasEvent | null>(null)
const getNewNodeLocation = (): [number, number] => {
if (triggerEvent.value === null) {
return [100, 100]
}
const originalEvent = triggerEvent.value.detail
.originalEvent as LiteGraphPointerEvent
return [originalEvent.canvasX, originalEvent.canvasY]
}
const nodeFilters = reactive([])
const addFilter = (filter: FilterAndValue) => {
nodeFilters.push(filter)
}
const removeFilter = (filter: FilterAndValue) => {
const index = nodeFilters.findIndex((f) => f === filter)
if (index !== -1) {
nodeFilters.splice(index, 1)
}
}
const clearFilters = () => {
nodeFilters.splice(0, nodeFilters.length)
}
const closeDialog = () => {
visible.value = false
}
const connectNodeOnLinkRelease = (
node: LGraphNode,
context: LinkReleaseContext
) => {
const destIsInput = context.node_from !== undefined
const srcNode = (
destIsInput ? context.node_from : context.node_to
) as LGraphNode
const srcSlotIndex: number = context.slot_from.slot_index
const linkDataType = destIsInput
? context.type_filter_in
: context.type_filter_out
const destSlots = destIsInput ? node.inputs : node.outputs
const destSlotIndex = destSlots.findIndex(
(slot: INodeSlot) => slot.type === linkDataType
)
if (destSlotIndex === -1) {
console.warn(
`Could not find slot with type ${linkDataType} on node ${node.title}`
)
return
}
if (destIsInput) {
srcNode.connect(srcSlotIndex, node, destSlotIndex)
} else {
node.connect(destSlotIndex, srcNode, srcSlotIndex)
}
}
const addNode = (nodeDef: ComfyNodeDefImpl) => {
closeDialog()
const node = app.addNodeOnGraph(nodeDef, { pos: getNewNodeLocation() })
const eventDetail = triggerEvent.value.detail
if (eventDetail.subType === 'empty-release') {
connectNodeOnLinkRelease(node, eventDetail.linkReleaseContext)
}
}
const canvasEventHandler = (e: LiteGraphCanvasEvent) => {
const shiftPressed = (e.detail.originalEvent as KeyboardEvent).shiftKey
// Ignore empty releases unless shift is pressed
// Empty release without shift is trigger right click menu
if (e.detail.subType === 'empty-release' && !shiftPressed) {
return
}
if (e.detail.subType === 'empty-release') {
const destIsInput = e.detail.linkReleaseContext.node_from !== undefined
const filter = useNodeDefStore().nodeSearchService.getFilterById(
destIsInput ? 'input' : 'output'
)
const value = destIsInput
? e.detail.linkReleaseContext.type_filter_in
: e.detail.linkReleaseContext.type_filter_out
addFilter([filter, value])
}
triggerEvent.value = e
visible.value = true
// Prevent the dialog from being dismissed immediately
dismissable.value = false
setTimeout(() => {
dismissable.value = true
}, 300)
}
const handleEscapeKeyPress = (event) => {
if (event.key === 'Escape') {
closeDialog()
}
}
onMounted(() => {
document.addEventListener('litegraph:canvas', canvasEventHandler)
document.addEventListener('keydown', handleEscapeKeyPress)
})
onUnmounted(() => {
document.removeEventListener('litegraph:canvas', canvasEventHandler)
document.removeEventListener('keydown', handleEscapeKeyPress)
})
</script>
<style>
.invisible-dialog-root {
width: 30%;
min-width: 24rem;
max-width: 48rem;
border: 0 !important;
background-color: transparent !important;
margin-top: 25vh;
}
.node-search-box-dialog-mask {
align-items: flex-start !important;
}
</style>