mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 06:20:10 +00:00
Add Subgraphs (#3905)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="workflowStore.isSubgraphActive"
|
||||
class="fixed top-[var(--comfy-topbar-height)] left-[var(--sidebar-width)] p-2 subgraph-breadcrumb"
|
||||
>
|
||||
<div v-if="workflowStore.isSubgraphActive" class="p-2 subgraph-breadcrumb">
|
||||
<Breadcrumb
|
||||
class="bg-transparent"
|
||||
:home="home"
|
||||
@@ -14,28 +11,30 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEventListener, whenever } from '@vueuse/core'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import Breadcrumb from 'primevue/breadcrumb'
|
||||
import type { MenuItem, MenuItemCommandEvent } from 'primevue/menuitem'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
|
||||
const workflowService = useWorkflowService()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const navigationStore = useSubgraphNavigationStore()
|
||||
|
||||
const workflowName = computed(() => workflowStore.activeWorkflow?.filename)
|
||||
|
||||
const items = computed(() => {
|
||||
if (!workflowStore.subgraphNamePath.length) return []
|
||||
if (!navigationStore.navigationStack.length) return []
|
||||
|
||||
return workflowStore.subgraphNamePath.map<MenuItem>((name) => ({
|
||||
label: name,
|
||||
command: async () => {
|
||||
const workflow = workflowStore.getWorkflowByPath(name)
|
||||
if (workflow) await workflowService.openWorkflow(workflow)
|
||||
return navigationStore.navigationStack.map<MenuItem>((subgraph) => ({
|
||||
label: subgraph.name,
|
||||
command: () => {
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
||||
|
||||
canvas.setGraph(subgraph)
|
||||
}
|
||||
}))
|
||||
})
|
||||
@@ -43,7 +42,7 @@ const items = computed(() => {
|
||||
const home = computed(() => ({
|
||||
label: workflowName.value,
|
||||
icon: 'pi pi-home',
|
||||
command: async () => {
|
||||
command: () => {
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
||||
|
||||
@@ -55,22 +54,32 @@ const handleItemClick = (event: MenuItemCommandEvent) => {
|
||||
event.item.command?.(event)
|
||||
}
|
||||
|
||||
whenever(
|
||||
() => useCanvasStore().canvas,
|
||||
(canvas) => {
|
||||
useEventListener(canvas.canvas, 'litegraph:set-graph', () => {
|
||||
useWorkflowStore().updateActiveGraph()
|
||||
})
|
||||
// Escape exits from the current subgraph.
|
||||
useEventListener(document, 'keydown', (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
const canvas = useCanvasStore().getCanvas()
|
||||
if (!canvas.graph) throw new TypeError('Canvas has no graph')
|
||||
|
||||
canvas.setGraph(
|
||||
navigationStore.navigationStack.at(-2) ?? canvas.graph.rootGraph
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.subgraph-breadcrumb {
|
||||
.p-breadcrumb-item-link,
|
||||
.p-breadcrumb-item-icon {
|
||||
@apply select-none;
|
||||
|
||||
color: #d26565;
|
||||
user-select: none;
|
||||
text-shadow:
|
||||
1px 1px 0 #000,
|
||||
-1px -1px 0 #000,
|
||||
1px -1px 0 #000,
|
||||
-1px 1px 0 #000,
|
||||
0 0 0.375rem #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,16 +21,14 @@ import { useDomWidgetStore } from '@/stores/domWidgetStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
|
||||
const domWidgetStore = useDomWidgetStore()
|
||||
const widgetStates = computed(() =>
|
||||
Array.from(domWidgetStore.widgetStates.values())
|
||||
)
|
||||
const widgetStates = computed(() => domWidgetStore.activeWidgetStates)
|
||||
|
||||
const updateWidgets = () => {
|
||||
const lgCanvas = canvasStore.canvas
|
||||
if (!lgCanvas) return
|
||||
|
||||
const lowQuality = lgCanvas.low_quality
|
||||
for (const widgetState of domWidgetStore.widgetStates.values()) {
|
||||
for (const widgetState of widgetStates.value) {
|
||||
const widget = widgetState.widget
|
||||
const node = widget.node as LGraphNode
|
||||
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
<BottomPanel />
|
||||
</template>
|
||||
<template #graph-canvas-panel>
|
||||
<SecondRowWorkflowTabs
|
||||
v-if="workflowTabsPosition === 'Topbar (2nd-row)'"
|
||||
class="pointer-events-auto"
|
||||
/>
|
||||
<div class="absolute top-0 left-0 w-auto max-w-full pointer-events-auto">
|
||||
<SecondRowWorkflowTabs
|
||||
v-if="workflowTabsPosition === 'Topbar (2nd-row)'"
|
||||
/>
|
||||
<SubgraphBreadcrumb />
|
||||
</div>
|
||||
<GraphCanvasMenu v-if="canvasMenuEnabled" class="pointer-events-auto" />
|
||||
</template>
|
||||
</LiteGraphCanvasSplitterOverlay>
|
||||
@@ -39,12 +41,11 @@
|
||||
</SelectionOverlay>
|
||||
<DomWidgets />
|
||||
</template>
|
||||
<SubgraphBreadcrumb />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LGraphNode } from '@comfyorg/litegraph'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { useEventListener, whenever } from '@vueuse/core'
|
||||
import { computed, onMounted, ref, watch, watchEffect } from 'vue'
|
||||
|
||||
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
|
||||
@@ -84,6 +85,7 @@ import { useCanvasStore } from '@/stores/graphStore'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useToastStore } from '@/stores/toastStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
|
||||
@@ -192,10 +194,10 @@ watch(
|
||||
// Update the progress of the executing node
|
||||
watch(
|
||||
() =>
|
||||
[executionStore.executingNodeId, executionStore.executingNodeProgress] as [
|
||||
NodeId | null,
|
||||
number | null
|
||||
],
|
||||
[
|
||||
executionStore.executingNodeId,
|
||||
executionStore.executingNodeProgress
|
||||
] satisfies [NodeId | null, number | null],
|
||||
([executingNodeId, executingNodeProgress]) => {
|
||||
for (const node of comfyApp.graph.nodes) {
|
||||
if (node.id == executingNodeId) {
|
||||
@@ -339,6 +341,16 @@ onMounted(async () => {
|
||||
}
|
||||
)
|
||||
|
||||
whenever(
|
||||
() => useCanvasStore().canvas,
|
||||
(canvas) => {
|
||||
useEventListener(canvas.canvas, 'litegraph:set-graph', () => {
|
||||
useWorkflowStore().updateActiveGraph()
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
emit('ready')
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<PinButton />
|
||||
<EditModelButton />
|
||||
<MaskEditorButton />
|
||||
<ConvertToSubgraphButton />
|
||||
<DeleteButton />
|
||||
<RefreshButton />
|
||||
<ExtensionCommandButton
|
||||
@@ -29,6 +30,7 @@ import { computed } from 'vue'
|
||||
|
||||
import BypassButton from '@/components/graph/selectionToolbox/BypassButton.vue'
|
||||
import ColorPickerButton from '@/components/graph/selectionToolbox/ColorPickerButton.vue'
|
||||
import ConvertToSubgraphButton from '@/components/graph/selectionToolbox/ConvertToSubgraphButton.vue'
|
||||
import DeleteButton from '@/components/graph/selectionToolbox/DeleteButton.vue'
|
||||
import EditModelButton from '@/components/graph/selectionToolbox/EditModelButton.vue'
|
||||
import ExecuteButton from '@/components/graph/selectionToolbox/ExecuteButton.vue'
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<Button
|
||||
v-show="isVisible"
|
||||
v-tooltip.top="{
|
||||
value: t('commands.Comfy_Graph_ConvertToSubgraph.label'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
severity="secondary"
|
||||
text
|
||||
icon="pi pi-box"
|
||||
@click="() => commandStore.execute('Comfy.Graph.ConvertToSubgraph')"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const commandStore = useCommandStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const isVisible = computed(() => {
|
||||
return (
|
||||
canvasStore.groupSelected ||
|
||||
canvasStore.rerouteSelected ||
|
||||
canvasStore.nodeSelected
|
||||
)
|
||||
})
|
||||
</script>
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<Button
|
||||
v-show="isDeletable"
|
||||
v-tooltip.top="{
|
||||
value: t('commands.Comfy_Canvas_DeleteSelectedItems.label'),
|
||||
showDelay: 1000
|
||||
@@ -13,10 +14,17 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useCanvasStore } from '@/stores/graphStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
const commandStore = useCommandStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const isDeletable = computed(() =>
|
||||
canvasStore.selectedItems.some((x) => x.removable !== false)
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -25,8 +25,9 @@ const commandStore = useCommandStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const isSingleImageNode = computed(() => {
|
||||
const nodes = canvasStore.selectedItems.filter(isLGraphNode)
|
||||
return nodes.length === 1 && nodes.some(isImageNode)
|
||||
const { selectedItems } = canvasStore
|
||||
const item = selectedItems[0]
|
||||
return selectedItems.length === 1 && isLGraphNode(item) && isImageNode(item)
|
||||
})
|
||||
|
||||
const openMaskEditor = () => {
|
||||
|
||||
@@ -95,12 +95,14 @@ const addNode = (nodeDef: ComfyNodeDefImpl) => {
|
||||
return
|
||||
}
|
||||
|
||||
disconnectOnReset = false
|
||||
const node = litegraphService.addNodeOnGraph(nodeDef, {
|
||||
pos: getNewNodeLocation()
|
||||
})
|
||||
|
||||
canvasStore.getCanvas().linkConnector.connectToNode(node, triggerEvent)
|
||||
if (disconnectOnReset) {
|
||||
canvasStore.getCanvas().linkConnector.connectToNode(node, triggerEvent)
|
||||
}
|
||||
disconnectOnReset = false
|
||||
|
||||
// Notify changeTracker - new step should be added
|
||||
useWorkflowStore().activeWorkflow?.changeTracker?.checkState()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="absolute top-0 left-0 w-auto max-w-full">
|
||||
<div class="w-auto max-w-full">
|
||||
<WorkflowTabs />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user