From e827138f6fcfb39f170a4b92caa2ed1f614b6ebf Mon Sep 17 00:00:00 2001 From: Arjan Singh <1598641+arjansingh@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:37:27 -0700 Subject: [PATCH] feat(frontend): update cloud branch 2025-10-16 (#6096) ## Summary Updates with cloud specific features merged into `main`. Notable changes include the `DISTRIBUTION=cloud` changes. Will also be changing cloud build workflow to build with that flag in https://github.com/Comfy-Org/cloud/pull/1043 ## Changes - bb61d9822 feat: AssetCard tweaks (#6085) - 05f73523f fix terminal style (#6056) - d5fa22168 Add distribution detection pattern (#6028) - 6c36aaa1d feat: Improve MediaAssetCard video controls and add gallery view (#6065) - 6944ef0a2 fix Cloudbadge (#6063) - 6764f8dab Badge for cloud environment (#6048) --------- Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Co-authored-by: Jin Yi Co-authored-by: Claude Co-authored-by: Christian Byrne --- packages/design-system/src/css/style.css | 4 + .../helpcenter/HelpCenterMenuContent.vue | 39 ++-- src/components/topbar/TopMenubar.vue | 8 +- src/components/topbar/TopbarBadge.vue | 20 ++ src/components/topbar/TopbarBadges.vue | 17 ++ .../bottomPanelTabs/useTerminal.ts | 4 +- src/extensions/core/cloudBadge.ts | 16 ++ src/extensions/core/index.ts | 6 + src/locales/en/main.json | 3 +- .../assets/components/AssetCard.stories.ts | 45 +++++ src/platform/assets/components/AssetCard.vue | 77 +++++--- .../components/MediaAssetCard.stories.ts | 23 ++- .../assets/components/MediaAssetCard.vue | 107 ++++++++--- .../assets/components/MediaAssetMoreMenu.vue | 9 +- .../assets/components/MediaVideoTop.vue | 27 ++- .../composables/useMediaAssetActions.ts | 5 - .../useMediaAssetGalleryStore.test.ts | 179 ++++++++++++++++++ .../composables/useMediaAssetGalleryStore.ts | 47 +++++ src/platform/assets/services/assetService.ts | 2 +- src/platform/distribution/types.ts | 20 ++ src/stores/topbarBadgeStore.ts | 18 ++ src/types/comfy.ts | 12 ++ tests-ui/tests/services/assetService.test.ts | 8 +- tsconfig.json | 1 + vite.config.mts | 8 +- vitest.setup.ts | 1 + 26 files changed, 609 insertions(+), 97 deletions(-) create mode 100644 src/components/topbar/TopbarBadge.vue create mode 100644 src/components/topbar/TopbarBadges.vue create mode 100644 src/extensions/core/cloudBadge.ts create mode 100644 src/platform/assets/composables/useMediaAssetGalleryStore.test.ts create mode 100644 src/platform/assets/composables/useMediaAssetGalleryStore.ts create mode 100644 src/platform/distribution/types.ts create mode 100644 src/stores/topbarBadgeStore.ts diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index 5208d4bbb..2ba773b8d 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -85,6 +85,10 @@ --color-bypass: #6a246a; --color-error: #962a2a; + --color-comfy-menu-secondary: var(--comfy-menu-secondary-bg); + --text-xxxs: 0.5625rem; + --text-xxxs--line-height: calc(1 / 0.5625); + --color-blue-selection: rgb(from var(--color-blue-100) r g b / 0.3); --color-node-hover-100: rgb(from var(--color-charcoal-100) r g b/ 0.15); --color-node-hover-200: rgb(from var(--color-charcoal-100) r g b/ 0.1); diff --git a/src/components/helpcenter/HelpCenterMenuContent.vue b/src/components/helpcenter/HelpCenterMenuContent.vue index a58cb4a76..bfa1cbc96 100644 --- a/src/components/helpcenter/HelpCenterMenuContent.vue +++ b/src/components/helpcenter/HelpCenterMenuContent.vue @@ -135,6 +135,7 @@ import type { CSSProperties, Component } from 'vue' import { useI18n } from 'vue-i18n' import PuzzleIcon from '@/components/icons/PuzzleIcon.vue' +import { isCloud } from '@/platform/distribution/types' import { useSettingStore } from '@/platform/settings/settingStore' import type { ReleaseNote } from '@/platform/updates/common/releaseService' import { useReleaseStore } from '@/platform/updates/common/releaseStore' @@ -265,7 +266,7 @@ const moreMenuItem = computed(() => ) const menuItems = computed(() => { - return [ + const items: MenuItem[] = [ { key: 'docs', type: 'item', @@ -305,8 +306,12 @@ const menuItems = computed(() => { void commandStore.execute('Comfy.ContactSupport') emit('close') } - }, - { + } + ] + + // Extension manager - only in non-cloud distributions + if (!isCloud) { + items.push({ key: 'manager', type: 'item', icon: PuzzleIcon, @@ -319,17 +324,20 @@ const menuItems = computed(() => { }) emit('close') } - }, - { - key: 'more', - type: 'item', - icon: '', - label: t('helpCenter.more'), - visible: hasVisibleMoreItems.value, - action: () => {}, // No action for more item - items: moreItems.value - } - ] + }) + } + + items.push({ + key: 'more', + type: 'item', + icon: '', + label: t('helpCenter.more'), + visible: hasVisibleMoreItems.value, + action: () => {}, // No action for more item + items: moreItems.value + }) + + return items }) // Utility Functions @@ -420,6 +428,9 @@ const formatReleaseDate = (dateString?: string): string => { } const shouldShowUpdateButton = (release: ReleaseNote): boolean => { + // Hide update buttons in cloud distribution + if (isCloud) return false + return ( releaseStore.shouldShowUpdateButton && release === releaseStore.recentReleases[0] diff --git a/src/components/topbar/TopMenubar.vue b/src/components/topbar/TopMenubar.vue index d5909766e..8e9bf015f 100644 --- a/src/components/topbar/TopMenubar.vue +++ b/src/components/topbar/TopMenubar.vue @@ -2,15 +2,16 @@
+
@@ -44,9 +45,10 @@ import { app } from '@/scripts/app' import { useWorkspaceStore } from '@/stores/workspaceStore' import { electronAPI, isElectron, isNativeWindow } from '@/utils/envUtil' +import TopbarBadges from './TopbarBadges.vue' + const workspaceState = useWorkspaceStore() const settingStore = useSettingStore() - const menuSetting = computed(() => settingStore.get('Comfy.UseNewMenu')) const betaMenuEnabled = computed(() => menuSetting.value !== 'Disabled') const showTopMenu = computed( diff --git a/src/components/topbar/TopbarBadge.vue b/src/components/topbar/TopbarBadge.vue new file mode 100644 index 000000000..4ec12dffc --- /dev/null +++ b/src/components/topbar/TopbarBadge.vue @@ -0,0 +1,20 @@ + + diff --git a/src/components/topbar/TopbarBadges.vue b/src/components/topbar/TopbarBadges.vue new file mode 100644 index 000000000..381b4d13c --- /dev/null +++ b/src/components/topbar/TopbarBadges.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/composables/bottomPanelTabs/useTerminal.ts b/src/composables/bottomPanelTabs/useTerminal.ts index d353294f0..ed7fb3fa8 100644 --- a/src/composables/bottomPanelTabs/useTerminal.ts +++ b/src/composables/bottomPanelTabs/useTerminal.ts @@ -5,12 +5,14 @@ import { debounce } from 'es-toolkit/compat' import type { Ref } from 'vue' import { markRaw, onMounted, onUnmounted } from 'vue' +import { isDesktop } from '@/platform/distribution/types' + export function useTerminal(element: Ref) { const fitAddon = new FitAddon() const terminal = markRaw( new Terminal({ convertEol: true, - theme: { background: '#171717' } + theme: isDesktop ? { background: '#171717' } : undefined }) ) terminal.loadAddon(fitAddon) diff --git a/src/extensions/core/cloudBadge.ts b/src/extensions/core/cloudBadge.ts new file mode 100644 index 000000000..5f2b569fa --- /dev/null +++ b/src/extensions/core/cloudBadge.ts @@ -0,0 +1,16 @@ +import { t } from '@/i18n' +import { isCloud } from '@/platform/distribution/types' +import { useExtensionService } from '@/services/extensionService' + +useExtensionService().registerExtension({ + name: 'Comfy.CloudBadge', + // Only show badge when running in cloud environment + topbarBadges: isCloud + ? [ + { + label: t('g.beta'), + text: 'Comfy Cloud' + } + ] + : undefined +}) diff --git a/src/extensions/core/index.ts b/src/extensions/core/index.ts index 5354ef4e9..011502f44 100644 --- a/src/extensions/core/index.ts +++ b/src/extensions/core/index.ts @@ -1,3 +1,5 @@ +import { isCloud } from '@/platform/distribution/types' + import './clipspace' import './contextMenuFilter' import './dynamicPrompts' @@ -21,3 +23,7 @@ import './uploadAudio' import './uploadImage' import './webcamCapture' import './widgetInputs' + +if (isCloud) { + import('./cloudBadge') +} diff --git a/src/locales/en/main.json b/src/locales/en/main.json index d9f727603..0f5dda3ba 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -197,7 +197,8 @@ "volume": "Volume", "halfSpeed": "0.5x", "1x": "1x", - "2x": "2x" + "2x": "2x", + "beta": "BETA" }, "manager": { "title": "Custom Nodes Manager", diff --git a/src/platform/assets/components/AssetCard.stories.ts b/src/platform/assets/components/AssetCard.stories.ts index 2b3532a05..2915a219a 100644 --- a/src/platform/assets/components/AssetCard.stories.ts +++ b/src/platform/assets/components/AssetCard.stories.ts @@ -84,6 +84,51 @@ export const NonInteractive: Story = { } } +export const WithPreviewImage: Story = { + args: { + asset: createAssetData({ + preview_url: '/assets/images/comfy-logo-single.svg' + }), + interactive: true + }, + decorators: [ + () => ({ + template: + '
' + }) + ], + parameters: { + docs: { + description: { + story: 'AssetCard with a preview image displayed.' + } + } + } +} + +export const FallbackGradient: Story = { + args: { + asset: createAssetData({ + preview_url: undefined + }), + interactive: true + }, + decorators: [ + () => ({ + template: + '
' + }) + ], + parameters: { + docs: { + description: { + story: + 'AssetCard showing fallback gradient when no preview image is available.' + } + } + } +} + export const EdgeCases: Story = { render: () => ({ components: { AssetCard }, diff --git a/src/platform/assets/components/AssetCard.vue b/src/platform/assets/components/AssetCard.vue index dca0d2d64..dd35b4fc1 100644 --- a/src/platform/assets/components/AssetCard.vue +++ b/src/platform/assets/components/AssetCard.vue @@ -4,38 +4,29 @@ data-component-id="AssetCard" :data-asset-id="asset.id" v-bind="elementProps" - :class=" - cn( - // Base layout and container styles (always applied) - 'rounded-xl overflow-hidden transition-all duration-200', - interactive && 'group', - // Button-specific styles - interactive && [ - 'appearance-none bg-transparent p-0 m-0 font-inherit text-inherit outline-none cursor-pointer text-left', - 'bg-gray-100 dark-theme:bg-charcoal-800', - 'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600', - 'border-none', - 'focus:outline-solid outline-blue-100 outline-4' - ], - // Div-specific styles - !interactive && 'bg-gray-100 dark-theme:bg-charcoal-800' - ) - " + :class="cardClasses" @click="interactive && $emit('select', asset)" @keydown.enter="interactive && $emit('select', asset)" > -
+
+

-import { computed } from 'vue' +import { useImage } from '@vueuse/core' +import { computed, useId } from 'vue' import AssetBadgeGroup from '@/platform/assets/components/AssetBadgeGroup.vue' import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser' @@ -94,13 +87,51 @@ const props = defineProps<{ interactive?: boolean }>() +const titleId = useId() +const descId = useId() + +const { error } = useImage({ + src: props.asset.preview_url ?? '', + alt: props.asset.name +}) + +const shouldShowImage = computed(() => props.asset.preview_url && !error.value) + +const cardClasses = computed(() => { + const base = [ + 'rounded-xl', + 'overflow-hidden', + 'transition-all', + 'duration-200' + ] + + if (!props.interactive) { + return cn(...base, 'bg-gray-100 dark-theme:bg-charcoal-800') + } + + return cn( + ...base, + 'group', + 'appearance-none bg-transparent p-0 m-0', + 'font-inherit text-inherit outline-none cursor-pointer text-left', + 'bg-gray-100 dark-theme:bg-charcoal-800', + 'hover:bg-gray-200 dark-theme:hover:bg-charcoal-600', + 'border-none', + 'focus:outline-solid outline-blue-100 outline-4' + ) +}) + const elementProps = computed(() => props.interactive ? { type: 'button', - 'aria-label': `Select asset ${props.asset.name}` + 'aria-labelledby': titleId, + 'aria-describedby': descId + } + : { + 'aria-labelledby': titleId, + 'aria-describedby': descId } - : {} ) defineEmits<{ diff --git a/src/platform/assets/components/MediaAssetCard.stories.ts b/src/platform/assets/components/MediaAssetCard.stories.ts index fa1d42856..231b86a9b 100644 --- a/src/platform/assets/components/MediaAssetCard.stories.ts +++ b/src/platform/assets/components/MediaAssetCard.stories.ts @@ -1,11 +1,32 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite' +import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue' + +import { useMediaAssetGalleryStore } from '../composables/useMediaAssetGalleryStore' import type { AssetMeta } from '../schemas/mediaAssetSchema' import MediaAssetCard from './MediaAssetCard.vue' const meta: Meta = { - title: 'AssetLibrary/MediaAssetCard', + title: 'Platform/Assets/MediaAssetCard', component: MediaAssetCard, + decorators: [ + () => ({ + components: { ResultGallery }, + setup() { + const galleryStore = useMediaAssetGalleryStore() + return { galleryStore } + }, + template: ` +
+ + +
+ ` + }) + ], argTypes: { context: { control: 'select', diff --git a/src/platform/assets/components/MediaAssetCard.vue b/src/platform/assets/components/MediaAssetCard.vue index 8aa58063e..1681dcf3e 100644 --- a/src/platform/assets/components/MediaAssetCard.vue +++ b/src/platform/assets/components/MediaAssetCard.vue @@ -33,7 +33,7 @@ :is="getTopComponent(asset.kind)" :asset="asset" :context="context" - @view="actions.viewAsset(asset!.id)" + @view="handleZoomClick" @download="actions.downloadAsset(asset!.id)" @play="actions.playAsset(asset!.id)" @video-playing-state-changed="isVideoPlaying = $event" @@ -41,31 +41,48 @@ /> - + - + - + - +