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:
pythongosssss
2024-12-02 18:19:39 +00:00
committed by GitHub
parent 1afc584393
commit 17b7ef18d6
6 changed files with 134 additions and 30 deletions

View File

@@ -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) {

View File

@@ -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"
}
}
}

View File

@@ -198,6 +198,13 @@
},
"star": "スター",
"systemInfo": "システム情報",
"tabMenu": {
"closeOtherTabs": "他のタブを閉じる",
"closeTab": "タブを閉じる",
"closeTabsToLeft": "左のタブを閉じる",
"closeTabsToRight": "右のタブを閉じる",
"duplicateTab": "タブを複製"
},
"templateWorkflows": {
"template": {
"default": "画像生成",

View File

@@ -198,6 +198,13 @@
},
"star": "Звёздочка",
"systemInfo": "Информация о системе",
"tabMenu": {
"closeOtherTabs": "Закрыть другие вкладки",
"closeTab": "Закрыть вкладку",
"closeTabsToLeft": "Закрыть вкладки слева",
"closeTabsToRight": "Закрыть вкладки справа",
"duplicateTab": "Дублировать вкладку"
},
"templateWorkflows": {
"template": {
"default": "Image Generation",

View File

@@ -198,6 +198,13 @@
},
"star": "星星",
"systemInfo": "系统信息",
"tabMenu": {
"closeOtherTabs": "关闭其他标签",
"closeTab": "关闭标签",
"closeTabsToLeft": "关闭左侧标签",
"closeTabsToRight": "关闭右侧标签",
"duplicateTab": "复制标签"
},
"templateWorkflows": {
"template": {
"default": "Image Generation",

View File

@@ -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)
}
}