merge main into rh-test

This commit is contained in:
bymyself
2025-09-28 15:33:29 -07:00
parent 1c0f151d02
commit ff0c15b119
1317 changed files with 85439 additions and 18373 deletions

View File

@@ -28,29 +28,7 @@
@show="onMenuShow"
>
<template #item="{ item, props }">
<div
v-if="item.key === 'theme'"
class="flex items-center gap-4 px-4 py-5"
@click.stop.prevent
>
{{ item.label }}
<SelectButton
:options="[darkLabel, lightLabel]"
:model-value="activeTheme"
@click.stop.prevent
@update:model-value="onThemeChange"
>
<template #option="{ option }">
<div class="flex items-center gap-2">
<i v-if="option === lightLabel" class="pi pi-sun" />
<i v-if="option === darkLabel" class="pi pi-moon" />
<span>{{ option }}</span>
</div>
</template>
</SelectButton>
</div>
<a
v-else
class="p-menubar-item-link px-4 py-2"
v-bind="props.action"
:href="item.url"
@@ -95,7 +73,6 @@
<script setup lang="ts">
import type { MenuItem } from 'primevue/menuitem'
import SelectButton from 'primevue/selectbutton'
import TieredMenu, {
type TieredMenuMethods,
type TieredMenuState
@@ -104,27 +81,30 @@ import { computed, nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue'
import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue'
import { useDialogService } from '@/services/dialogService'
import { useAboutPanelStore } from '@/stores/aboutPanelStore'
import SettingDialogContent from '@/platform/settings/components/SettingDialogContent.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useColorPaletteService } from '@/services/colorPaletteService'
import { useCommandStore } from '@/stores/commandStore'
import { useDialogStore } from '@/stores/dialogStore'
import { useMenuItemStore } from '@/stores/menuItemStore'
import { useSettingStore } from '@/stores/settingStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { showNativeSystemMenu } from '@/utils/envUtil'
import { normalizeI18nKey } from '@/utils/formatUtil'
import { whileMouseDown } from '@/utils/mouseDownUtil'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
const colorPaletteStore = useColorPaletteStore()
const colorPaletteService = useColorPaletteService()
const menuItemsStore = useMenuItemStore()
const commandStore = useCommandStore()
const dialogStore = useDialogStore()
const aboutPanelStore = useAboutPanelStore()
const settingStore = useSettingStore()
const { t } = useI18n()
const managerState = useManagerState()
const menuRef = ref<
({ dirty: boolean } & TieredMenuMethods & TieredMenuState) | null
>(null)
@@ -157,31 +137,33 @@ const showSettings = (defaultPanel?: string) => {
})
}
// Temporary duplicated from LoadWorkflowWarning.vue
// Determines if ComfyUI-Manager is installed by checking for its badge in the about panel
// This allows us to conditionally show the Manager button only when the extension is available
// TODO: Remove this check when Manager functionality is fully migrated into core
const isManagerInstalled = computed(() => {
return aboutPanelStore.badges.some(
(badge) =>
badge.label.includes('ComfyUI-Manager') ||
badge.url.includes('ComfyUI-Manager')
)
})
const showManageExtensions = () => {
if (isManagerInstalled.value) {
useDialogService().showManagerDialog()
} else {
showSettings('extension')
}
const showManageExtensions = async () => {
await managerState.openManager({
initialTab: ManagerTab.All,
showToastOnLegacyError: false
})
}
const extraMenuItems = computed<MenuItem[]>(() => [
const themeMenuItems = computed(() => {
return colorPaletteStore.palettes.map((palette) => ({
key: `theme-${palette.id}`,
label: palette.name,
parentPath: 'theme',
comfyCommand: {
active: () => colorPaletteStore.activePaletteId === palette.id
},
command: async () => {
await colorPaletteService.loadColorPalette(palette.id)
}
}))
})
const extraMenuItems = computed(() => [
{ separator: true },
{
key: 'theme',
label: t('menu.theme')
label: t('menu.theme'),
items: themeMenuItems.value
},
{ separator: true },
{
@@ -204,19 +186,6 @@ const extraMenuItems = computed<MenuItem[]>(() => [
}
])
const lightLabel = computed(() => t('menu.light'))
const darkLabel = computed(() => t('menu.dark'))
const activeTheme = computed(() => {
return colorPaletteStore.completedActivePalette.light_theme
? lightLabel.value
: darkLabel.value
})
const onThemeChange = async () => {
await commandStore.execute('Comfy.ToggleTheme')
}
const translatedItems = computed(() => {
const items = menuItemsStore.menuItems.map(translateMenuItem)
let helpIndex = items.findIndex((item) => item.key === 'Help')
@@ -301,11 +270,18 @@ const handleItemClick = (item: MenuItem, event: MouseEvent) => {
}
const hasActiveStateSiblings = (item: MenuItem): boolean => {
return menuItemsStore.menuItemHasActiveStateChildren[item.parentPath]
// Check if this item has siblings with active state (either from store or theme items)
return (
item.parentPath &&
(item.parentPath === 'theme' ||
menuItemsStore.menuItemHasActiveStateChildren[item.parentPath])
)
}
</script>
<style scoped>
@reference '../../assets/css/style.css';
:deep(.p-menubar-submenu.dropdown-direction-up) {
@apply top-auto bottom-full flex-col-reverse;
}
@@ -323,12 +299,34 @@ const hasActiveStateSiblings = (item: MenuItem): boolean => {
</style>
<style>
.comfy-command-menu {
--p-tieredmenu-item-focus-background: color-mix(
in srgb,
var(--fg-color) 15%,
transparent
);
--p-tieredmenu-item-active-background: color-mix(
in srgb,
var(--fg-color) 10%,
transparent
);
}
.comfy-command-menu ul {
background-color: var(--comfy-menu-secondary-bg) !important;
}
.comfy-command-menu-top .p-tieredmenu-submenu {
left: calc(100% + 15px) !important;
top: -4px !important;
}
@media (max-height: 700px) {
.comfy-command-menu .p-tieredmenu-submenu {
@apply absolute max-h-[90vh] overflow-y-auto;
}
/* Help (last) submenu upward offset in compact mode */
.p-tieredmenu-root-list
> .p-tieredmenu-item:last-of-type
.p-tieredmenu-submenu {
top: -188px !important;
}
}
</style>

View File

@@ -1,10 +1,11 @@
import { VueWrapper, mount } from '@vue/test-utils'
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import Button from 'primevue/button'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { h } from 'vue'
import { createI18n } from 'vue-i18n'
import enMessages from '@/locales/en/main.json'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import CurrentUserButton from './CurrentUserButton.vue'

View File

@@ -9,9 +9,7 @@
aria-label="user profile"
@click="popover?.toggle($event)"
>
<div
class="flex items-center rounded-full bg-[var(--p-content-background)]"
>
<div class="flex items-center rounded-full bg-(--p-content-background)">
<UserAvatar :photo-url="photoURL" />
<i class="pi pi-chevron-down px-1" :style="{ fontSize: '0.5rem' }" />

View File

@@ -1,10 +1,11 @@
import { VueWrapper, mount } from '@vue/test-utils'
import type { VueWrapper } from '@vue/test-utils'
import { mount } from '@vue/test-utils'
import Button from 'primevue/button'
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { h } from 'vue'
import { createI18n } from 'vue-i18n'
import enMessages from '@/locales/en/main.json'
import enMessages from '@/locales/en/main.json' with { type: 'json' }
import CurrentUserPopover from './CurrentUserPopover.vue'
@@ -41,11 +42,13 @@ afterAll(() => {
})
// Mock the useCurrentUser composable
const mockHandleSignOut = vi.fn()
vi.mock('@/composables/auth/useCurrentUser', () => ({
useCurrentUser: vi.fn(() => ({
userPhotoUrl: 'https://example.com/avatar.jpg',
userDisplayName: 'Test User',
userEmail: 'test@example.com'
userEmail: 'test@example.com',
handleSignOut: mockHandleSignOut
}))
}))
@@ -155,8 +158,8 @@ describe('CurrentUserPopover', () => {
// Click the logout button
await logoutButton.trigger('click')
// Verify logout was called
expect(mockLogout).toHaveBeenCalled()
// Verify handleSignOut was called
expect(mockHandleSignOut).toHaveBeenCalled()
// Verify close event was emitted
expect(wrapper.emitted('close')).toBeTruthy()

View File

@@ -8,7 +8,7 @@
class="mb-3"
:photo-url="userPhotoUrl"
:pt:icon:class="{
'!text-2xl': !userPhotoUrl
'text-2xl!': !userPhotoUrl
}"
size="large"
/>
@@ -88,7 +88,8 @@ const emit = defineEmits<{
close: []
}>()
const { userDisplayName, userEmail, userPhotoUrl } = useCurrentUser()
const { userDisplayName, userEmail, userPhotoUrl, handleSignOut } =
useCurrentUser()
const authActions = useFirebaseAuthActions()
const dialogService = useDialogService()
@@ -103,7 +104,7 @@ const handleTopUp = () => {
}
const handleLogout = async () => {
await authActions.logout()
await handleSignOut()
emit('close')
}

View File

@@ -2,7 +2,7 @@
<div>
<div
v-show="showTopMenu && workflowTabsPosition === 'Topbar'"
class="w-full flex content-end z-[1001] h-[38px]"
class="w-full flex content-end z-1001 h-[38px]"
style="background: var(--border-color)"
>
<WorkflowTabs />
@@ -14,19 +14,19 @@
:class="{ dropzone: isDropZone, 'dropzone-active': isDroppable }"
>
<CommandMenubar />
<div class="flex-grow min-w-0 app-drag h-full"></div>
<div class="grow min-w-0 app-drag h-full"></div>
<div
ref="menuRight"
class="comfyui-menu-right flex-shrink-1 overflow-auto"
/>
<Actionbar />
<CurrentUserButton class="flex-shrink-0" />
<CurrentUserButton class="shrink-0" />
</div>
<!-- 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)]"
class="fixed top-0 left-0 app-drag w-full h-(--comfy-topbar-height)"
/>
</div>
</template>
@@ -39,8 +39,8 @@ import Actionbar from '@/components/actionbar/ComfyActionbar.vue'
import CommandMenubar from '@/components/topbar/CommandMenubar.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { app } from '@/scripts/app'
import { useSettingStore } from '@/stores/settingStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { electronAPI, isElectron, isNativeWindow } from '@/utils/envUtil'

View File

@@ -23,8 +23,8 @@ import Button from 'primevue/button'
import Menu from 'primevue/menu'
import { computed, ref } from 'vue'
import { useWorkflowService } from '@/services/workflowService'
import type { ComfyWorkflow } from '@/stores/workflowStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
const props = defineProps<{
workflows: ComfyWorkflow[]

View File

@@ -1,7 +1,7 @@
<template>
<div
ref="workflowTabRef"
class="flex p-2 gap-2 workflow-tab"
class="flex p-2 gap-2 workflow-tab group"
v-bind="$attrs"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@@ -11,9 +11,13 @@
{{ workflowOption.workflow.filename }}
</span>
<div class="relative">
<span v-if="shouldShowStatusIndicator" class="status-indicator"></span>
<span
v-if="shouldShowStatusIndicator"
class="group-hover:hidden absolute font-bold text-2xl top-1/2 left-1/2 -translate-1/2 z-10 bg-(--comfy-menu-secondary-bg) w-4"
></span
>
<Button
class="close-button p-0 w-auto"
class="close-button p-0 w-auto invisible"
icon="pi pi-times"
text
severity="secondary"
@@ -40,11 +44,11 @@ import {
usePragmaticDraggable,
usePragmaticDroppable
} from '@/composables/usePragmaticDragAndDrop'
import { useWorkflowThumbnail } from '@/renderer/thumbnail/composables/useWorkflowThumbnail'
import { useWorkflowService } from '@/services/workflowService'
import { useSettingStore } from '@/stores/settingStore'
import { ComfyWorkflow } from '@/stores/workflowStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import WorkflowTabPopover from './WorkflowTabPopover.vue'
@@ -174,16 +178,6 @@ onUnmounted(() => {
})
</script>
<style scoped>
.status-indicator {
@apply absolute font-bold;
font-size: 1.5rem;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
<style>
.p-tooltip.workflow-tab-tooltip {
z-index: 1200 !important;

View File

@@ -41,7 +41,7 @@
import Popover from 'primevue/popover'
import { computed, nextTick, ref, toRefs, useId } from 'vue'
import { useSettingStore } from '@/stores/settingStore'
import { useSettingStore } from '@/platform/settings/settingStore'
const POPOVER_WIDTH = 250
@@ -169,6 +169,8 @@ defineExpose({
</script>
<style scoped>
@reference '../../assets/css/style.css';
.workflow-preview-content {
@apply flex flex-col rounded-xl overflow-hidden;
max-width: var(--popover-width);
@@ -204,6 +206,8 @@ defineExpose({
</style>
<style>
@reference '../../assets/css/style.css';
.workflow-popover-fade {
--p-popover-background: transparent;
--p-popover-content-padding: 0;

View File

@@ -55,7 +55,7 @@
/>
<Button
v-tooltip="{ value: $t('sideToolbar.newBlankWorkflow'), showDelay: 300 }"
class="new-blank-workflow-button flex-shrink-0 no-drag rounded-none"
class="new-blank-workflow-button shrink-0 no-drag rounded-none"
icon="pi pi-plus"
text
severity="secondary"
@@ -65,7 +65,7 @@
<ContextMenu ref="menu" :model="contextMenuItems" />
<div
v-if="menuSetting !== 'Bottom' && isDesktop"
class="window-actions-spacer flex-shrink-0 app-drag"
class="window-actions-spacer shrink-0 app-drag"
/>
</div>
</template>
@@ -81,11 +81,12 @@ import { useI18n } from 'vue-i18n'
import WorkflowTab from '@/components/topbar/WorkflowTab.vue'
import { useOverflowObserver } from '@/composables/element/useOverflowObserver'
import { useWorkflowService } from '@/services/workflowService'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowBookmarkStore } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useCommandStore } from '@/stores/commandStore'
import { useSettingStore } from '@/stores/settingStore'
import { ComfyWorkflow, useWorkflowBookmarkStore } from '@/stores/workflowStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { isElectron } from '@/utils/envUtil'
import { whileMouseDown } from '@/utils/mouseDownUtil'
@@ -302,12 +303,14 @@ onUpdated(() => {
</script>
<style scoped>
@reference '../../assets/css/style.css';
.workflow-tabs-container {
background-color: var(--comfy-menu-secondary-bg);
}
:deep(.p-togglebutton) {
@apply p-0 bg-transparent rounded-none flex-shrink relative border-0 border-r border-solid;
@apply p-0 bg-transparent rounded-none shrink relative border-0 border-r border-solid;
border-right-color: var(--border-color);
min-width: 90px;
}
@@ -355,14 +358,6 @@ onUpdated(() => {
@apply visible;
}
:deep(.p-togglebutton:hover) .status-indicator {
@apply hidden;
}
:deep(.p-togglebutton) .close-button {
@apply invisible;
}
:deep(.p-scrollpanel-content) {
@apply h-full;
}