fix: keep queue overlay expanded only

This commit is contained in:
Benjamin Lu
2026-01-21 09:26:33 -08:00
parent 9a6ead37cb
commit f23c291f25
2 changed files with 10 additions and 144 deletions

View File

@@ -1,10 +1,5 @@
<template> <template>
<div <div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
v-if="!workspaceStore.focusMode"
class="ml-1 flex gap-x-0.5 pt-1"
@mouseenter="isTopMenuHovered = true"
@mouseleave="isTopMenuHovered = false"
>
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<SubgraphBreadcrumb /> <SubgraphBreadcrumb />
</div> </div>
@@ -76,10 +71,7 @@
</Button> </Button>
</div> </div>
</div> </div>
<QueueProgressOverlay <QueueProgressOverlay v-model:expanded="isQueueOverlayExpanded" />
v-model:expanded="isQueueOverlayExpanded"
:menu-hovered="isTopMenuHovered"
/>
</div> </div>
</div> </div>
</template> </template>
@@ -127,7 +119,6 @@ const releaseStore = useReleaseStore()
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore) const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
const { shouldShowRedDot: shouldShowConflictRedDot } = const { shouldShowRedDot: shouldShowConflictRedDot } =
useConflictAcknowledgment() useConflictAcknowledgment()
const isTopMenuHovered = ref(false)
const queuedCount = computed(() => queueStore.pendingTasks.length) const queuedCount = computed(() => queueStore.pendingTasks.length)
const isIntegratedTabBar = computed( const isIntegratedTabBar = computed(
() => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated' () => settingStore.get('Comfy.UI.TabBarLayout') === 'Integrated'

View File

@@ -1,17 +1,9 @@
<template> <template>
<div <div v-if="isExpanded" class="flex justify-end w-full pointer-events-none">
v-show="isVisible"
:class="['flex', 'justify-end', 'w-full', 'pointer-events-none']"
>
<div <div
class="pointer-events-auto flex w-[350px] min-w-[310px] max-h-[60vh] flex-col overflow-hidden rounded-lg border font-inter transition-colors duration-200 ease-in-out" class="pointer-events-auto flex w-[350px] min-w-[310px] max-h-[60vh] flex-col overflow-hidden rounded-lg border border-interface-stroke bg-interface-panel-surface font-inter shadow-interface"
:class="containerClass"
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
> >
<!-- Expanded state -->
<QueueOverlayExpanded <QueueOverlayExpanded
v-if="isExpanded"
v-model:selected-job-tab="selectedJobTab" v-model:selected-job-tab="selectedJobTab"
v-model:selected-workflow-filter="selectedWorkflowFilter" v-model:selected-workflow-filter="selectedWorkflowFilter"
v-model:selected-sort-mode="selectedSortMode" v-model:selected-sort-mode="selectedSortMode"
@@ -29,27 +21,6 @@
@delete-item="onDeleteItem" @delete-item="onDeleteItem"
@view-item="inspectJobAsset" @view-item="inspectJobAsset"
/> />
<QueueOverlayActive
v-else-if="hasActiveJob"
:total-progress-style="totalProgressStyle"
:current-node-progress-style="currentNodeProgressStyle"
:total-percent-formatted="totalPercentFormatted"
:current-node-percent-formatted="currentNodePercentFormatted"
:current-node-name="currentNodeName"
:running-count="runningCount"
:queued-count="queuedCount"
:bottom-row-class="bottomRowClass"
@interrupt-all="interruptAll"
@clear-queued="cancelQueuedWorkflows"
@view-all-jobs="viewAllJobs"
/>
<QueueOverlayEmpty
v-else-if="completionSummary"
:summary="completionSummary"
@summary-click="onSummaryClick"
/>
</div> </div>
</div> </div>
@@ -63,15 +34,11 @@
import { computed, nextTick, ref } from 'vue' import { computed, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import QueueOverlayActive from '@/components/queue/QueueOverlayActive.vue'
import QueueOverlayEmpty from '@/components/queue/QueueOverlayEmpty.vue'
import QueueOverlayExpanded from '@/components/queue/QueueOverlayExpanded.vue' import QueueOverlayExpanded from '@/components/queue/QueueOverlayExpanded.vue'
import QueueClearHistoryDialog from '@/components/queue/dialogs/QueueClearHistoryDialog.vue' import QueueClearHistoryDialog from '@/components/queue/dialogs/QueueClearHistoryDialog.vue'
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue' import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
import { useCompletionSummary } from '@/composables/queue/useCompletionSummary'
import { useJobList } from '@/composables/queue/useJobList' import { useJobList } from '@/composables/queue/useJobList'
import type { JobListItem } from '@/composables/queue/useJobList' import type { JobListItem } from '@/composables/queue/useJobList'
import { useQueueProgress } from '@/composables/queue/useQueueProgress'
import { useResultGallery } from '@/composables/queue/useResultGallery' import { useResultGallery } from '@/composables/queue/useResultGallery'
import { useErrorHandling } from '@/composables/useErrorHandling' import { useErrorHandling } from '@/composables/useErrorHandling'
import { useAssetSelectionStore } from '@/platform/assets/composables/useAssetSelectionStore' import { useAssetSelectionStore } from '@/platform/assets/composables/useAssetSelectionStore'
@@ -84,17 +51,9 @@ import { useExecutionStore } from '@/stores/executionStore'
import { useQueueStore } from '@/stores/queueStore' import { useQueueStore } from '@/stores/queueStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore' import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
type OverlayState = 'hidden' | 'empty' | 'active' | 'expanded' const { expanded } = defineProps<{
expanded?: boolean
const props = withDefaults( }>()
defineProps<{
expanded?: boolean
menuHovered?: boolean
}>(),
{
menuHovered: false
}
)
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:expanded', value: boolean): void (e: 'update:expanded', value: boolean): void
@@ -110,65 +69,22 @@ const assetsStore = useAssetsStore()
const assetSelectionStore = useAssetSelectionStore() const assetSelectionStore = useAssetSelectionStore()
const { wrapWithErrorHandlingAsync } = useErrorHandling() const { wrapWithErrorHandlingAsync } = useErrorHandling()
const {
totalPercentFormatted,
currentNodePercentFormatted,
totalProgressStyle,
currentNodeProgressStyle
} = useQueueProgress()
const isHovered = ref(false)
const isOverlayHovered = computed(() => isHovered.value || props.menuHovered)
const internalExpanded = ref(false) const internalExpanded = ref(false)
const isExpanded = computed({ const isExpanded = computed({
get: () => get: () => (expanded === undefined ? internalExpanded.value : expanded),
props.expanded === undefined ? internalExpanded.value : props.expanded,
set: (value) => { set: (value) => {
if (props.expanded === undefined) { if (expanded === undefined) {
internalExpanded.value = value internalExpanded.value = value
} }
emit('update:expanded', value) emit('update:expanded', value)
} }
}) })
const { summary: completionSummary, clearSummary } = useCompletionSummary()
const hasCompletionSummary = computed(() => completionSummary.value !== null)
const runningCount = computed(() => queueStore.runningTasks.length) const runningCount = computed(() => queueStore.runningTasks.length)
const queuedCount = computed(() => queueStore.pendingTasks.length) const queuedCount = computed(() => queueStore.pendingTasks.length)
const isExecuting = computed(() => !executionStore.isIdle) const isExecuting = computed(() => !executionStore.isIdle)
const hasActiveJob = computed(() => runningCount.value > 0 || isExecuting.value) const hasActiveJob = computed(() => runningCount.value > 0 || isExecuting.value)
const activeJobsCount = computed(() => runningCount.value + queuedCount.value) const activeJobsCount = computed(() => runningCount.value + queuedCount.value)
const overlayState = computed<OverlayState>(() => {
if (isExpanded.value) return 'expanded'
if (hasActiveJob.value) return 'active'
if (hasCompletionSummary.value) return 'empty'
return 'hidden'
})
const showBackground = computed(
() =>
overlayState.value === 'expanded' ||
overlayState.value === 'empty' ||
(overlayState.value === 'active' && isOverlayHovered.value)
)
const isVisible = computed(() => overlayState.value !== 'hidden')
const containerClass = computed(() =>
showBackground.value
? 'border-interface-stroke bg-interface-panel-surface shadow-interface'
: 'border-transparent bg-transparent shadow-none'
)
const bottomRowClass = computed(
() =>
`flex items-center justify-end gap-4 transition-opacity duration-200 ease-in-out ${
overlayState.value === 'active' && isOverlayHovered.value
? 'opacity-100 pointer-events-auto'
: 'opacity-0 pointer-events-none'
}`
)
const headerTitle = computed(() => const headerTitle = computed(() =>
hasActiveJob.value hasActiveJob.value
? `${activeJobsCount.value} ${t('sideToolbar.queueProgressOverlay.activeJobsSuffix')}` ? `${activeJobsCount.value} ${t('sideToolbar.queueProgressOverlay.activeJobsSuffix')}`
@@ -188,8 +104,7 @@ const {
selectedSortMode, selectedSortMode,
hasFailedJobs, hasFailedJobs,
filteredTasks, filteredTasks,
groupedJobItems, groupedJobItems
currentNodeName
} = useJobList() } = useJobList()
const displayedJobGroups = computed(() => groupedJobItems.value) const displayedJobGroups = computed(() => groupedJobItems.value)
@@ -226,23 +141,6 @@ const {
onViewItem: openResultGallery onViewItem: openResultGallery
} = useResultGallery(() => filteredTasks.value) } = useResultGallery(() => filteredTasks.value)
const setExpanded = (expanded: boolean) => {
isExpanded.value = expanded
}
const openExpandedFromEmpty = () => {
setExpanded(true)
}
const viewAllJobs = () => {
setExpanded(true)
}
const onSummaryClick = () => {
openExpandedFromEmpty()
clearSummary()
}
const openAssetsSidebar = () => { const openAssetsSidebar = () => {
sidebarTabStore.activeSidebarTabId = 'assets' sidebarTabStore.activeSidebarTabId = 'assets'
} }
@@ -285,29 +183,6 @@ const cancelQueuedWorkflows = wrapWithErrorHandlingAsync(async () => {
executionStore.clearInitializationByPromptIds(pendingPromptIds) executionStore.clearInitializationByPromptIds(pendingPromptIds)
}) })
const interruptAll = wrapWithErrorHandlingAsync(async () => {
const tasks = queueStore.runningTasks
const promptIds = tasks
.map((task) => task.promptId)
.filter((id): id is string => typeof id === 'string' && id.length > 0)
if (!promptIds.length) return
// Cloud backend supports cancelling specific jobs via /queue delete,
// while /interrupt always targets the "first" job. Use the targeted API
// on cloud to ensure we cancel the workflow the user clicked.
if (isCloud) {
await Promise.all(promptIds.map((id) => api.deleteItem('queue', id)))
executionStore.clearInitializationByPromptIds(promptIds)
await queueStore.update()
return
}
await Promise.all(promptIds.map((id) => api.interrupt(id)))
executionStore.clearInitializationByPromptIds(promptIds)
await queueStore.update()
})
const showClearHistoryDialog = () => { const showClearHistoryDialog = () => {
dialogStore.showDialog({ dialogStore.showDialog({
key: 'queue-clear-history', key: 'queue-clear-history',