mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 10:59:53 +00:00
## Summary Updates for the linter/formatter deps, turning on some more rules. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7309-WIP-Linter-updates-2c56d73d36508101b3ece6bcaf7e5212) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org>
274 lines
7.0 KiB
Vue
274 lines
7.0 KiB
Vue
<template>
|
|
<a
|
|
ref="wrapperRef"
|
|
v-tooltip.bottom="{
|
|
value: tooltipText,
|
|
showDelay: 512
|
|
}"
|
|
draggable="false"
|
|
href="#"
|
|
class="p-breadcrumb-item-link h-12 cursor-pointer px-2"
|
|
:class="{
|
|
'flex items-center gap-1': isActive,
|
|
'p-breadcrumb-item-link-menu-visible': menu?.overlayVisible,
|
|
'p-breadcrumb-item-link-icon-visible': isActive,
|
|
'active-breadcrumb-item': isActive
|
|
}"
|
|
@click="handleClick"
|
|
>
|
|
<i
|
|
v-if="hasMissingNodes && isRoot"
|
|
class="icon-[lucide--triangle-alert] text-warning-background"
|
|
/>
|
|
<span class="p-breadcrumb-item-label px-2">{{ item.label }}</span>
|
|
<Tag v-if="item.isBlueprint" value="Blueprint" severity="primary" />
|
|
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
|
|
</a>
|
|
<Menu
|
|
v-if="isActive"
|
|
ref="menu"
|
|
:model="menuItems"
|
|
:popup="true"
|
|
:pt="{
|
|
root: {
|
|
style: 'background-color: var(--comfy-menu-bg)'
|
|
},
|
|
itemLink: {
|
|
class: 'py-2'
|
|
}
|
|
}"
|
|
/>
|
|
<InputText
|
|
v-if="isEditing"
|
|
ref="itemInputRef"
|
|
v-model="itemLabel"
|
|
class="fixed z-10000 px-2 py-2 text-[.8rem]"
|
|
@blur="inputBlur(false)"
|
|
@click.stop
|
|
@keydown.enter="inputBlur(true)"
|
|
@keydown.esc="inputBlur(false)"
|
|
/>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import InputText from 'primevue/inputtext'
|
|
import type { MenuState } from 'primevue/menu'
|
|
import Menu from 'primevue/menu'
|
|
import type { MenuItem } from 'primevue/menuitem'
|
|
import Tag from 'primevue/tag'
|
|
import { computed, nextTick, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
|
import {
|
|
ComfyWorkflow,
|
|
useWorkflowStore
|
|
} from '@/platform/workflow/management/stores/workflowStore'
|
|
import { app } from '@/scripts/app'
|
|
import { useDialogService } from '@/services/dialogService'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
|
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
|
|
import { appendJsonExt } from '@/utils/formatUtil'
|
|
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
|
|
|
|
interface Props {
|
|
item: MenuItem
|
|
isActive?: boolean
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
isActive: false
|
|
})
|
|
|
|
const nodeDefStore = useNodeDefStore()
|
|
const hasMissingNodes = computed(() =>
|
|
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
|
|
)
|
|
|
|
const { t } = useI18n()
|
|
const menu = ref<InstanceType<typeof Menu> & MenuState>()
|
|
const dialogService = useDialogService()
|
|
const workflowStore = useWorkflowStore()
|
|
const workflowService = useWorkflowService()
|
|
const isEditing = ref(false)
|
|
const itemLabel = ref<string>()
|
|
const itemInputRef = ref<{ $el?: HTMLInputElement }>()
|
|
const wrapperRef = ref<HTMLAnchorElement>()
|
|
|
|
const rename = async (
|
|
newName: string | null | undefined,
|
|
initialName: string
|
|
) => {
|
|
if (newName && newName !== initialName) {
|
|
// Synchronize the node titles with the new name
|
|
props.item.updateTitle?.(newName)
|
|
|
|
if (workflowStore.activeSubgraph) {
|
|
workflowStore.activeSubgraph.name = newName
|
|
} else if (workflowStore.activeWorkflow) {
|
|
try {
|
|
await workflowService.renameWorkflow(
|
|
workflowStore.activeWorkflow,
|
|
ComfyWorkflow.basePath + appendJsonExt(newName)
|
|
)
|
|
} catch (error) {
|
|
console.error(error)
|
|
dialogService.showErrorDialog(error)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Force the navigation stack to recompute the labels
|
|
// TODO: investigate if there is a better way to do this
|
|
const navigationStore = useSubgraphNavigationStore()
|
|
navigationStore.restoreState(navigationStore.exportState())
|
|
}
|
|
}
|
|
|
|
const isRoot = props.item.key === 'root'
|
|
|
|
const tooltipText = computed(() => {
|
|
if (hasMissingNodes.value && isRoot) {
|
|
return t('breadcrumbsMenu.missingNodesWarning')
|
|
}
|
|
return props.item.label
|
|
})
|
|
|
|
const menuItems = computed<MenuItem[]>(() => {
|
|
return [
|
|
{
|
|
label: t('g.rename'),
|
|
icon: 'pi pi-pencil',
|
|
command: startRename
|
|
},
|
|
{
|
|
label: t('breadcrumbsMenu.duplicate'),
|
|
icon: 'pi pi-copy',
|
|
command: async () => {
|
|
await workflowService.duplicateWorkflow(workflowStore.activeWorkflow!)
|
|
},
|
|
visible: isRoot && !props.item.isBlueprint
|
|
},
|
|
{
|
|
separator: true,
|
|
visible: isRoot
|
|
},
|
|
{
|
|
label: t('menuLabels.Save'),
|
|
icon: 'pi pi-save',
|
|
command: async () => {
|
|
await useCommandStore().execute('Comfy.SaveWorkflow')
|
|
},
|
|
visible: isRoot
|
|
},
|
|
{
|
|
label: t('menuLabels.Save As'),
|
|
icon: 'pi pi-save',
|
|
command: async () => {
|
|
await useCommandStore().execute('Comfy.SaveWorkflowAs')
|
|
},
|
|
visible: isRoot
|
|
},
|
|
{
|
|
separator: true
|
|
},
|
|
{
|
|
label: t('breadcrumbsMenu.clearWorkflow'),
|
|
icon: 'pi pi-trash',
|
|
command: async () => {
|
|
await useCommandStore().execute('Comfy.ClearWorkflow')
|
|
}
|
|
},
|
|
{
|
|
separator: true,
|
|
visible: props.item.key === 'root' && props.item.isBlueprint
|
|
},
|
|
{
|
|
label: t('subgraphStore.publish'),
|
|
icon: 'pi pi-copy',
|
|
command: async () => {
|
|
await workflowService.saveWorkflowAs(workflowStore.activeWorkflow!)
|
|
},
|
|
visible: props.item.key === 'root' && props.item.isBlueprint
|
|
},
|
|
{
|
|
separator: true,
|
|
visible: isRoot
|
|
},
|
|
{
|
|
label: props.item.isBlueprint
|
|
? t('breadcrumbsMenu.deleteBlueprint')
|
|
: t('breadcrumbsMenu.deleteWorkflow'),
|
|
icon: 'pi pi-times',
|
|
command: async () => {
|
|
await workflowService.deleteWorkflow(workflowStore.activeWorkflow!)
|
|
},
|
|
visible: isRoot
|
|
}
|
|
]
|
|
})
|
|
|
|
const handleClick = (event: MouseEvent) => {
|
|
if (isEditing.value) {
|
|
return
|
|
}
|
|
|
|
if (event.detail === 1) {
|
|
if (props.isActive) {
|
|
menu.value?.toggle(event)
|
|
} else {
|
|
props.item.command?.({ item: props.item, originalEvent: event })
|
|
}
|
|
} else if (props.isActive && event.detail === 2) {
|
|
menu.value?.hide()
|
|
event.stopPropagation()
|
|
event.preventDefault()
|
|
startRename()
|
|
}
|
|
}
|
|
|
|
const startRename = () => {
|
|
isEditing.value = true
|
|
itemLabel.value = props.item.label as string
|
|
void nextTick(() => {
|
|
if (itemInputRef.value?.$el) {
|
|
itemInputRef.value.$el.focus()
|
|
itemInputRef.value.$el.select()
|
|
if (wrapperRef.value) {
|
|
itemInputRef.value.$el.style.width = `${Math.max(200, wrapperRef.value.offsetWidth)}px`
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const inputBlur = async (doRename: boolean) => {
|
|
if (doRename) {
|
|
await rename(itemLabel.value, props.item.label as string)
|
|
}
|
|
|
|
isEditing.value = false
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
@reference '../../assets/css/style.css';
|
|
|
|
.p-breadcrumb-item-link,
|
|
.p-breadcrumb-item-icon {
|
|
@apply select-none;
|
|
}
|
|
|
|
.p-breadcrumb-item-link {
|
|
@apply overflow-hidden;
|
|
}
|
|
|
|
.p-breadcrumb-item-label {
|
|
@apply whitespace-nowrap text-ellipsis overflow-hidden;
|
|
}
|
|
|
|
.active-breadcrumb-item {
|
|
color: var(--text-primary);
|
|
}
|
|
</style>
|