App mode - Unify menus - 2 (#9023)

## Summary

Updates subgraph breadcrumbs menu, workflow tabs context menu & linear
mode menu to use a single implementation.
Adds new menu items for enter/exit app mode  
Hides menu when in builder mode

## Changes

- **What**: Changes the components to use either a reka-ui context menu
or dropdown, with a standard inner list
- **Breaking**: Remove existing linear toggle from sidebar as it is now
in the menu


## Screenshots (if applicable)
It looks basically identical other than the icon changes based on mode:

In Graph Mode:
<img width="261" height="497" alt="image"
src="https://github.com/user-attachments/assets/eb9968a2-b528-4e21-9e14-ab4a67e717ae"
/>

In App Mode:
<img width="254" height="499" alt="image"
src="https://github.com/user-attachments/assets/54a89fab-e7b2-4cb0-bcb7-43d6d076ac83"
/>

Right click tab:
<img width="321" height="564" alt="image"
src="https://github.com/user-attachments/assets/c12c7d64-2dba-45bb-be76-2615f3e38cc6"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9023-App-mode-Unify-menus-2-30d6d73d36508162bfc0e308d5f705de)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
pythongosssss
2026-02-23 17:49:52 +00:00
committed by GitHub
parent ddcfdb924d
commit d601aba721
36 changed files with 892 additions and 268 deletions

View File

@@ -1,6 +1,6 @@
<template>
<div
class="subgraph-breadcrumb flex w-auto drop-shadow-[var(--interface-panel-drop-shadow)]"
class="subgraph-breadcrumb flex w-auto drop-shadow-[var(--interface-panel-drop-shadow)] items-center"
:class="{
'subgraph-breadcrumb-collapse': collapseTabs,
'subgraph-breadcrumb-overflow': overflowingTabs
@@ -13,17 +13,10 @@
'--p-breadcrumb-icon-width': `${ICON_WIDTH}px`
}"
>
<Button
class="context-menu-button pointer-events-auto h-8 w-8 shrink-0 border border-transparent bg-transparent p-0 transition-all hover:rounded-lg hover:border-interface-stroke hover:bg-comfy-menu-bg"
icon="pi pi-bars"
text
severity="secondary"
size="small"
@click="handleMenuClick"
/>
<WorkflowActionsDropdown source="breadcrumb_subgraph_menu_selected" />
<Button
v-if="isInSubgraph"
class="back-button pointer-events-auto h-8 w-8 shrink-0 border border-transparent bg-transparent p-0 transition-all hover:rounded-lg hover:border-interface-stroke hover:bg-comfy-menu-bg"
class="back-button pointer-events-auto h-8 w-8 shrink-0 border border-transparent bg-transparent p-0 ml-1.5 transition-all hover:rounded-lg hover:border-interface-stroke hover:bg-comfy-menu-bg"
text
severity="secondary"
size="small"
@@ -41,7 +34,6 @@
>
<template #item="{ item }">
<SubgraphBreadcrumbItem
:ref="(el) => setItemRef(item, el)"
:item="item"
:is-active="item.key === activeItemKey"
/>
@@ -60,6 +52,7 @@ import type { MenuItem } from 'primevue/menuitem'
import { computed, onUpdated, ref, watch } from 'vue'
import SubgraphBreadcrumbItem from '@/components/breadcrumb/SubgraphBreadcrumbItem.vue'
import WorkflowActionsDropdown from '@/components/common/WorkflowActionsDropdown.vue'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useTelemetry } from '@/platform/telemetry'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
@@ -77,12 +70,6 @@ const ICON_WIDTH = 20
const workflowStore = useWorkflowStore()
const navigationStore = useSubgraphNavigationStore()
const breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>()
const rootItemRef = ref<InstanceType<typeof SubgraphBreadcrumbItem>>()
const setItemRef = (item: MenuItem, el: unknown) => {
if (item.key === 'root') {
rootItemRef.value = el as InstanceType<typeof SubgraphBreadcrumbItem>
}
}
const workflowName = computed(() => workflowStore.activeWorkflow?.filename)
const isBlueprint = computed(() =>
useSubgraphStore().isSubgraphBlueprint(workflowStore.activeWorkflow)
@@ -136,13 +123,6 @@ const items = computed(() => {
const activeItemKey = computed(() => items.value.at(-1)?.key)
const handleMenuClick = (event: MouseEvent) => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'breadcrumb_subgraph_menu_selected'
})
rootItemRef.value?.toggleMenu(event)
}
const handleBackClick = () => {
void useCommandStore().execute('Comfy.Graph.ExitSubgraph')
}

