mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-04 15:10:06 +00:00
Add context menu to tabs (#1759)
* Add context menu to tabs * Flatten menu Translate * Add translations --------- Co-authored-by: huchenlei <huchenlei@proton.me>
This commit is contained in:
@@ -9,32 +9,38 @@
|
||||
dataKey="value"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<span
|
||||
class="workflow-label text-sm max-w-[150px] truncate inline-block"
|
||||
v-tooltip.bottom="option.workflow.key"
|
||||
<div
|
||||
class="flex p-2 gap-2"
|
||||
@contextmenu="showContextMenu($event, option)"
|
||||
>
|
||||
{{ option.workflow.filename }}
|
||||
</span>
|
||||
<div class="relative">
|
||||
<span
|
||||
class="status-indicator"
|
||||
v-if="
|
||||
!workspaceStore.shiftDown &&
|
||||
(option.workflow.isModified || !option.workflow.isPersisted)
|
||||
"
|
||||
>•</span
|
||||
class="workflow-label text-sm max-w-[150px] truncate inline-block"
|
||||
v-tooltip.bottom="option.workflow.key"
|
||||
>
|
||||
<Button
|
||||
class="close-button p-0 w-auto"
|
||||
icon="pi pi-times"
|
||||
text
|
||||
severity="secondary"
|
||||
size="small"
|
||||
@click.stop="onCloseWorkflow(option)"
|
||||
/>
|
||||
{{ option.workflow.filename }}
|
||||
</span>
|
||||
<div class="relative">
|
||||
<span
|
||||
class="status-indicator"
|
||||
v-if="
|
||||
!workspaceStore.shiftDown &&
|
||||
(option.workflow.isModified || !option.workflow.isPersisted)
|
||||
"
|
||||
>•</span
|
||||
>
|
||||
<Button
|
||||
class="close-button p-0 w-auto"
|
||||
icon="pi pi-times"
|
||||
text
|
||||
severity="secondary"
|
||||
size="small"
|
||||
@click.stop="onCloseWorkflow(option)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SelectButton>
|
||||
<ContextMenu ref="menu" :model="contextMenuItems" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -42,20 +48,30 @@ import { ComfyWorkflow } from '@/stores/workflowStore'
|
||||
import { useWorkflowStore } from '@/stores/workflowStore'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import Button from 'primevue/button'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { workflowService } from '@/services/workflowService'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface WorkflowOption {
|
||||
value: string
|
||||
workflow: ComfyWorkflow
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
class?: string
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const rightClickedTab = ref<WorkflowOption>(null)
|
||||
const menu = ref()
|
||||
|
||||
interface WorkflowOption {
|
||||
value: string
|
||||
workflow: ComfyWorkflow
|
||||
const showContextMenu = (event, option) => {
|
||||
rightClickedTab.value = option
|
||||
menu.value.show(event)
|
||||
}
|
||||
|
||||
const workflowToOption = (workflow: ComfyWorkflow): WorkflowOption => ({
|
||||
@@ -84,11 +100,63 @@ const onWorkflowChange = (option: WorkflowOption) => {
|
||||
workflowService.openWorkflow(option.workflow)
|
||||
}
|
||||
|
||||
const onCloseWorkflow = (option: WorkflowOption) => {
|
||||
workflowService.closeWorkflow(option.workflow, {
|
||||
warnIfUnsaved: !workspaceStore.shiftDown
|
||||
})
|
||||
const closeWorkflows = async (options: WorkflowOption[]) => {
|
||||
for (const opt of options) {
|
||||
if (
|
||||
!(await workflowService.closeWorkflow(opt.workflow, {
|
||||
warnIfUnsaved: !workspaceStore.shiftDown
|
||||
}))
|
||||
) {
|
||||
// User clicked cancel
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onCloseWorkflow = (option: WorkflowOption) => {
|
||||
closeWorkflows([option])
|
||||
}
|
||||
|
||||
const contextMenuItems = computed(() => {
|
||||
const tab = rightClickedTab.value as WorkflowOption
|
||||
if (!tab) return []
|
||||
const index = options.value.findIndex((v) => v.workflow === tab.workflow)
|
||||
|
||||
return [
|
||||
{
|
||||
label: t('tabMenu.duplicateTab'),
|
||||
command: () => {
|
||||
workflowService.duplicateWorkflow(tab.workflow)
|
||||
}
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
{
|
||||
label: t('tabMenu.closeTab'),
|
||||
command: () => onCloseWorkflow(tab)
|
||||
},
|
||||
{
|
||||
label: t('tabMenu.closeTabsToLeft'),
|
||||
command: () => closeWorkflows(options.value.slice(0, index)),
|
||||
disabled: index <= 0
|
||||
},
|
||||
{
|
||||
label: t('tabMenu.closeTabsToRight'),
|
||||
command: () => closeWorkflows(options.value.slice(index + 1)),
|
||||
disabled: index === options.value.length - 1
|
||||
},
|
||||
{
|
||||
label: t('tabMenu.closeOtherTabs'),
|
||||
command: () =>
|
||||
closeWorkflows([
|
||||
...options.value.slice(index + 1),
|
||||
...options.value.slice(0, index)
|
||||
]),
|
||||
disabled: options.value.length <= 1
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -97,7 +165,7 @@ const onCloseWorkflow = (option: WorkflowOption) => {
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton) {
|
||||
@apply px-2 bg-transparent rounded-none flex-shrink-0 relative;
|
||||
@apply p-0 bg-transparent rounded-none flex-shrink-0 relative;
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton.p-togglebutton-checked) {
|
||||
|
||||
@@ -194,6 +194,13 @@
|
||||
"clear": "Clear workflow",
|
||||
"toggleBottomPanel": "Toggle Bottom Panel"
|
||||
},
|
||||
"tabMenu": {
|
||||
"duplicateTab": "Duplicate Tab",
|
||||
"closeTab": "Close Tab",
|
||||
"closeTabsToLeft": "Close Tabs to Left",
|
||||
"closeTabsToRight": "Close Tabs to Right",
|
||||
"closeOtherTabs": "Close Other Tabs"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"title": "Get Started with a Template",
|
||||
"template": {
|
||||
@@ -220,4 +227,4 @@
|
||||
"cancel": "Cancel Download",
|
||||
"cancelled": "Cancelled"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,13 @@
|
||||
},
|
||||
"star": "スター",
|
||||
"systemInfo": "システム情報",
|
||||
"tabMenu": {
|
||||
"closeOtherTabs": "他のタブを閉じる",
|
||||
"closeTab": "タブを閉じる",
|
||||
"closeTabsToLeft": "左のタブを閉じる",
|
||||
"closeTabsToRight": "右のタブを閉じる",
|
||||
"duplicateTab": "タブを複製"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
"default": "画像生成",
|
||||
|
||||
@@ -198,6 +198,13 @@
|
||||
},
|
||||
"star": "Звёздочка",
|
||||
"systemInfo": "Информация о системе",
|
||||
"tabMenu": {
|
||||
"closeOtherTabs": "Закрыть другие вкладки",
|
||||
"closeTab": "Закрыть вкладку",
|
||||
"closeTabsToLeft": "Закрыть вкладки слева",
|
||||
"closeTabsToRight": "Закрыть вкладки справа",
|
||||
"duplicateTab": "Дублировать вкладку"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
"default": "Image Generation",
|
||||
|
||||
@@ -198,6 +198,13 @@
|
||||
},
|
||||
"star": "星星",
|
||||
"systemInfo": "系统信息",
|
||||
"tabMenu": {
|
||||
"closeOtherTabs": "关闭其他标签",
|
||||
"closeTab": "关闭标签",
|
||||
"closeTabsToLeft": "关闭左侧标签",
|
||||
"closeTabsToRight": "关闭右侧标签",
|
||||
"duplicateTab": "复制标签"
|
||||
},
|
||||
"templateWorkflows": {
|
||||
"template": {
|
||||
"default": "Image Generation",
|
||||
|
||||
@@ -300,5 +300,13 @@ export const workflowService = {
|
||||
if (previousWorkflow) {
|
||||
await this.openWorkflow(previousWorkflow)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an existing workflow and duplicates it with a new name
|
||||
*/
|
||||
async duplicateWorkflow(workflow: ComfyWorkflow) {
|
||||
const state = JSON.parse(JSON.stringify(workflow.activeState))
|
||||
await app.loadGraphData(state, true, true, workflow.filename)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user