mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
Feat/vue nodes preview (#5747)
## Summary Create a LGraphNodePreview.vue component to use Vue Nodes for preview when hovering over search results / sidebar tree list. <!-- If this PR fixes an issue, uncomment and update the line below --> <!-- Fixes #ISSUE_NUMBER --> ## Screenshots (if applicable) <img width="3024" height="1642" alt="image" src="https://github.com/user-attachments/assets/d102b08e-2970-407b-aff8-3fa6333d5e38" /> <img width="3024" height="1646" alt="image (1)" src="https://github.com/user-attachments/assets/b5d378d5-3cf6-4cca-9fa1-741647e8d72c" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5747-Feat-vue-nodes-preview-2786d73d3650817dbf9af458bd5dda8c) by [Unito](https://www.unito.io) --------- Co-authored-by: JakeSchroeder <jake@axiom.co> Co-authored-by: AustinMroz <AustinMroz@users.noreply.github.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
<!-- Reference:
|
||||
https://github.com/Nuked88/ComfyUI-N-Sidebar/blob/7ae7da4a9761009fb6629bc04c683087a3e168db/app/js/functions/sb_fn.js#L149
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_sb_node_preview">
|
||||
<LGraphNodePreview v-if="shouldRenderVueNodes" :node-def="nodeDef" />
|
||||
<div v-else class="_sb_node_preview">
|
||||
<div class="_sb_table">
|
||||
<div
|
||||
class="node_header text-ellipsis mr-4"
|
||||
@@ -85,6 +85,8 @@ https://github.com/Nuked88/ComfyUI-N-Sidebar/blob/7ae7da4a9761009fb6629bc04c6830
|
||||
import _ from 'es-toolkit/compat'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
import LGraphNodePreview from '@/renderer/extensions/vueNodes/components/LGraphNodePreview.vue'
|
||||
import type { ComfyNodeDef as ComfyNodeDefV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
@@ -94,6 +96,8 @@ const { nodeDef } = defineProps<{
|
||||
nodeDef: ComfyNodeDefV2
|
||||
}>()
|
||||
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
const litegraphColors = computed(
|
||||
() => colorPaletteStore.completedActivePalette.colors.litegraph_base
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="scale-75">
|
||||
<div
|
||||
class="bg-white dark-theme:bg-charcoal-800 lg-node absolute rounded-2xl border border-solid border-sand-100 dark-theme:border-charcoal-600 outline-transparent -outline-offset-2 outline-2 pointer-events-none"
|
||||
>
|
||||
<NodeHeader :node-data="nodeData" :readonly="readonly" />
|
||||
|
||||
<div
|
||||
class="bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full mb-4"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-4 pb-4">
|
||||
<NodeSlots
|
||||
v-memo="[nodeData.inputs?.length, nodeData.outputs?.length]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
|
||||
<NodeWidgets
|
||||
v-if="nodeData.widgets?.length"
|
||||
v-memo="[nodeData.widgets?.length]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
|
||||
<NodeContent
|
||||
v-if="hasCustomContent"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:image-urls="nodeImageUrls"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import NodeContent from '@/renderer/extensions/vueNodes/components/NodeContent.vue'
|
||||
import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue'
|
||||
import NodeSlots from '@/renderer/extensions/vueNodes/components/NodeSlots.vue'
|
||||
import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue'
|
||||
import type { ComfyNodeDef as ComfyNodeDefV2 } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
|
||||
const { nodeDef } = defineProps<{
|
||||
nodeDef: ComfyNodeDefV2
|
||||
}>()
|
||||
|
||||
const widgetStore = useWidgetStore()
|
||||
|
||||
// Convert nodeDef into VueNodeData
|
||||
const nodeData = computed<VueNodeData>(() => {
|
||||
const widgets = Object.entries(nodeDef.inputs || {})
|
||||
.filter(([_, input]) => widgetStore.inputIsWidget(input))
|
||||
.map(([name, input]) => ({
|
||||
name,
|
||||
type: input.widgetType || input.type,
|
||||
value:
|
||||
input.default !== undefined
|
||||
? input.default
|
||||
: input.type === 'COMBO' &&
|
||||
Array.isArray(input.options) &&
|
||||
input.options.length > 0
|
||||
? input.options[0]
|
||||
: undefined,
|
||||
options: {
|
||||
...input,
|
||||
hidden: input.hidden,
|
||||
advanced: input.advanced,
|
||||
values: input.type === 'COMBO' ? input.options : undefined // For combo widgets
|
||||
}
|
||||
}))
|
||||
|
||||
const inputs = Object.entries(nodeDef.inputs || {})
|
||||
.filter(([_, input]) => !widgetStore.inputIsWidget(input))
|
||||
.map(([name, input]) => ({
|
||||
name,
|
||||
type: input.type,
|
||||
shape: input.isOptional ? 'HollowCircle' : undefined,
|
||||
boundingRect: [0, 0, 0, 0]
|
||||
}))
|
||||
|
||||
const outputs = (nodeDef.outputs || []).map((output) => {
|
||||
if (typeof output === 'string') {
|
||||
return {
|
||||
name: output,
|
||||
type: output,
|
||||
boundingRect: [0, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
return {
|
||||
...output,
|
||||
boundingRect: [0, 0, 0, 0]
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
id: `preview-${nodeDef.name}`,
|
||||
title: nodeDef.display_name || nodeDef.name,
|
||||
type: nodeDef.name,
|
||||
mode: 0, // Normal mode
|
||||
selected: false,
|
||||
executing: false,
|
||||
widgets,
|
||||
inputs,
|
||||
outputs,
|
||||
flags: {
|
||||
collapsed: false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const readonly = true
|
||||
const hasCustomContent = false
|
||||
const nodeImageUrls = ['']
|
||||
</script>
|
||||
@@ -45,7 +45,8 @@ vi.mock('@vueuse/core', async () => {
|
||||
whenever: vi.fn(),
|
||||
useStorage: vi.fn((_key, defaultValue) => {
|
||||
return ref(defaultValue)
|
||||
})
|
||||
}),
|
||||
createSharedComposable: vi.fn((fn) => fn)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ vi.mock('@/platform/settings/settingStore')
|
||||
vi.mock('@/stores/systemStatsStore')
|
||||
vi.mock('@vueuse/core', () => ({
|
||||
until: vi.fn(() => Promise.resolve()),
|
||||
useStorage: vi.fn(() => ({ value: {} }))
|
||||
useStorage: vi.fn(() => ({ value: {} })),
|
||||
createSharedComposable: vi.fn((fn) => fn)
|
||||
}))
|
||||
|
||||
describe('useReleaseStore', () => {
|
||||
|
||||
Reference in New Issue
Block a user