mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 22:39:39 +00:00
Rework queue button (#968)
* Move queue button to right side * Rework split button * Group * Remove unused code * x2 buttons * Use primevue divider * adjust style * Add tooltip * Update test * Add clearing pending tasks button to queue bar * Fix state * Dropdown list fix
This commit is contained in:
@@ -1,73 +1,47 @@
|
||||
<template>
|
||||
<Panel v-if="visible" class="app-menu">
|
||||
<div class="app-menu-content">
|
||||
<Popover ref="queuePopover" data-testid="queue-options">
|
||||
<div class="queue-options">
|
||||
<p class="batch-count">
|
||||
<FloatLabel v-tooltip="$t('menu.batchCountTooltip')">
|
||||
<InputNumber id="batchCount" v-model="batchCount" :min="1" />
|
||||
<label for="batchCount">{{ $t('menu.batchCount') }}</label>
|
||||
</FloatLabel>
|
||||
|
||||
<Slider
|
||||
v-model="batchCount"
|
||||
:min="1"
|
||||
:max="100"
|
||||
v-tooltip="$t('menu.batchCountTooltip')"
|
||||
<div class="app-menu-content flex align-center">
|
||||
<div class="queue-button-group flex">
|
||||
<SplitButton
|
||||
class="comfyui-queue-button"
|
||||
:label="activeQueueModeMenuItem.label"
|
||||
:icon="activeQueueModeMenuItem.icon"
|
||||
severity="primary"
|
||||
@click="queuePrompt"
|
||||
:model="queueModeMenuItems"
|
||||
data-testid="queue-button"
|
||||
v-tooltip.bottom="$t('menu.queueWorkflow')"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<Button
|
||||
:label="item.label"
|
||||
:icon="item.icon"
|
||||
:severity="item.key === queueMode ? 'primary' : 'secondary'"
|
||||
text
|
||||
v-tooltip="item.tooltip"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<Divider layout="vertical" />
|
||||
|
||||
<p class="auto-queue">
|
||||
<span class="label">{{ $t('menu.autoQueue') }}</span>
|
||||
<template v-for="mode in queueModes" :key="mode">
|
||||
<div
|
||||
v-tooltip="$t(`menu.${mode}Tooltip`)"
|
||||
class="auto-queue-mode"
|
||||
>
|
||||
<RadioButton
|
||||
v-model="queueMode"
|
||||
:inputId="`autoqueue-${mode}`"
|
||||
name="dynamic"
|
||||
:value="mode"
|
||||
:data-testid="`autoqueue-${mode}`"
|
||||
/>
|
||||
<label :for="`autoqueue-${mode}`">{{
|
||||
$t(`menu.${mode}`)
|
||||
}}</label>
|
||||
</div>
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</Popover>
|
||||
<SplitButton
|
||||
v-tooltip.bottom="$t('menu.queueWorkflow')"
|
||||
:label="$t('menu.generate')"
|
||||
:icon="`pi pi-${icon}`"
|
||||
severity="secondary"
|
||||
@click="queuePrompt"
|
||||
:model="[]"
|
||||
:pt="{
|
||||
pcDropdown: ({ instance }) => {
|
||||
instance.onDropdownButtonClick = function (e: Event) {
|
||||
e.preventDefault()
|
||||
queuePopover.toggle(e)
|
||||
}
|
||||
}
|
||||
}"
|
||||
data-testid="queue-button"
|
||||
>
|
||||
</SplitButton>
|
||||
<div class="separator"></div>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('menu.interrupt')"
|
||||
icon="pi pi-times"
|
||||
severity="secondary"
|
||||
:disabled="!executingPrompt"
|
||||
@click="() => commandStore.getCommand('Comfy.Interrupt')()"
|
||||
></Button>
|
||||
|
||||
</template>
|
||||
</SplitButton>
|
||||
<BatchCountEdit />
|
||||
<ButtonGroup class="execution-actions ml-2">
|
||||
<Button
|
||||
v-tooltip.bottom="$t('menu.interrupt')"
|
||||
icon="pi pi-times"
|
||||
:severity="executingPrompt ? 'danger' : 'secondary'"
|
||||
:disabled="!executingPrompt"
|
||||
@click="() => commandStore.getCommand('Comfy.Interrupt')()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
icon="pi pi-stop"
|
||||
:severity="hasPendingTasks ? 'danger' : 'secondary'"
|
||||
:disabled="!hasPendingTasks"
|
||||
@click="() => commandStore.getCommand('Comfy.ClearPendingTasks')()"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<Divider layout="vertical" class="mx-2" />
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
v-tooltip.bottom="$t('menu.refresh')"
|
||||
@@ -89,18 +63,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import Panel from 'primevue/panel'
|
||||
import Divider from 'primevue/divider'
|
||||
import SplitButton from 'primevue/splitbutton'
|
||||
import Button from 'primevue/button'
|
||||
import FloatLabel from 'primevue/floatlabel'
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
import Popover from 'primevue/popover'
|
||||
import Divider from 'primevue/divider'
|
||||
import Slider from 'primevue/slider'
|
||||
import RadioButton from 'primevue/radiobutton'
|
||||
import ButtonGroup from 'primevue/buttongroup'
|
||||
import BatchCountEdit from './BatchCountEdit.vue'
|
||||
import {
|
||||
AutoQueueMode,
|
||||
useQueuePendingTaskCountStore,
|
||||
useQueueSettingsStore
|
||||
} from '@/stores/queueStore'
|
||||
@@ -108,6 +79,8 @@ import { app } from '@/scripts/app'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { MenuItem } from 'primevue/menuitem'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const settingsStore = useSettingStore()
|
||||
const commandStore = useCommandStore()
|
||||
@@ -118,21 +91,46 @@ const visible = computed(
|
||||
() => settingsStore.get('Comfy.UseNewMenu') === 'Floating'
|
||||
)
|
||||
|
||||
const queuePopover = ref(null)
|
||||
const queueModes = ['disabled', 'instant', 'change']
|
||||
|
||||
const icon = computed(() => {
|
||||
switch (queueMode.value) {
|
||||
case 'instant':
|
||||
return 'forward'
|
||||
case 'change':
|
||||
return 'step-forward-alt'
|
||||
default:
|
||||
return 'play'
|
||||
const { t } = useI18n()
|
||||
const queueModeMenuItemLookup: Record<AutoQueueMode, MenuItem> = {
|
||||
disabled: {
|
||||
key: 'disabled',
|
||||
label: 'Queue',
|
||||
icon: 'pi pi-play',
|
||||
tooltip: t('menu.disabledTooltip'),
|
||||
command: () => {
|
||||
queueMode.value = 'disabled'
|
||||
}
|
||||
},
|
||||
instant: {
|
||||
key: 'instant',
|
||||
label: 'Queue (Instant)',
|
||||
icon: 'pi pi-forward',
|
||||
tooltip: t('menu.instantTooltip'),
|
||||
command: () => {
|
||||
queueMode.value = 'instant'
|
||||
}
|
||||
},
|
||||
change: {
|
||||
key: 'change',
|
||||
label: 'Queue (Change)',
|
||||
icon: 'pi pi-step-forward-alt',
|
||||
tooltip: t('menu.changeTooltip'),
|
||||
command: () => {
|
||||
queueMode.value = 'change'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const activeQueueModeMenuItem = computed(
|
||||
() => queueModeMenuItemLookup[queueMode.value]
|
||||
)
|
||||
const queueModeMenuItems = computed(() =>
|
||||
Object.values(queueModeMenuItemLookup)
|
||||
)
|
||||
|
||||
const executingPrompt = computed(() => !!queueCountStore.count.value)
|
||||
const hasPendingTasks = computed(() => queueCountStore.count.value > 1)
|
||||
|
||||
const queuePrompt = (e: MouseEvent) => {
|
||||
app.queuePrompt(e.shiftKey ? -1 : 0, batchCount.value)
|
||||
@@ -149,12 +147,6 @@ const queuePrompt = (e: MouseEvent) => {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.app-menu-content {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.p-panel-content) {
|
||||
padding: 10px;
|
||||
}
|
||||
@@ -163,50 +155,8 @@ const queuePrompt = (e: MouseEvent) => {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.separator {
|
||||
background-color: var(--p-content-border-color);
|
||||
border-radius: 10px;
|
||||
opacity: 0.75;
|
||||
width: 5px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.queue-options {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.batch-count {
|
||||
padding-top: 0.75rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.p-slider {
|
||||
--p-slider-border-radius: 5px;
|
||||
margin: 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.p-floatlabel label {
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: var(--p-floatlabel-focus-color);
|
||||
}
|
||||
|
||||
.auto-queue {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.auto-queue-mode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
.comfyui-queue-button :deep(.p-splitbutton-dropdown) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
69
src/components/appMenu/BatchCountEdit.vue
Normal file
69
src/components/appMenu/BatchCountEdit.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div
|
||||
class="batch-count"
|
||||
:class="props.class"
|
||||
v-tooltip.bottom="$t('menu.batchCount')"
|
||||
>
|
||||
<InputNumber
|
||||
class="w-14"
|
||||
v-model="batchCount"
|
||||
:min="minQueueCount"
|
||||
:max="maxQueueCount"
|
||||
fluid
|
||||
showButtons
|
||||
:pt="{
|
||||
incrementButton: {
|
||||
class: 'w-6',
|
||||
onmousedown: () => {
|
||||
handleClick(true)
|
||||
}
|
||||
},
|
||||
decrementButton: {
|
||||
class: 'w-6',
|
||||
onmousedown: () => {
|
||||
handleClick(false)
|
||||
}
|
||||
}
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useQueueSettingsStore } from '@/stores/queueStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
|
||||
interface Props {
|
||||
class?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
class: ''
|
||||
})
|
||||
|
||||
const queueSettingsStore = useQueueSettingsStore()
|
||||
const { batchCount } = storeToRefs(queueSettingsStore)
|
||||
const minQueueCount = 1
|
||||
const maxQueueCount = 100
|
||||
|
||||
const handleClick = (increment: boolean) => {
|
||||
let newCount: number
|
||||
if (increment) {
|
||||
const originalCount = batchCount.value - 1
|
||||
newCount = originalCount * 2
|
||||
} else {
|
||||
const originalCount = batchCount.value + 1
|
||||
newCount = Math.floor(originalCount / 2)
|
||||
}
|
||||
|
||||
batchCount.value = newCount
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.p-inputtext) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -34,11 +34,10 @@
|
||||
<Button
|
||||
v-if="queueStore.hasPendingTasks"
|
||||
icon="pi pi-stop"
|
||||
text
|
||||
severity="danger"
|
||||
@click="clearPendingTasks"
|
||||
class="clear-pending-button"
|
||||
v-tooltip="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
text
|
||||
@click="() => commandStore.getCommand('Comfy.ClearPendingTasks')()"
|
||||
v-tooltip.bottom="$t('sideToolbar.queueTab.clearPendingTasks')"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
@@ -109,6 +108,7 @@ import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { ComfyNode } from '@/types/comfyWorkflow'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
const IMAGE_FIT = 'Comfy.Queue.ImageFit'
|
||||
@@ -116,6 +116,7 @@ const confirm = useConfirm()
|
||||
const toast = useToast()
|
||||
const queueStore = useQueueStore()
|
||||
const settingStore = useSettingStore()
|
||||
const commandStore = useCommandStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// Expanded view: show all outputs in a flat list.
|
||||
@@ -230,16 +231,6 @@ const confirmRemoveAll = (event: Event) => {
|
||||
})
|
||||
}
|
||||
|
||||
const clearPendingTasks = async () => {
|
||||
await queueStore.clear(['queue'])
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Confirmed',
|
||||
detail: 'Pending tasks deleted',
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
|
||||
const onStatus = async () => {
|
||||
await queueStore.update()
|
||||
updateVisibleTasks()
|
||||
|
||||
Reference in New Issue
Block a user