Backward compatibility with extension injections on legacy menu bar (#970)

* Compatible to legacy top menu extensions

* Rework css

* nit
This commit is contained in:
Chenlei Hu
2024-09-25 15:41:05 +09:00
parent 9199639320
commit 4ab3aa9a39
8 changed files with 68 additions and 390 deletions

View File

@@ -1,140 +1,37 @@
import type { ComfyApp } from '@/scripts/app'
import { api } from '../../api'
import { $el } from '../../ui'
import { downloadBlob } from '../../utils'
import { ComfyButton } from '../components/button'
import { ComfyButtonGroup } from '../components/buttonGroup'
import { ComfySplitButton } from '../components/splitButton'
import { ComfyQueueButton } from './queueButton'
import { getInterruptButton } from './interruptButton'
import './menu.css'
const collapseOnMobile = (t) => {
;(t.element ?? t).classList.add('comfyui-menu-mobile-collapse')
return t
}
const showOnMobile = (t) => {
;(t.element ?? t).classList.add('lt-lg-show')
return t
}
// Import ComfyButton to make sure it's shimmed and exported by vite
import { ComfyButton } from '../components/button'
import { ComfySplitButton } from '../components/splitButton'
import { ComfyPopup } from '../components/popup'
console.debug(
`Keep following definitions ${ComfyButton} ${ComfySplitButton} ${ComfyPopup}`
)
export class ComfyAppMenu {
app: ComfyApp
logo: HTMLElement
saveButton: ComfySplitButton
actionsGroup: ComfyButtonGroup
settingsGroup: ComfyButtonGroup
viewGroup: ComfyButtonGroup
mobileMenuButton: ComfyButton
queueButton: ComfyQueueButton
element: HTMLElement
constructor(app: ComfyApp) {
this.app = app
const getSaveButton = (t?: string) =>
new ComfyButton({
icon: 'content-save',
tooltip: 'Save the current workflow',
action: () => app.workflowManager.activeWorkflow.save(),
content: t
})
this.logo = $el('h1.comfyui-logo.nlg-hide', { title: 'ComfyUI' }, 'ComfyUI')
this.saveButton = new ComfySplitButton(
{
primary: getSaveButton(),
mode: 'hover',
position: 'absolute'
},
getSaveButton('Save'),
new ComfyButton({
icon: 'content-save-edit',
content: 'Save As',
tooltip: 'Save the current graph as a new workflow',
action: () => app.workflowManager.activeWorkflow.save(true)
}),
new ComfyButton({
icon: 'download',
content: 'Export',
tooltip: 'Export the current workflow as JSON',
action: () => this.exportWorkflow('workflow', 'workflow')
}),
new ComfyButton({
icon: 'api',
content: 'Export (API Format)',
tooltip:
'Export the current workflow as JSON for use with the ComfyUI API',
action: () => this.exportWorkflow('workflow_api', 'output'),
visibilitySetting: { id: 'Comfy.DevMode', showValue: true },
app
})
)
const actionButtons = [
new ComfyButton({
icon: 'refresh',
content: 'Refresh',
tooltip: 'Refresh widgets in nodes to find new models or files',
action: () => app.refreshComboInNodes()
}),
new ComfyButton({
icon: 'clipboard-edit-outline',
content: 'Clipspace',
tooltip: 'Open Clipspace window',
action: () => app['openClipspace']()
}),
new ComfyButton({
icon: 'fit-to-page-outline',
content: 'Reset View',
tooltip: 'Reset the canvas view',
action: () => app.resetView()
}),
new ComfyButton({
icon: 'cancel',
content: 'Clear',
tooltip: 'Clears current workflow',
action: () => {
if (
!app.ui.settings.getSettingValue('Comfy.ConfirmClear', true) ||
confirm('Clear workflow?')
) {
app.clean()
app.graph.clear()
api.dispatchEvent(new CustomEvent('graphCleared'))
}
}
})
]
this.actionsGroup = new ComfyButtonGroup(...actionButtons)
// Keep the settings group as there are custom scripts attaching extra
// Keep the group as there are custom scripts attaching extra
// elements to it.
this.actionsGroup = new ComfyButtonGroup()
this.settingsGroup = new ComfyButtonGroup()
const interruptButton = getInterruptButton('nlg-hide').element
this.viewGroup = new ComfyButtonGroup(interruptButton)
this.mobileMenuButton = new ComfyButton({
icon: 'menu',
action: (_, btn) => {
btn.icon = this.element.classList.toggle('expanded')
? 'menu-open'
: 'menu'
window.dispatchEvent(new Event('resize'))
},
classList: 'comfyui-button comfyui-menu-button'
})
this.queueButton = new ComfyQueueButton(app)
this.viewGroup = new ComfyButtonGroup()
this.element = $el('nav.comfyui-menu.lg', { style: { display: 'none' } }, [
this.logo,
this.saveButton.element,
collapseOnMobile(this.actionsGroup).element,
$el('section.comfyui-menu-push'),
collapseOnMobile(this.settingsGroup).element,
collapseOnMobile(this.viewGroup).element,
getInterruptButton('lt-lg-show').element,
this.queueButton.element,
showOnMobile(this.mobileMenuButton).element
this.element = $el('div.flex.gap-2.mx-2', [
this.actionsGroup.element,
this.settingsGroup.element,
this.viewGroup.element
])
}

View File

@@ -1,32 +0,0 @@
import { StatusWsMessageStatus } from '@/types/apiTypes'
import { api } from '../../api'
import { ComfyButton } from '../components/button'
import { useToastStore } from '@/stores/toastStore'
export function getInterruptButton(visibility: string) {
const btn = new ComfyButton({
icon: 'close',
tooltip: 'Cancel current generation',
enabled: false,
action: async () => {
await api.interrupt()
useToastStore().add({
severity: 'info',
summary: 'Interrupted',
detail: 'Execution has been interrupted',
life: 1000
})
},
classList: ['comfyui-button', 'comfyui-interrupt-button', visibility]
})
api.addEventListener(
'status',
({ detail }: CustomEvent<StatusWsMessageStatus>) => {
const sz = detail?.exec_info?.queue_remaining
btn.enabled = sz > 0
}
)
return btn
}

View File

@@ -122,9 +122,6 @@
}
/* Menu */
.comfyui-menu>* {
flex-shrink: 0;
}
.comfyui-menu .mdi::before {
font-size: 18px;
}

View File

@@ -1,103 +0,0 @@
import { ComfyButton } from '../components/button'
import { $el } from '../../ui'
import { api } from '../../api'
import { ComfySplitButton } from '../components/splitButton'
import { ComfyQueueOptions } from './queueOptions'
import { prop } from '../../utils'
import type { ComfyApp } from '@/scripts/app'
import { StatusWsMessageStatus } from '@/types/apiTypes'
export class ComfyQueueButton {
element = $el('div.comfyui-queue-button')
#internalQueueSize = 0
queuePrompt = async (e?: MouseEvent) => {
this.#internalQueueSize += this.queueOptions.batchCount
// Hold shift to queue front, event is undefined when auto-queue is enabled
await this.app.queuePrompt(
e?.shiftKey ? -1 : 0,
this.queueOptions.batchCount
)
}
queueOptions: ComfyQueueOptions
app: ComfyApp
autoQueueMode: string
graphHasChanged: boolean
constructor(app: ComfyApp) {
this.app = app
const queue = new ComfyButton({
content: $el('div', [
$el('span', {
textContent: 'Queue'
})
]),
icon: 'play',
classList: 'comfyui-button',
action: this.queuePrompt
})
this.queueOptions = new ComfyQueueOptions(app)
const btn = new ComfySplitButton(
{
primary: queue,
mode: 'click',
position: 'absolute',
horizontal: 'right'
},
this.queueOptions.element
)
btn.element.classList.add('primary')
this.element.append(btn.element)
this.autoQueueMode = prop(this, 'autoQueueMode', '', () => {
switch (this.autoQueueMode) {
case 'instant':
queue.icon = 'infinity'
break
case 'change':
queue.icon = 'auto-mode'
break
default:
queue.icon = 'play'
break
}
})
this.queueOptions.addEventListener(
'autoQueueMode',
(e) => (this.autoQueueMode = e['detail'])
)
api.addEventListener('graphChanged', () => {
if (this.autoQueueMode === 'change') {
if (this.#internalQueueSize) {
this.graphHasChanged = true
} else {
this.graphHasChanged = false
this.queuePrompt()
}
}
})
api.addEventListener(
'status',
({ detail }: CustomEvent<StatusWsMessageStatus>) => {
this.#internalQueueSize = detail?.exec_info?.queue_remaining
if (this.#internalQueueSize != null) {
if (!this.#internalQueueSize && !app.lastExecutionError) {
if (
this.autoQueueMode === 'instant' ||
(this.autoQueueMode === 'change' && this.graphHasChanged)
) {
this.graphHasChanged = false
this.queuePrompt()
}
}
}
}
)
}
}

View File

@@ -1,82 +0,0 @@
import type { ComfyApp } from '@/scripts/app'
import { $el } from '../../ui'
import { prop } from '../../utils'
export class ComfyQueueOptions extends EventTarget {
element = $el('div.comfyui-queue-options')
app: ComfyApp
batchCountInput: HTMLInputElement
batchCount: number
batchCountRange: HTMLInputElement
autoQueueMode: string
autoQueueEl: HTMLElement
constructor(app: ComfyApp) {
super()
this.app = app
this.batchCountInput = $el('input', {
className: 'comfyui-queue-batch-value',
type: 'number',
min: '1',
value: '1',
oninput: () => (this.batchCount = +this.batchCountInput.value)
})
this.batchCountRange = $el('input', {
type: 'range',
min: '1',
max: '100',
value: '1',
oninput: () => (this.batchCount = +this.batchCountRange.value)
})
this.element.append(
$el('div.comfyui-queue-batch', [
$el(
'label',
{
textContent: 'Batch count: '
},
this.batchCountInput
),
this.batchCountRange
])
)
const createOption = (text, value, checked = false) =>
$el(
'label',
{ textContent: text },
$el('input', {
type: 'radio',
name: 'AutoQueueMode',
checked,
value,
oninput: (e) => (this.autoQueueMode = e.target['value'])
})
)
this.autoQueueEl = $el('div.comfyui-queue-mode', [
$el('span', 'Auto Queue:'),
createOption('Disabled', '', true),
createOption('Instant', 'instant'),
createOption('On Change', 'change')
])
this.element.append(this.autoQueueEl)
this.batchCount = prop(this, 'batchCount', 1, () => {
this.batchCountInput.value = this.batchCount + ''
this.batchCountRange.value = this.batchCount + ''
})
this.autoQueueMode = prop(this, 'autoQueueMode', 'Disabled', () => {
this.dispatchEvent(
new CustomEvent('autoQueueMode', {
detail: this.autoQueueMode
})
)
})
}
}