View File

@@ -185,14 +185,6 @@ const inputBlur = async (doRename: boolean) => {
isEditing.value = false
}
const toggleMenu = (event: MouseEvent) => {
menu.value?.toggle(event)
}
defineExpose({
toggleMenu
})
</script>
<style scoped>

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import {
DropdownMenuContent,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuTrigger
} from 'reka-ui'
import { useI18n } from 'vue-i18n'
import WorkflowActionsList from '@/components/common/WorkflowActionsList.vue'
import Button from '@/components/ui/button/Button.vue'
import { useWorkflowActionsMenu } from '@/composables/useWorkflowActionsMenu'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'
const { source, align = 'start' } = defineProps<{
source: string
align?: 'start' | 'center' | 'end'
}>()
const { t } = useI18n()
const canvasStore = useCanvasStore()
const { menuItems } = useWorkflowActionsMenu(
() => useCommandStore().execute('Comfy.RenameWorkflow'),
{ isRoot: true }
)
function handleOpen(open: boolean) {
if (open) {
useTelemetry()?.trackUiButtonClicked({
button_id: source
})
}
}
</script>
<template>
<DropdownMenuRoot @update:open="handleOpen">
<DropdownMenuTrigger as-child>
<slot name="button">
<Button
v-tooltip="{
value: t('breadcrumbsMenu.workflowActions'),
showDelay: 300,
hideDelay: 300
}"
variant="secondary"
size="unset"
:aria-label="t('breadcrumbsMenu.workflowActions')"
class="h-10 rounded-lg pl-3 pr-2 pointer-events-auto gap-1 data-[state=open]:bg-secondary-background-hover data-[state=open]:shadow-interface"
>
<i
class="size-4"
:class="
canvasStore.linearMode
? 'icon-[lucide--panels-top-left]'
: 'icon-[comfy--workflow]'
"
/>
<i class="icon-[lucide--chevron-down] size-4 text-muted-foreground" />
</Button>
</slot>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
:align
:side-offset="5"
:collision-padding="10"
class="z-1000 rounded-lg px-2 py-3 min-w-56 bg-base-background shadow-interface border border-border-subtle"
>
<WorkflowActionsList :items="menuItems" />
</DropdownMenuContent>
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>

View File

