mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-15 01:48:06 +00:00
Compare commits
19 Commits
fix/qwenvl
...
desktop-wi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e8e9e624b | ||
|
|
546993ffe3 | ||
|
|
c41db49f7a | ||
|
|
a9e4f5eb22 | ||
|
|
c5314e4dc0 | ||
|
|
aa08f3d1cc | ||
|
|
9b28372501 | ||
|
|
56e18aee3e | ||
|
|
6df7a45d61 | ||
|
|
5f90dd9c3f | ||
|
|
002b7df795 | ||
|
|
9d108b80b9 | ||
|
|
504c661859 | ||
|
|
eb01fa6574 | ||
|
|
104c0a5ba4 | ||
|
|
45596058f1 | ||
|
|
582ca967c6 | ||
|
|
738de3baaa | ||
|
|
ad87080e8c |
BIN
public/assets/images/Comfy_Logo_x256.png
Normal file
BIN
public/assets/images/Comfy_Logo_x256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -45,6 +45,9 @@ onMounted(() => {
|
||||
|
||||
if (isElectron()) {
|
||||
document.addEventListener('contextmenu', showContextMenu)
|
||||
|
||||
// Enable CSS selectors
|
||||
document.documentElement.dataset['platform'] = 'electron'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -765,7 +765,6 @@ audio.comfy-audio.empty-audio-widget {
|
||||
padding: var(--comfy-tree-explorer-item-padding) !important;
|
||||
}
|
||||
|
||||
/* [Desktop] Electron window specific styles */
|
||||
.app-drag {
|
||||
app-region: drag;
|
||||
}
|
||||
@@ -773,8 +772,3 @@ audio.comfy-audio.empty-audio-widget {
|
||||
.no-drag {
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.window-actions-spacer {
|
||||
width: calc(100vw - env(titlebar-area-width, 100vw));
|
||||
}
|
||||
/* End of [Desktop] Electron window specific styles */
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
<Button
|
||||
v-show="workspaceState.focusMode"
|
||||
class="comfy-menu-hamburger no-drag"
|
||||
class="comfy-menu-hamburger"
|
||||
:style="positionCSS"
|
||||
>
|
||||
<Button
|
||||
icon="pi pi-bars"
|
||||
severity="secondary"
|
||||
text
|
||||
size="large"
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
:aria-label="$t('menu.showMenu')"
|
||||
aria-live="assertive"
|
||||
@click="exitFocusMode"
|
||||
@contextmenu="showNativeMenu"
|
||||
/>
|
||||
<div v-show="menuSetting !== 'Bottom'" class="window-actions-spacer" />
|
||||
</div>
|
||||
icon="pi pi-bars"
|
||||
severity="secondary"
|
||||
text
|
||||
size="large"
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
:aria-label="$t('menu.showMenu')"
|
||||
aria-live="assertive"
|
||||
@click="exitFocusMode"
|
||||
@contextmenu="showNativeMenu"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -50,13 +46,15 @@ const positionCSS = computed<CSSProperties>(() =>
|
||||
// 'Bottom' menuSetting shows the hamburger button in the bottom right corner
|
||||
// 'Disabled', 'Top' menuSetting shows the hamburger button in the top right corner
|
||||
menuSetting.value === 'Bottom'
|
||||
? { bottom: '0px', right: '0px' }
|
||||
: { top: '0px', right: '0px' }
|
||||
? { bottom: '0', right: '0' }
|
||||
: { top: '0', right: 'calc(100% - env(titlebar-area-width, 100%))' }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.comfy-menu-hamburger {
|
||||
@apply pointer-events-auto fixed z-[9999] flex flex-row;
|
||||
pointer-events: auto;
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div
|
||||
ref="topMenuRef"
|
||||
class="comfyui-menu flex items-center"
|
||||
v-show="showTopMenu"
|
||||
v-show="betaMenuEnabled && !workspaceState.focusMode"
|
||||
:class="{ dropzone: isDropZone, 'dropzone-active': isDroppable }"
|
||||
>
|
||||
<h1 class="comfyui-logo mx-2 app-drag">ComfyUI</h1>
|
||||
<h1 class="comfyui-logo mx-2">ComfyUI</h1>
|
||||
<CommandMenubar />
|
||||
<Divider layout="vertical" class="mx-2" />
|
||||
<div class="flex-grow min-w-0 app-drag h-full">
|
||||
<div class="flex-grow min-w-0">
|
||||
<WorkflowTabs v-if="workflowTabsPosition === 'Topbar'" />
|
||||
</div>
|
||||
<div class="comfyui-menu-right" ref="menuRight"></div>
|
||||
@@ -25,22 +25,12 @@
|
||||
@click="workspaceState.focusMode = true"
|
||||
@contextmenu="showNativeMenu"
|
||||
/>
|
||||
<div
|
||||
v-show="menuSetting !== 'Bottom'"
|
||||
class="window-actions-spacer flex-shrink-0"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
|
||||
<!-- Virtual top menu for native window (drag handle) -->
|
||||
<div
|
||||
v-show="isNativeWindow && !showTopMenu"
|
||||
class="fixed top-0 left-0 app-drag w-full h-[var(--comfy-topbar-height)]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useEventBus } from '@vueuse/core'
|
||||
import { useEventBus, useResizeObserver } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import Divider from 'primevue/divider'
|
||||
import { computed, onMounted, provide, ref } from 'vue'
|
||||
@@ -59,20 +49,14 @@ const settingStore = useSettingStore()
|
||||
const workflowTabsPosition = computed(() =>
|
||||
settingStore.get('Comfy.Workflow.WorkflowTabsPosition')
|
||||
)
|
||||
const menuSetting = computed(() => settingStore.get('Comfy.UseNewMenu'))
|
||||
const betaMenuEnabled = computed(() => menuSetting.value !== 'Disabled')
|
||||
const betaMenuEnabled = computed(
|
||||
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
|
||||
)
|
||||
const teleportTarget = computed(() =>
|
||||
settingStore.get('Comfy.UseNewMenu') === 'Top'
|
||||
? '.comfyui-body-top'
|
||||
: '.comfyui-body-bottom'
|
||||
)
|
||||
const isNativeWindow = computed(
|
||||
() =>
|
||||
isElectron() && settingStore.get('Comfy-Desktop.WindowStyle') === 'custom'
|
||||
)
|
||||
const showTopMenu = computed(
|
||||
() => betaMenuEnabled.value && !workspaceState.focusMode
|
||||
)
|
||||
|
||||
const menuRight = ref<HTMLDivElement | null>(null)
|
||||
// Menu-right holds legacy topbar elements attached by custom scripts
|
||||
@@ -94,13 +78,20 @@ eventBus.on((event: string, payload: any) => {
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (isElectron()) {
|
||||
electronAPI().changeTheme({
|
||||
height: topMenuRef.value.getBoundingClientRect().height
|
||||
})
|
||||
}
|
||||
})
|
||||
/** Height of titlebar on desktop. */
|
||||
if (isElectron()) {
|
||||
let desktopHeight = 0
|
||||
|
||||
useResizeObserver(topMenuRef, (entries) => {
|
||||
if (settingStore.get('Comfy.UseNewMenu') !== 'Top') return
|
||||
|
||||
const { height } = entries[0].contentRect
|
||||
if (desktopHeight === height) return
|
||||
|
||||
electronAPI().changeTheme({ height })
|
||||
desktopHeight = height
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -136,3 +127,30 @@ onMounted(() => {
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="postcss">
|
||||
/* Desktop: Custom window styling */
|
||||
:root[data-platform='electron'] {
|
||||
.comfyui-logo {
|
||||
@apply flex items-center gap-2 my-1 mx-1.5;
|
||||
|
||||
&::before {
|
||||
@apply w-7 h-7 bg-[url('/assets/images/Comfy_Logo_x256.png')] bg-no-repeat bg-contain content-[''];
|
||||
}
|
||||
}
|
||||
|
||||
.comfyui-body-top {
|
||||
.comfyui-menu {
|
||||
app-region: drag;
|
||||
padding-right: calc(100% - env(titlebar-area-width, 0));
|
||||
}
|
||||
}
|
||||
|
||||
button,
|
||||
.p-menubar,
|
||||
.comfyui-menu-right > *,
|
||||
.actionbar {
|
||||
app-region: no-drag;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="workflow-tabs-container flex flex-row max-w-full">
|
||||
<div class="workflow-tabs-container flex flex-row w-full">
|
||||
<ScrollPanel
|
||||
class="overflow-hidden no-drag"
|
||||
class="overflow-hidden"
|
||||
:pt:content="{
|
||||
class: 'p-0 w-full',
|
||||
onwheel: handleWheel
|
||||
@@ -28,7 +28,7 @@
|
||||
</ScrollPanel>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('sideToolbar.newBlankWorkflow'), showDelay: 300 }"
|
||||
class="new-blank-workflow-button flex-shrink-0 no-drag"
|
||||
class="new-blank-workflow-button flex-shrink-0"
|
||||
icon="pi pi-plus"
|
||||
text
|
||||
severity="secondary"
|
||||
|
||||
@@ -386,6 +386,8 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
category: ['Comfy', 'Menu', 'UseNewMenu'],
|
||||
defaultValue: 'Top',
|
||||
name: 'Use new menu',
|
||||
tooltip:
|
||||
'(Desktop, Windows only): When using custom window style, only Top is supported',
|
||||
type: 'combo',
|
||||
options: ['Disabled', 'Top', 'Bottom'],
|
||||
migrateDeprecatedValue: (value: string) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { t } from '@/i18n'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
|
||||
|
||||
;(async () => {
|
||||
@@ -39,17 +40,22 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
|
||||
id: 'Comfy-Desktop.WindowStyle',
|
||||
category: ['Comfy-Desktop', 'General', 'Window Style'],
|
||||
name: 'Window Style',
|
||||
tooltip: 'Choose custom option to hide the system title bar',
|
||||
tooltip: "Custom: Replace the system title bar with ComfyUI's Top menu",
|
||||
type: 'combo',
|
||||
defaultValue: 'default',
|
||||
options: ['default', 'custom'],
|
||||
onChange: (
|
||||
newValue: 'default' | 'custom',
|
||||
oldValue: 'default' | 'custom'
|
||||
oldValue?: 'default' | 'custom'
|
||||
) => {
|
||||
electronAPI.Config.setWindowStyle(newValue)
|
||||
if (!oldValue) return
|
||||
|
||||
onChangeRestartApp(newValue, oldValue)
|
||||
// Custom window mode requires the Top menu.
|
||||
if (newValue === 'custom') {
|
||||
useSettingStore().set('Comfy.UseNewMenu', 'Top')
|
||||
}
|
||||
|
||||
electronAPI.Config.setWindowStyle(newValue)
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -188,4 +194,21 @@ import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// TODO: Replace monkey patch with API or replace UX.
|
||||
// If the user changes frontend menu type, ensure custom window style is disabled.
|
||||
const menuSetting = useSettingStore().settingsById['Comfy.UseNewMenu']
|
||||
if (menuSetting) {
|
||||
const { onChange } = menuSetting
|
||||
menuSetting.onChange = (
|
||||
newValue: 'Disabled' | 'Top' | 'Bottom',
|
||||
oldValue?: 'Disabled' | 'Top' | 'Bottom'
|
||||
) => {
|
||||
const style = useSettingStore().get('Comfy-Desktop.WindowStyle')
|
||||
if (oldValue === 'Top' && style === 'custom') {
|
||||
useSettingStore().set('Comfy-Desktop.WindowStyle', 'default')
|
||||
}
|
||||
return onChange?.(newValue, oldValue)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"Comfy-Desktop_WindowStyle": {
|
||||
"name": "Window Style",
|
||||
"tooltip": "Choose custom option to hide the system title bar",
|
||||
"tooltip": "Custom: Replace the system title bar with ComfyUI's Top menu",
|
||||
"options": {
|
||||
"default": "default",
|
||||
"custom": "custom"
|
||||
@@ -261,6 +261,7 @@
|
||||
},
|
||||
"Comfy_UseNewMenu": {
|
||||
"name": "Use new menu",
|
||||
"tooltip": "(Desktop, Windows only): When using custom window style, only Top is supported",
|
||||
"options": {
|
||||
"Disabled": "Disabled",
|
||||
"Top": "Top",
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
"Bottom": "Bas",
|
||||
"Disabled": "Désactivé",
|
||||
"Top": "Haut"
|
||||
}
|
||||
},
|
||||
"tooltip": "(Bureau, uniquement Windows): Lors de l'utilisation d'un style de fenêtre personnalisé, seul Top est pris en charge"
|
||||
},
|
||||
"Comfy_Validation_NodeDefs": {
|
||||
"name": "Valider les définitions de nœuds (lent)",
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
"Bottom": "下",
|
||||
"Disabled": "無効",
|
||||
"Top": "上"
|
||||
}
|
||||
},
|
||||
"tooltip": "(デスクトップ、Windowsのみ): カスタムウィンドウスタイルを使用する場合、Topのみがサポートされます"
|
||||
},
|
||||
"Comfy_Validation_NodeDefs": {
|
||||
"name": "ノード定義を検証(遅い)",
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
"Bottom": "하단",
|
||||
"Disabled": "비활성화",
|
||||
"Top": "상단"
|
||||
}
|
||||
},
|
||||
"tooltip": "(데스크톱, 윈도우 전용): 사용자 정의 창 스타일을 사용할 때는 Top만 지원됩니다"
|
||||
},
|
||||
"Comfy_Validation_NodeDefs": {
|
||||
"name": "노드 정의 유효성 검사 (느림)",
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
"Bottom": "Внизу",
|
||||
"Disabled": "Отключено",
|
||||
"Top": "Вверху"
|
||||
}
|
||||
},
|
||||
"tooltip": "(Рабочий стол, только для Windows): При использовании пользовательского стиля окна поддерживается только верхняя часть"
|
||||
},
|
||||
"Comfy_Validation_NodeDefs": {
|
||||
"name": "Проверка определений узлов (медленно)",
|
||||
|
||||
@@ -265,7 +265,8 @@
|
||||
"Bottom": "底部",
|
||||
"Disabled": "禁用",
|
||||
"Top": "顶部"
|
||||
}
|
||||
},
|
||||
"tooltip": "(仅限桌面,Windows): 使用自定义窗口样式时,只支持顶部"
|
||||
},
|
||||
"Comfy_Validation_NodeDefs": {
|
||||
"name": "校验节点定义(慢)",
|
||||
|
||||
@@ -411,231 +411,223 @@ export class ComfyUI {
|
||||
}
|
||||
})
|
||||
|
||||
this.menuContainer = $el(
|
||||
'div.comfy-menu.no-drag',
|
||||
{ parent: containerElement },
|
||||
[
|
||||
$el(
|
||||
'div.drag-handle.comfy-menu-header',
|
||||
{
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
cursor: 'default'
|
||||
}
|
||||
},
|
||||
[
|
||||
$el('span.drag-handle'),
|
||||
$el('span.comfy-menu-queue-size', {
|
||||
$: (q) => (this.queueSize = q)
|
||||
this.menuContainer = $el('div.comfy-menu', { parent: containerElement }, [
|
||||
$el(
|
||||
'div.drag-handle.comfy-menu-header',
|
||||
{
|
||||
style: {
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
cursor: 'default'
|
||||
}
|
||||
},
|
||||
[
|
||||
$el('span.drag-handle'),
|
||||
$el('span.comfy-menu-queue-size', { $: (q) => (this.queueSize = q) }),
|
||||
$el('div.comfy-menu-actions', [
|
||||
$el('button.comfy-settings-btn', {
|
||||
textContent: '⚙️',
|
||||
onclick: () => {
|
||||
useDialogService().showSettingsDialog()
|
||||
}
|
||||
}),
|
||||
$el('div.comfy-menu-actions', [
|
||||
$el('button.comfy-settings-btn', {
|
||||
textContent: '⚙️',
|
||||
onclick: () => {
|
||||
useDialogService().showSettingsDialog()
|
||||
}
|
||||
}),
|
||||
$el('button.comfy-close-menu-btn', {
|
||||
textContent: '\u00d7',
|
||||
onclick: () => {
|
||||
useWorkspaceStore().focusMode = true
|
||||
}
|
||||
})
|
||||
])
|
||||
]
|
||||
),
|
||||
$el('button.comfy-queue-btn', {
|
||||
id: 'queue-button',
|
||||
textContent: 'Queue Prompt',
|
||||
onclick: () => app.queuePrompt(0, this.batchCount)
|
||||
}),
|
||||
$el('div', {}, [
|
||||
$el('label', { innerHTML: 'Extra options' }, [
|
||||
$el('input', {
|
||||
type: 'checkbox',
|
||||
onchange: (i) => {
|
||||
document.getElementById('extraOptions').style.display = i
|
||||
.srcElement.checked
|
||||
? 'block'
|
||||
: 'none'
|
||||
this.batchCount = i.srcElement.checked
|
||||
? Number.parseInt(
|
||||
(
|
||||
document.getElementById(
|
||||
'batchCountInputRange'
|
||||
) as HTMLInputElement
|
||||
).value
|
||||
)
|
||||
: 1
|
||||
;(
|
||||
document.getElementById(
|
||||
'autoQueueCheckbox'
|
||||
) as HTMLInputElement
|
||||
).checked = false
|
||||
this.autoQueueEnabled = false
|
||||
$el('button.comfy-close-menu-btn', {
|
||||
textContent: '\u00d7',
|
||||
onclick: () => {
|
||||
useWorkspaceStore().focusMode = true
|
||||
}
|
||||
})
|
||||
])
|
||||
]),
|
||||
$el(
|
||||
'div',
|
||||
{ id: 'extraOptions', style: { width: '100%', display: 'none' } },
|
||||
[
|
||||
$el('div', [
|
||||
$el('label', { innerHTML: 'Batch count' }),
|
||||
$el('input', {
|
||||
id: 'batchCountInputNumber',
|
||||
type: 'number',
|
||||
value: this.batchCount,
|
||||
min: '1',
|
||||
style: { width: '35%', marginLeft: '0.4em' },
|
||||
oninput: (i) => {
|
||||
this.batchCount = i.target.value
|
||||
/* Even though an <input> element with a type of range logically represents a number (since
|
||||
]
|
||||
),
|
||||
$el('button.comfy-queue-btn', {
|
||||
id: 'queue-button',
|
||||
textContent: 'Queue Prompt',
|
||||
onclick: () => app.queuePrompt(0, this.batchCount)
|
||||
}),
|
||||
$el('div', {}, [
|
||||
$el('label', { innerHTML: 'Extra options' }, [
|
||||
$el('input', {
|
||||
type: 'checkbox',
|
||||
onchange: (i) => {
|
||||
document.getElementById('extraOptions').style.display = i
|
||||
.srcElement.checked
|
||||
? 'block'
|
||||
: 'none'
|
||||
this.batchCount = i.srcElement.checked
|
||||
? Number.parseInt(
|
||||
(
|
||||
document.getElementById(
|
||||
'batchCountInputRange'
|
||||
) as HTMLInputElement
|
||||
).value
|
||||
)
|
||||
: 1
|
||||
;(
|
||||
document.getElementById('autoQueueCheckbox') as HTMLInputElement
|
||||
).checked = false
|
||||
this.autoQueueEnabled = false
|
||||
}
|
||||
})
|
||||
])
|
||||
]),
|
||||
$el(
|
||||
'div',
|
||||
{ id: 'extraOptions', style: { width: '100%', display: 'none' } },
|
||||
[
|
||||
$el('div', [
|
||||
$el('label', { innerHTML: 'Batch count' }),
|
||||
$el('input', {
|
||||
id: 'batchCountInputNumber',
|
||||
type: 'number',
|
||||
value: this.batchCount,
|
||||
min: '1',
|
||||
style: { width: '35%', marginLeft: '0.4em' },
|
||||
oninput: (i) => {
|
||||
this.batchCount = i.target.value
|
||||
/* Even though an <input> element with a type of range logically represents a number (since
|
||||
it's used for numeric input), the value it holds is still treated as a string in HTML and
|
||||
JavaScript. This behavior is consistent across all <input> elements regardless of their type
|
||||
(like text, number, or range), where the .value property is always a string. */
|
||||
;(
|
||||
document.getElementById(
|
||||
'batchCountInputRange'
|
||||
) as HTMLInputElement
|
||||
).value = this.batchCount.toString()
|
||||
}
|
||||
}),
|
||||
$el('input', {
|
||||
id: 'batchCountInputRange',
|
||||
type: 'range',
|
||||
min: '1',
|
||||
max: '100',
|
||||
value: this.batchCount,
|
||||
oninput: (i) => {
|
||||
this.batchCount = i.srcElement.value
|
||||
// Note
|
||||
;(
|
||||
document.getElementById(
|
||||
'batchCountInputNumber'
|
||||
) as HTMLInputElement
|
||||
).value = i.srcElement.value
|
||||
}
|
||||
})
|
||||
]),
|
||||
$el('div', [
|
||||
$el('label', {
|
||||
for: 'autoQueueCheckbox',
|
||||
innerHTML: 'Auto Queue'
|
||||
}),
|
||||
$el('input', {
|
||||
id: 'autoQueueCheckbox',
|
||||
type: 'checkbox',
|
||||
checked: false,
|
||||
title: 'Automatically queue prompt when the queue size hits 0',
|
||||
onchange: (e) => {
|
||||
this.autoQueueEnabled = e.target.checked
|
||||
autoQueueModeEl.style.display = this.autoQueueEnabled
|
||||
? ''
|
||||
: 'none'
|
||||
}
|
||||
}),
|
||||
autoQueueModeEl
|
||||
])
|
||||
]
|
||||
),
|
||||
$el('div.comfy-menu-btns', [
|
||||
$el('button', {
|
||||
id: 'queue-front-button',
|
||||
textContent: 'Queue Front',
|
||||
onclick: () => app.queuePrompt(-1, this.batchCount)
|
||||
}),
|
||||
$el('button', {
|
||||
$: (b) => (this.queue.button = b as HTMLButtonElement),
|
||||
id: 'comfy-view-queue-button',
|
||||
textContent: 'View Queue',
|
||||
onclick: () => {
|
||||
this.history.hide()
|
||||
this.queue.toggle()
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
$: (b) => (this.history.button = b as HTMLButtonElement),
|
||||
id: 'comfy-view-history-button',
|
||||
textContent: 'View History',
|
||||
onclick: () => {
|
||||
this.queue.hide()
|
||||
this.history.toggle()
|
||||
}
|
||||
})
|
||||
]),
|
||||
this.queue.element,
|
||||
this.history.element,
|
||||
;(
|
||||
document.getElementById(
|
||||
'batchCountInputRange'
|
||||
) as HTMLInputElement
|
||||
).value = this.batchCount.toString()
|
||||
}
|
||||
}),
|
||||
$el('input', {
|
||||
id: 'batchCountInputRange',
|
||||
type: 'range',
|
||||
min: '1',
|
||||
max: '100',
|
||||
value: this.batchCount,
|
||||
oninput: (i) => {
|
||||
this.batchCount = i.srcElement.value
|
||||
// Note
|
||||
;(
|
||||
document.getElementById(
|
||||
'batchCountInputNumber'
|
||||
) as HTMLInputElement
|
||||
).value = i.srcElement.value
|
||||
}
|
||||
})
|
||||
]),
|
||||
$el('div', [
|
||||
$el('label', {
|
||||
for: 'autoQueueCheckbox',
|
||||
innerHTML: 'Auto Queue'
|
||||
}),
|
||||
$el('input', {
|
||||
id: 'autoQueueCheckbox',
|
||||
type: 'checkbox',
|
||||
checked: false,
|
||||
title: 'Automatically queue prompt when the queue size hits 0',
|
||||
onchange: (e) => {
|
||||
this.autoQueueEnabled = e.target.checked
|
||||
autoQueueModeEl.style.display = this.autoQueueEnabled
|
||||
? ''
|
||||
: 'none'
|
||||
}
|
||||
}),
|
||||
autoQueueModeEl
|
||||
])
|
||||
]
|
||||
),
|
||||
$el('div.comfy-menu-btns', [
|
||||
$el('button', {
|
||||
id: 'comfy-save-button',
|
||||
textContent: 'Save',
|
||||
id: 'queue-front-button',
|
||||
textContent: 'Queue Front',
|
||||
onclick: () => app.queuePrompt(-1, this.batchCount)
|
||||
}),
|
||||
$el('button', {
|
||||
$: (b) => (this.queue.button = b as HTMLButtonElement),
|
||||
id: 'comfy-view-queue-button',
|
||||
textContent: 'View Queue',
|
||||
onclick: () => {
|
||||
useCommandStore().execute('Comfy.ExportWorkflow')
|
||||
this.history.hide()
|
||||
this.queue.toggle()
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-dev-save-api-button',
|
||||
textContent: 'Save (API Format)',
|
||||
style: { width: '100%', display: 'none' },
|
||||
$: (b) => (this.history.button = b as HTMLButtonElement),
|
||||
id: 'comfy-view-history-button',
|
||||
textContent: 'View History',
|
||||
onclick: () => {
|
||||
useCommandStore().execute('Comfy.ExportWorkflowAPI')
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-load-button',
|
||||
textContent: 'Load',
|
||||
onclick: () => fileInput.click()
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-refresh-button',
|
||||
textContent: 'Refresh',
|
||||
onclick: () => app.refreshComboInNodes()
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-clipspace-button',
|
||||
textContent: 'Clipspace',
|
||||
onclick: () => app.openClipspace()
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-clear-button',
|
||||
textContent: 'Clear',
|
||||
onclick: () => {
|
||||
if (
|
||||
!useSettingStore().get('Comfy.ConfirmClear') ||
|
||||
confirm('Clear workflow?')
|
||||
) {
|
||||
app.clean()
|
||||
app.graph.clear()
|
||||
app.resetView()
|
||||
api.dispatchCustomEvent('graphCleared')
|
||||
}
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-load-default-button',
|
||||
textContent: 'Load Default',
|
||||
onclick: async () => {
|
||||
if (
|
||||
!useSettingStore().get('Comfy.ConfirmClear') ||
|
||||
confirm('Load default workflow?')
|
||||
) {
|
||||
app.resetView()
|
||||
await app.loadGraphData()
|
||||
}
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-reset-view-button',
|
||||
textContent: 'Reset View',
|
||||
onclick: async () => {
|
||||
app.resetView()
|
||||
this.queue.hide()
|
||||
this.history.toggle()
|
||||
}
|
||||
})
|
||||
]
|
||||
) as HTMLDivElement
|
||||
]),
|
||||
this.queue.element,
|
||||
this.history.element,
|
||||
$el('button', {
|
||||
id: 'comfy-save-button',
|
||||
textContent: 'Save',
|
||||
onclick: () => {
|
||||
useCommandStore().execute('Comfy.ExportWorkflow')
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-dev-save-api-button',
|
||||
textContent: 'Save (API Format)',
|
||||
style: { width: '100%', display: 'none' },
|
||||
onclick: () => {
|
||||
useCommandStore().execute('Comfy.ExportWorkflowAPI')
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-load-button',
|
||||
textContent: 'Load',
|
||||
onclick: () => fileInput.click()
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-refresh-button',
|
||||
textContent: 'Refresh',
|
||||
onclick: () => app.refreshComboInNodes()
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-clipspace-button',
|
||||
textContent: 'Clipspace',
|
||||
onclick: () => app.openClipspace()
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-clear-button',
|
||||
textContent: 'Clear',
|
||||
onclick: () => {
|
||||
if (
|
||||
!useSettingStore().get('Comfy.ConfirmClear') ||
|
||||
confirm('Clear workflow?')
|
||||
) {
|
||||
app.clean()
|
||||
app.graph.clear()
|
||||
app.resetView()
|
||||
api.dispatchCustomEvent('graphCleared')
|
||||
}
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-load-default-button',
|
||||
textContent: 'Load Default',
|
||||
onclick: async () => {
|
||||
if (
|
||||
!useSettingStore().get('Comfy.ConfirmClear') ||
|
||||
confirm('Load default workflow?')
|
||||
) {
|
||||
app.resetView()
|
||||
await app.loadGraphData()
|
||||
}
|
||||
}
|
||||
}),
|
||||
$el('button', {
|
||||
id: 'comfy-reset-view-button',
|
||||
textContent: 'Reset View',
|
||||
onclick: async () => {
|
||||
app.resetView()
|
||||
}
|
||||
})
|
||||
]) as HTMLDivElement
|
||||
// Hide by default on construction so it does not interfere with other views.
|
||||
this.menuContainer.style.display = 'none'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<BaseViewTemplate>
|
||||
<BaseViewTemplate class="app-drag">
|
||||
<div
|
||||
class="max-w-screen-sm flex flex-col gap-8 p-8 bg-[url('/assets/images/Git-Logo-White.svg')] bg-no-repeat bg-right-top bg-origin-padding"
|
||||
class="no-drag max-w-screen-sm flex flex-col gap-8 p-8 bg-[url('/assets/images/Git-Logo-White.svg')] bg-no-repeat bg-right-top bg-origin-padding"
|
||||
>
|
||||
<!-- Header -->
|
||||
<h1 class="mt-24 text-4xl font-bold text-red-500">
|
||||
|
||||
@@ -66,11 +66,23 @@ watch(
|
||||
document.body.classList.add(DARK_THEME_CLASS)
|
||||
}
|
||||
|
||||
// Native window control theme
|
||||
if (isElectron()) {
|
||||
electronAPI().changeTheme({
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
symbolColor: newTheme.colors.comfy_base['input-text']
|
||||
})
|
||||
const cssVars = newTheme.colors.comfy_base
|
||||
// Allow OS to set matching hover colour
|
||||
const color = setZeroAlpha(cssVars['comfy-menu-bg'])
|
||||
const symbolColor = cssVars['input-text']
|
||||
electronAPI().changeTheme({ color, symbolColor })
|
||||
}
|
||||
|
||||
function setZeroAlpha(color: string) {
|
||||
if (!color.startsWith('#')) return color
|
||||
|
||||
if (color.length === 4) {
|
||||
const [_, r, g, b] = color
|
||||
return `#${r}${r}${g}${g}${b}${b}00`
|
||||
}
|
||||
return `#${color.substring(1, 7)}00`
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark>
|
||||
<BaseViewTemplate dark class="app-drag">
|
||||
<!-- h-full to make sure the stepper does not layout shift between steps
|
||||
as for each step the stepper height is different. Inherit the center element
|
||||
placement from BaseViewTemplate would cause layout shift. -->
|
||||
<Stepper
|
||||
class="h-full p-8 2xl:p-16"
|
||||
class="no-drag h-full p-8 2xl:p-16"
|
||||
value="0"
|
||||
@update:value="setHighestStep"
|
||||
>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark>
|
||||
<BaseViewTemplate dark class="app-drag">
|
||||
<!-- Installation Path Section -->
|
||||
<div
|
||||
class="comfy-installer grow flex flex-col gap-4 text-neutral-300 max-w-110"
|
||||
class="no-drag comfy-installer grow flex flex-col gap-4 text-neutral-300 max-w-110"
|
||||
>
|
||||
<h2 class="text-2xl font-semibold text-neutral-100">
|
||||
{{ $t('install.manualConfiguration.title') }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<BaseViewTemplate>
|
||||
<div class="sad-container">
|
||||
<BaseViewTemplate class="app-drag">
|
||||
<div class="no-drag sad-container">
|
||||
<!-- Right side image -->
|
||||
<img
|
||||
class="sad-girl"
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark class="flex-col">
|
||||
<div class="flex flex-col w-full h-full items-center">
|
||||
<h2 class="text-2xl font-bold">
|
||||
{{ t(`serverStart.process.${status}`) }}
|
||||
<span v-if="status === ProgressStatus.ERROR">
|
||||
v{{ electronVersion }}
|
||||
</span>
|
||||
</h2>
|
||||
<div
|
||||
v-if="status === ProgressStatus.ERROR"
|
||||
class="flex flex-col items-center gap-4"
|
||||
>
|
||||
<div class="flex items-center my-4 gap-2">
|
||||
<Button
|
||||
icon="pi pi-flag"
|
||||
severity="secondary"
|
||||
:label="t('serverStart.reportIssue')"
|
||||
@click="reportIssue"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-file"
|
||||
severity="secondary"
|
||||
:label="t('serverStart.openLogs')"
|
||||
@click="openLogs"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
:label="t('serverStart.reinstall')"
|
||||
@click="reinstall"
|
||||
/>
|
||||
</div>
|
||||
<BaseViewTemplate dark class="app-drag flex-col">
|
||||
<h2 class="text-2xl font-bold">
|
||||
{{ t(`serverStart.process.${status}`) }}
|
||||
<span v-if="status === ProgressStatus.ERROR">
|
||||
v{{ electronVersion }}
|
||||
</span>
|
||||
</h2>
|
||||
<div
|
||||
v-if="status === ProgressStatus.ERROR"
|
||||
class="no-drag flex flex-col items-center gap-4"
|
||||
>
|
||||
<div class="flex items-center my-4 gap-2">
|
||||
<Button
|
||||
v-if="!terminalVisible"
|
||||
icon="pi pi-search"
|
||||
icon="pi pi-flag"
|
||||
severity="secondary"
|
||||
:label="t('serverStart.showTerminal')"
|
||||
@click="terminalVisible = true"
|
||||
:label="t('serverStart.reportIssue')"
|
||||
@click="reportIssue"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-file"
|
||||
severity="secondary"
|
||||
:label="t('serverStart.openLogs')"
|
||||
@click="openLogs"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-refresh"
|
||||
:label="t('serverStart.reinstall')"
|
||||
@click="reinstall"
|
||||
/>
|
||||
</div>
|
||||
<BaseTerminal v-show="terminalVisible" @created="terminalCreated" />
|
||||
<Button
|
||||
v-if="!terminalVisible"
|
||||
icon="pi pi-search"
|
||||
severity="secondary"
|
||||
:label="t('serverStart.showTerminal')"
|
||||
@click="terminalVisible = true"
|
||||
/>
|
||||
</div>
|
||||
<BaseTerminal
|
||||
v-show="terminalVisible"
|
||||
class="no-drag"
|
||||
@created="terminalCreated"
|
||||
/>
|
||||
</BaseViewTemplate>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark>
|
||||
<BaseViewTemplate dark class="app-drag">
|
||||
<main
|
||||
id="comfy-user-selection"
|
||||
class="min-w-84 relative rounded-lg bg-[var(--comfy-menu-bg)] p-5 px-10 shadow-lg"
|
||||
class="no-drag min-w-84 relative rounded-lg bg-[var(--comfy-menu-bg)] p-5 px-10 shadow-lg"
|
||||
>
|
||||
<h1 class="my-2.5 mb-7 font-normal">ComfyUI</h1>
|
||||
<div class="flex w-full flex-col items-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<BaseViewTemplate dark>
|
||||
<div class="flex flex-col items-center justify-center gap-8 p-8">
|
||||
<BaseViewTemplate dark class="app-drag">
|
||||
<div class="no-drag flex flex-col items-center justify-center gap-8 p-8">
|
||||
<!-- Header -->
|
||||
<h1 class="animated-gradient-text text-glow select-none">
|
||||
{{ $t('welcome.title') }}
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
class="font-sans w-screen h-screen flex flex-col pointer-events-auto"
|
||||
class="font-sans w-screen h-screen flex items-center justify-center pointer-events-auto overflow-auto"
|
||||
:class="[
|
||||
props.dark
|
||||
? 'text-neutral-300 bg-neutral-900 dark-theme'
|
||||
: 'text-neutral-900 bg-neutral-300'
|
||||
]"
|
||||
>
|
||||
<!-- Virtual top menu for native window (drag handle) -->
|
||||
<div
|
||||
v-show="isNativeWindow"
|
||||
ref="topMenuRef"
|
||||
class="app-drag w-full h-[var(--comfy-topbar-height)]"
|
||||
/>
|
||||
<div
|
||||
class="flex-grow w-full flex items-center justify-center overflow-auto"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import { electronAPI, isElectron } from '@/utils/envUtil'
|
||||
|
||||
@@ -36,28 +26,18 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const darkTheme = {
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
color: 'rgba(23, 23, 23, 0)',
|
||||
symbolColor: '#d4d4d4'
|
||||
}
|
||||
|
||||
const lightTheme = {
|
||||
color: 'rgba(0, 0, 0, 0)',
|
||||
color: 'rgba(212, 212, 212, 0)',
|
||||
symbolColor: '#171717'
|
||||
}
|
||||
|
||||
const topMenuRef = ref<HTMLDivElement | null>(null)
|
||||
const isNativeWindow = ref(false)
|
||||
onMounted(async () => {
|
||||
onMounted(() => {
|
||||
if (isElectron()) {
|
||||
const windowStyle = await electronAPI().Config.getWindowStyle()
|
||||
isNativeWindow.value = windowStyle === 'custom'
|
||||
|
||||
await nextTick()
|
||||
|
||||
electronAPI().changeTheme({
|
||||
...(props.dark ? darkTheme : lightTheme),
|
||||
height: topMenuRef.value.getBoundingClientRect().height
|
||||
})
|
||||
electronAPI().changeTheme(props.dark ? darkTheme : lightTheme)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user