Files
ComfyUI_frontend/src/composables/useWorkflowActionsMenu.ts
pythongosssss dfb78b2e87 Subgraph/workflow breadcrumbs menu updates (#7852)
## Summary
For users who don't use subgraphs, the workflow name in the top left can
be unnecessarily obstructive so this updated collapses it to a simple
icon until a subgraph is entered.

## Changes

- Add menu button to WorkflowTab for quick workflow actions
- Add menu and back button to SubgraphBreadcrumb
- Extract shared menu items to useBreadcrumbMenu composable
- Add Comfy.RenameWorkflow command for renaming persisted workflows
- Menu always shows root workflow menu, even when in subgraph

## Screenshots (if applicable)

<img width="399" height="396" alt="image"
src="https://github.com/user-attachments/assets/701ab60e-790f-4d1e-a817-dc42b2d98712"
/>
<img width="569" height="381" alt="image"
src="https://github.com/user-attachments/assets/fcea3ab0-8388-4c72-a649-1428c1defd6a"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7852-Subgraph-workflow-breadcrumbs-menu-updates-2df6d73d3650815b8490ca0a9a92d540)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-01-12 16:08:28 -07:00

193 lines
4.8 KiB
TypeScript

import type { MenuItem } from 'primevue/menuitem'
import type { ComputedRef, Ref } from 'vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import {
useWorkflowBookmarkStore,
useWorkflowStore
} from '@/platform/workflow/management/stores/workflowStore'
import { useCommandStore } from '@/stores/commandStore'
import { useSubgraphStore } from '@/stores/subgraphStore'
interface WorkflowActionsMenuOptions {
/** Whether this is the root workflow level. Defaults to true. */
isRoot?: boolean
/** Whether to include the delete workflow action. Defaults to true. */
includeDelete?: boolean
/** Override the workflow to operate on. If not provided, uses activeWorkflow. */
workflow?: Ref<ComfyWorkflow | null> | ComputedRef<ComfyWorkflow | null>
}
export function useWorkflowActionsMenu(
startRename: () => void,
options: WorkflowActionsMenuOptions = {}
) {
const { isRoot = true, includeDelete = true, workflow } = options
const { t } = useI18n()
const workflowStore = useWorkflowStore()
const workflowService = useWorkflowService()
const bookmarkStore = useWorkflowBookmarkStore()
const commandStore = useCommandStore()
const subgraphStore = useSubgraphStore()
const targetWorkflow = computed(
() => workflow?.value ?? workflowStore.activeWorkflow
)
/** Switch to the target workflow tab if it's not already active */
const ensureWorkflowActive = async (wf: ComfyWorkflow | null) => {
if (!wf || wf === workflowStore.activeWorkflow) return
await workflowService.openWorkflow(wf)
}
const menuItems = computed<MenuItem[]>(() => {
const workflow = targetWorkflow.value
const isBlueprint = workflow
? subgraphStore.isSubgraphBlueprint(workflow)
: false
const items: MenuItem[] = []
const addItem = (
label: string,
icon: string,
command: () => void,
visible = true,
disabled = false,
separator = false
) => {
if (!visible) return
if (separator) items.push({ separator: true })
items.push({ label, icon, command, disabled })
}
addItem(
t('g.rename'),
'pi pi-pencil',
async () => {
await ensureWorkflowActive(targetWorkflow.value)
startRename()
},
true,
isRoot && !workflow?.isPersisted
)
addItem(
t('breadcrumbsMenu.duplicate'),
'pi pi-copy',
async () => {
if (workflow) {
await workflowService.duplicateWorkflow(workflow)
}
},
isRoot && !isBlueprint
)
addItem(
t('menuLabels.Save'),
'pi pi-save',
async () => {
await ensureWorkflowActive(workflow)
await commandStore.execute('Comfy.SaveWorkflow')
},
isRoot,
false,
true
)
addItem(
t('menuLabels.Save As'),
'pi pi-save',
async () => {
await ensureWorkflowActive(workflow)
await commandStore.execute('Comfy.SaveWorkflowAs')
},
isRoot
)
addItem(
bookmarkStore.isBookmarked(workflow?.path ?? '')
? t('tabMenu.removeFromBookmarks')
: t('tabMenu.addToBookmarks'),
'pi pi-bookmark' +
(bookmarkStore.isBookmarked(workflow?.path ?? '') ? '-fill' : ''),
async () => {
if (workflow?.path) {
await bookmarkStore.toggleBookmarked(workflow.path)
}
},
isRoot,
workflow?.isTemporary ?? false
)
addItem(
t('menuLabels.Export'),
'pi pi-download',
async () => {
await ensureWorkflowActive(workflow)
await commandStore.execute('Comfy.ExportWorkflow')
},
isRoot
)
addItem(
t('menuLabels.Export (API)'),
'pi pi-download',
async () => {
await ensureWorkflowActive(workflow)
await commandStore.execute('Comfy.ExportWorkflowAPI')
},
isRoot
)
addItem(
t('breadcrumbsMenu.clearWorkflow'),
'pi pi-trash',
async () => {
await ensureWorkflowActive(workflow)
await commandStore.execute('Comfy.ClearWorkflow')
},
true,
false,
true
)
addItem(
t('subgraphStore.publish'),
'pi pi-upload',
async () => {
if (workflow) {
await workflowService.saveWorkflowAs(workflow)
}
},
isRoot && isBlueprint,
false,
true
)
addItem(
isBlueprint
? t('breadcrumbsMenu.deleteBlueprint')
: t('breadcrumbsMenu.deleteWorkflow'),
'pi pi-times',
async () => {
if (workflow) {
await workflowService.deleteWorkflow(workflow)
}
},
isRoot && includeDelete,
false,
true
)
return items
})
return {
menuItems
}
}