@@ -0,0 +1,81 @@
import { shallowMount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import WorkflowActionsList from '@/components/common/WorkflowActionsList.vue'
import type {
WorkflowMenuAction,
WorkflowMenuItem
} from '@/types/workflowMenuItem'
function createWrapper(items: WorkflowMenuItem[]) {
return shallowMount(WorkflowActionsList, {
props: { items },
global: { renderStubDefaultSlot: true }
})
}
describe('WorkflowActionsList', () => {
it('renders action items with label and icon', () => {
const items: WorkflowMenuItem[] = [
{ label: 'Save', icon: 'pi pi-save', command: vi.fn() }
]
const wrapper = createWrapper(items)
expect(wrapper.text()).toContain('Save')
expect(wrapper.find('.pi-save').exists()).toBe(true)
})
it('renders separator items', () => {
const items: WorkflowMenuItem[] = [
{ label: 'Before', icon: 'pi pi-a', command: vi.fn() },
{ separator: true },
{ label: 'After', icon: 'pi pi-b', command: vi.fn() }
]
const wrapper = createWrapper(items)
const html = wrapper.html()
expect(html).toContain('dropdown-menu-separator-stub')
expect(wrapper.text()).toContain('Before')
expect(wrapper.text()).toContain('After')
})
it('dispatches command on select', async () => {
const command = vi.fn()
const items: WorkflowMenuItem[] = [
{ label: 'Action', icon: 'pi pi-play', command }
]
const wrapper = createWrapper(items)
const item = wrapper.findComponent({ name: 'DropdownMenuItem' })
await item.vm.$emit('select')
expect(command).toHaveBeenCalledOnce()
})
it('renders badge when present', () => {
const items: WorkflowMenuItem[] = [
{
label: 'New Feature',
icon: 'pi pi-star',
command: vi.fn(),
badge: 'NEW'
}
]
const wrapper = createWrapper(items)
expect(wrapper.text()).toContain('NEW')
})
it('does not render badge when absent', () => {
const items: WorkflowMenuAction[] = [
{ label: 'Plain', icon: 'pi pi-check', command: vi.fn() }
]
const wrapper = createWrapper(items)
expect(wrapper.text()).not.toContain('NEW')
})
})

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import { DropdownMenuItem, DropdownMenuSeparator } from 'reka-ui'
import type { Component } from 'vue'
import OverlayIcon from '@/components/common/OverlayIcon.vue'
import type { WorkflowMenuItem } from '@/types/workflowMenuItem'
import { cn } from '@/utils/tailwindUtil'
const {
items,
itemComponent = DropdownMenuItem,
separatorComponent = DropdownMenuSeparator
} = defineProps<{
items: WorkflowMenuItem[]
itemComponent?: Component
separatorComponent?: Component
}>()
</script>
<template>
<template v-for="(item, index) in items" :key="index">
<component
:is="separatorComponent"
v-if="item.separator"
class="border-b w-full border-border-subtle my-1"
/>
<component
:is="itemComponent"
v-else
:disabled="item.disabled"
:class="
cn(
'flex min-h-6 p-2 items-center gap-2 self-stretch rounded-sm outline-none',
!item.disabled && item.command && 'cursor-pointer',
'data-[highlighted]:bg-secondary-background-hover',
!item.disabled && 'hover:bg-secondary-background-hover',
'data-[disabled]:opacity-50 data-[disabled]:cursor-default'
)
"
@select="() => item.command?.()"
>
<OverlayIcon v-if="item.overlayIcon" v-bind="item.overlayIcon" />
<i v-else-if="item.icon" :class="item.icon" />
<span class="flex-1">{{ item.label }}</span>
<span
v-if="item.badge"
class="rounded-full uppercase ml-3 flex items-center gap-1 bg-[var(--primary-background)] px-1.5 py-0.5 text-xxs text-base-foreground"
>
{{ item.badge }}
</span>
</component>
</template>
</template>

View File

@@ -45,9 +45,6 @@
<SidebarBottomPanelToggleButton v-if="!isCloud" :is-small="isSmall" />
<SidebarShortcutsToggleButton :is-small="isSmall" />
<SidebarSettingsButton :is-small="isSmall" />
<ModeToggle
v-if="menuItemStore.hasSeenLinear || flags.linearToggleEnabled"
/>
</div>
</div>
<HelpCenterPopups :is-small="isSmall" />
@@ -62,18 +59,15 @@ import { useI18n } from 'vue-i18n'
import HelpCenterPopups from '@/components/helpcenter/HelpCenterPopups.vue'
import ComfyMenuButton from '@/components/sidebar/ComfyMenuButton.vue'
import ModeToggle from '@/components/sidebar/ModeToggle.vue'
import SidebarBottomPanelToggleButton from '@/components/sidebar/SidebarBottomPanelToggleButton.vue'
import SidebarSettingsButton from '@/components/sidebar/SidebarSettingsButton.vue'
import SidebarShortcutsToggleButton from '@/components/sidebar/SidebarShortcutsToggleButton.vue'
import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCommandStore } from '@/stores/commandStore'
import { useKeybindingStore } from '@/platform/keybindings/keybindingStore'
import { useMenuItemStore } from '@/stores/menuItemStore'
import { useUserStore } from '@/stores/userStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import type { SidebarTabExtension } from '@/types/extensionTypes'
@@ -90,11 +84,9 @@ const settingStore = useSettingStore()
const userStore = useUserStore()
const commandStore = useCommandStore()
const canvasStore = useCanvasStore()
const menuItemStore = useMenuItemStore()
const sideToolbarRef = ref<HTMLElement>()
const topToolbarRef = ref<HTMLElement>()
const bottomToolbarRef = ref<HTMLElement>()
const { flags } = useFeatureFlags()
const isSmall = computed(
() => settingStore.get('Comfy.Sidebar.Size') === 'small'

View File

@@ -1,36 +1,53 @@
<template>
<div
ref="workflowTabRef"
class="workflow-tab group flex gap-2 p-2"
v-bind="$attrs"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@click="handleClick"
>
<i
v-if="workflowOption.workflow.activeState?.extra?.linearMode"
class="icon-[lucide--panels-top-left] bg-primary-background"
/>
<span class="workflow-label inline-block max-w-[150px] truncate text-sm">
{{ workflowOption.workflow.filename }}
</span>
<div class="relative">
<span
v-if="shouldShowStatusIndicator"
class="absolute top-1/2 left-1/2 z-10 w-4 -translate-1/2 bg-(--comfy-menu-bg) text-2xl font-bold group-hover:hidden"
></span
<ContextMenuRoot>
<ContextMenuTrigger as-child>
<div
ref="workflowTabRef"
class="workflow-tab group flex gap-2 p-2"
v-bind="$attrs"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@click="handleClick"
>
<Button
class="close-button invisible w-auto p-0"
variant="muted-textonly"
size="icon-sm"
:aria-label="t('g.close')"
@click.stop="onCloseWorkflow(workflowOption)"
<i
v-if="workflowOption.workflow.activeState?.extra?.linearMode"
class="icon-[lucide--panels-top-left] bg-primary-background"
/>
<span
class="workflow-label inline-block max-w-[150px] truncate text-sm"
>
{{ workflowOption.workflow.filename }}
</span>
<div class="relative">
<span
v-if="shouldShowStatusIndicator"
class="absolute top-1/2 left-1/2 z-10 w-4 -translate-1/2 bg-(--comfy-menu-bg) text-2xl font-bold group-hover:hidden"
></span
>
<Button
class="close-button invisible w-auto p-0"
variant="muted-textonly"
size="icon-sm"
:aria-label="t('g.close')"
@click.stop="onCloseWorkflow(workflowOption)"
>
<i class="pi pi-times" />
</Button>
</div>
</div>
</ContextMenuTrigger>
<ContextMenuPortal>
<ContextMenuContent
class="z-1000 rounded-lg px-2 py-3 min-w-56 bg-base-background shadow-interface border border-border-subtle"
>
<i class="pi pi-times" />
</Button>
</div>
</div>
<WorkflowActionsList
:items="contextMenuItems"
:item-component="ContextMenuItem"
:separator-component="ContextMenuSeparator"
/>
</ContextMenuContent>
</ContextMenuPortal>
</ContextMenuRoot>
<WorkflowTabPopover
ref="popoverRef"
@@ -41,20 +58,32 @@
</template>
<script setup lang="ts">
import {
ContextMenuContent,
ContextMenuItem,
ContextMenuPortal,
ContextMenuRoot,
ContextMenuSeparator,
ContextMenuTrigger
} from 'reka-ui'
import { computed, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import WorkflowActionsList from '@/components/common/WorkflowActionsList.vue'
import Button from '@/components/ui/button/Button.vue'
import {
usePragmaticDraggable,
usePragmaticDroppable
} from '@/composables/usePragmaticDragAndDrop'
import { useWorkflowActionsMenu } from '@/composables/useWorkflowActionsMenu'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail'
import { useCommandStore } from '@/stores/commandStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import type { WorkflowMenuItem } from '@/types/workflowMenuItem'
import WorkflowTabPopover from './WorkflowTabPopover.vue'
@@ -65,6 +94,8 @@ interface WorkflowOption {
const props = defineProps<{
workflowOption: WorkflowOption
isFirst: boolean
isLast: boolean
}>()
const { t } = useI18n()
@@ -148,6 +179,67 @@ const closeWorkflows = async (options: WorkflowOption[]) => {
const onCloseWorkflow = async (option: WorkflowOption) => {
await closeWorkflows([option])
}
const emit = defineEmits<{
closeToLeft: []
closeToRight: []
closeOthers: []
}>()
const commandStore = useCommandStore()
const workflow = computed(() => props.workflowOption.workflow)
const { menuItems: baseMenuItems } = useWorkflowActionsMenu(
() => commandStore.execute('Comfy.RenameWorkflow'),
{ includeDelete: false, workflow }
)
const contextMenuItems = computed<WorkflowMenuItem[]>(() => [
...baseMenuItems.value,
{ separator: true },
{
label: t('tabMenu.closeTab'),
icon: 'pi pi-times',
command: () => onCloseWorkflow(props.workflowOption)
},
{
label: t('tabMenu.closeTabsToLeft'),
overlayIcon: {
mainIcon: 'pi pi-times',
subIcon: 'pi pi-arrow-left',
positionX: 'right',
positionY: 'bottom',
subIconScale: 0.5
},
command: () => emit('closeToLeft'),
disabled: props.isFirst
},
{
label: t('tabMenu.closeTabsToRight'),
overlayIcon: {
mainIcon: 'pi pi-times',
subIcon: 'pi pi-arrow-right',
positionX: 'right',
positionY: 'bottom',
subIconScale: 0.5
},
command: () => emit('closeToRight'),
disabled: props.isLast
},
{
label: t('tabMenu.closeOtherTabs'),
overlayIcon: {
mainIcon: 'pi pi-times',
subIcon: 'pi pi-arrows-h',
positionX: 'right',
positionY: 'bottom',
subIconScale: 0.5
},
command: () => emit('closeOthers'),
disabled: props.isFirst && props.isLast
}
])
const tabGetter = () => workflowTabRef.value as HTMLElement
usePragmaticDraggable(tabGetter, {

View File

@@ -32,11 +32,20 @@
data-key="value"
@update:model-value="onWorkflowChange"
>
<template #option="{ option }">
<template #option="{ option, index }">
<WorkflowTab
:workflow-option="option"
@contextmenu="showContextMenu($event, option)"
:is-first="index === 0"
:is-last="index === options.length - 1"
@click.middle="onCloseWorkflow(option)"
@close-to-left="closeWorkflows(options.slice(0, index))"
@close-to-right="closeWorkflows(options.slice(index + 1))"
@close-others="
closeWorkflows([
...options.slice(index + 1),
...options.slice(0, index)
])
"
/>
</template>
</SelectButton>
@@ -58,7 +67,10 @@
:active-workflow="workflowStore.activeWorkflow"
/>
<Button
v-tooltip="{ value: $t('sideToolbar.newBlankWorkflow'), showDelay: 300 }"
v-tooltip="{
value: $t('sideToolbar.newBlankWorkflow'),
showDelay: 300
}"
class="new-blank-workflow-button no-drag shrink-0 rounded-none h-full w-auto aspect-square"
variant="muted-textonly"
size="icon"
@@ -80,27 +92,17 @@
/>
<LoginButton v-else-if="isDesktop" class="p-1" />
</div>
<ContextMenu ref="menu" :model="contextMenuItems">
<template #itemicon="{ item }">
<OverlayIcon v-if="item.overlayIcon" v-bind="item.overlayIcon" />
<i v-else-if="item.icon" :class="item.icon" />
</template>
</ContextMenu>
<div v-if="isDesktop" class="window-actions-spacer app-drag shrink-0" />
</div>
</template>
<script setup lang="ts">
import { useScroll } from '@vueuse/core'
import ContextMenu from 'primevue/contextmenu'
import ScrollPanel from 'primevue/scrollpanel'
import SelectButton from 'primevue/selectbutton'
import { computed, nextTick, onUpdated, ref, watch } from 'vue'
import type { WatchStopHandle } from 'vue'
import { useI18n } from 'vue-i18n'
import OverlayIcon from '@/components/common/OverlayIcon.vue'
import type { OverlayIconProps } from '@/components/common/OverlayIcon.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
import TopMenuHelpButton from '@/components/topbar/TopMenuHelpButton.vue'
@@ -108,7 +110,6 @@ import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useWorkflowActionsMenu } from '@/composables/useWorkflowActionsMenu'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
@@ -129,7 +130,6 @@ const props = defineProps<{
class?: string
}>()
const { t } = useI18n()
const settingStore = useSettingStore()
const workspaceStore = useWorkspaceStore()
const workflowStore = useWorkflowStore()
@@ -141,8 +141,6 @@ const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'
)
const rightClickedTab = ref<WorkflowOption | undefined>()
const menu = ref()
const containerRef = ref<HTMLElement | null>(null)
const showOverflowArrows = ref(false)
const leftArrowEnabled = ref(false)
@@ -192,78 +190,6 @@ const onCloseWorkflow = async (option: WorkflowOption) => {
await closeWorkflows([option])
}
const showContextMenu = (event: MouseEvent, option: WorkflowOption) => {
rightClickedTab.value = option
menu.value.show(event)
}
const rightClickedWorkflow = computed(
() => rightClickedTab.value?.workflow ?? null
)
const { menuItems: baseMenuItems } = useWorkflowActionsMenu(
() => commandStore.execute('Comfy.RenameWorkflow'),
{
includeDelete: false,
workflow: rightClickedWorkflow
}
)
const contextMenuItems = computed(() => {
const tab = rightClickedTab.value
if (!tab) return []
const index = options.value.findIndex((v) => v.workflow === tab.workflow)
return [
...baseMenuItems.value,
{
label: t('tabMenu.closeTab'),
icon: 'pi pi-times',
command: () => onCloseWorkflow(tab)
},
{
label: t('tabMenu.closeTabsToLeft'),
overlayIcon: {
mainIcon: 'pi pi-times',
subIcon: 'pi pi-arrow-left',
positionX: 'right',
positionY: 'bottom',
subIconScale: 0.5
} as OverlayIconProps,
command: () => closeWorkflows(options.value.slice(0, index)),
disabled: index <= 0
},
{
label: t('tabMenu.closeTabsToRight'),
overlayIcon: {
mainIcon: 'pi pi-times',
subIcon: 'pi pi-arrow-right',
positionX: 'right',
positionY: 'bottom',
subIconScale: 0.5
} as OverlayIconProps,
command: () => closeWorkflows(options.value.slice(index + 1)),
disabled: index === options.value.length - 1
},
{
label: t('tabMenu.closeOtherTabs'),
overlayIcon: {
mainIcon: 'pi pi-times',
subIcon: 'pi pi-arrows-h',
positionX: 'right',
positionY: 'bottom',
subIconScale: 0.5
} as OverlayIconProps,
command: () =>
closeWorkflows([
...options.value.slice(index + 1),
...options.value.slice(0, index)
]),
disabled: options.value.length <= 1
}
]
})
// Horizontal scroll on wheel
const handleWheel = (event: WheelEvent) => {
const scrollElement = event.currentTarget as HTMLElement