Merge remote-tracking branch 'origin/main' into fix/qpo-v2-queue-button-assets

# Conflicts:
#	src/components/TopMenuSection.test.ts
This commit is contained in:
Benjamin Lu
2026-01-22 19:30:00 -08:00
2 changed files with 60 additions and 2 deletions

View File

@@ -1,5 +1,6 @@
import { createTestingPinia } from '@pinia/testing' import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils' import { mount } from '@vue/test-utils'
import type { MenuItem } from 'primevue/menuitem'
import { beforeEach, describe, expect, it, vi } from 'vitest' import { beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, nextTick } from 'vue' import { computed, nextTick } from 'vue'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
@@ -44,7 +45,8 @@ function createWrapper(pinia = createTestingPinia({ createSpy: vi.fn })) {
queueProgressOverlay: { queueProgressOverlay: {
viewJobHistory: 'View job history', viewJobHistory: 'View job history',
expandCollapsedQueue: 'Expand collapsed queue', expandCollapsedQueue: 'Expand collapsed queue',
activeJobsShort: '{count} active | {count} active' activeJobsShort: '{count} active | {count} active',
clearQueueTooltip: 'Clear queue'
} }
} }
} }
@@ -58,7 +60,12 @@ function createWrapper(pinia = createTestingPinia({ createSpy: vi.fn })) {
SubgraphBreadcrumb: true, SubgraphBreadcrumb: true,
QueueProgressOverlay: true, QueueProgressOverlay: true,
CurrentUserButton: true, CurrentUserButton: true,
LoginButton: true LoginButton: true,
ContextMenu: {
name: 'ContextMenu',
props: ['model'],
template: '<div />'
}
}, },
directives: { directives: {
tooltip: () => {} tooltip: () => {}
@@ -185,4 +192,24 @@ describe('TopMenuSection', () => {
await toggleButton.trigger('click') await toggleButton.trigger('click')
expect(sidebarTabStore.activeSidebarTabId).toBe(null) expect(sidebarTabStore.activeSidebarTabId).toBe(null)
}) })
it('disables the clear queue context menu item when no queued jobs exist', () => {
const wrapper = createWrapper()
const menu = wrapper.findComponent({ name: 'ContextMenu' })
const model = menu.props('model') as MenuItem[]
expect(model[0]?.label).toBe('Clear queue')
expect(model[0]?.disabled).toBe(true)
})
it('enables the clear queue context menu item when queued jobs exist', async () => {
const wrapper = createWrapper()
const queueStore = useQueueStore()
queueStore.pendingTasks = [createTask('pending-1', 'pending')]
await nextTick()
const menu = wrapper.findComponent({ name: 'ContextMenu' })
const model = menu.props('model') as MenuItem[]
expect(model[0]?.disabled).toBe(false)
})
}) })

View File

@@ -51,6 +51,7 @@
class="px-3" class="px-3"
data-testid="queue-overlay-toggle" data-testid="queue-overlay-toggle"
@click="toggleQueueOverlay" @click="toggleQueueOverlay"
@contextmenu.stop.prevent="showQueueContextMenu"
> >
<span class="text-sm font-normal tabular-nums"> <span class="text-sm font-normal tabular-nums">
{{ activeJobsLabel }} {{ activeJobsLabel }}
@@ -63,6 +64,7 @@
}} }}
</span> </span>
</Button> </Button>
<ContextMenu ref="queueContextMenu" :model="queueContextMenuItems" />
<CurrentUserButton <CurrentUserButton
v-if="isLoggedIn && !isIntegratedTabBar" v-if="isLoggedIn && !isIntegratedTabBar"
class="shrink-0" class="shrink-0"
@@ -91,6 +93,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import ContextMenu from 'primevue/contextmenu'
import type { MenuItem } from 'primevue/menuitem'
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
@@ -108,6 +112,7 @@ import { useSettingStore } from '@/platform/settings/settingStore'
import { useReleaseStore } from '@/platform/updates/common/releaseStore' import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import { app } from '@/scripts/app' import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore' import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useQueueStore, useQueueUIStore } from '@/stores/queueStore' import { useQueueStore, useQueueUIStore } from '@/stores/queueStore'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore' import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
@@ -127,6 +132,7 @@ const { t, n } = useI18n()
const { toastErrorHandler } = useErrorHandling() const { toastErrorHandler } = useErrorHandling()
const commandStore = useCommandStore() const commandStore = useCommandStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const executionStore = useExecutionStore()
const queueUIStore = useQueueUIStore() const queueUIStore = useQueueUIStore()
const sidebarTabStore = useSidebarTabStore() const sidebarTabStore = useSidebarTabStore()
const { activeJobsCount } = storeToRefs(queueStore) const { activeJobsCount } = storeToRefs(queueStore)
@@ -159,6 +165,18 @@ const queueHistoryTooltipConfig = computed(() =>
const customNodesManagerTooltipConfig = computed(() => const customNodesManagerTooltipConfig = computed(() =>
buildTooltipConfig(t('menu.customNodesManager')) buildTooltipConfig(t('menu.customNodesManager'))
) )
const queueContextMenu = ref<InstanceType<typeof ContextMenu> | null>(null)
const queueContextMenuItems = computed<MenuItem[]>(() => [
{
label: t('sideToolbar.queueProgressOverlay.clearQueueTooltip'),
icon: 'icon-[lucide--list-x] text-destructive-background',
class: '*:text-destructive-background',
disabled: queueStore.pendingTasks.length === 0,
command: () => {
void handleClearQueue()
}
}
])
// Use either release red dot or conflict red dot // Use either release red dot or conflict red dot
const shouldShowRedDot = computed((): boolean => { const shouldShowRedDot = computed((): boolean => {
@@ -189,6 +207,19 @@ const toggleQueueOverlay = () => {
commandStore.execute('Comfy.Queue.ToggleOverlay') commandStore.execute('Comfy.Queue.ToggleOverlay')
} }
const showQueueContextMenu = (event: MouseEvent) => {
queueContextMenu.value?.show(event)
}
const handleClearQueue = async () => {
const pendingPromptIds = queueStore.pendingTasks
.map((task) => task.promptId)
.filter((id): id is string => typeof id === 'string' && id.length > 0)
await commandStore.execute('Comfy.ClearPendingTasks')
executionStore.clearInitializationByPromptIds(pendingPromptIds)
}
const openCustomNodeManager = async () => { const openCustomNodeManager = async () => {
try { try {
await managerState.openManager({ await managerState.openManager({