Skip to content

Commit 5d1cbd5

Browse files
comfy-pr-botviva-jinyiclaude
authored
[feat] Improve UX for disabled node packs in Manager dialog (#5478) (#5485)
* [feat] Improve UX for disabled node packs in Manager dialog - Hide "Update All" button when only disabled packs have updates - Add tooltip on "Update All" hover to indicate disabled nodes won't be updated - Disable version selector and show tooltip for disabled node packs - Filter updates to only show enabled packs in the update queue - Add visual indicators (opacity, cursor) for disabled pack cards - Add comprehensive test coverage for new functionality This improves the user experience by clearly indicating which packs can be updated and preventing confusion about disabled packs. 🤖 Generated with [Claude Code](https://claude.ai/code) * chore: missing nodes description added * test: test code modified --------- Co-authored-by: Jin Yi <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 5befd00 commit 5d1cbd5

File tree

11 files changed

+286
-22
lines changed

11 files changed

+286
-22
lines changed

src/components/button/IconButton.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<template>
2-
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
2+
<Button
3+
v-bind="$attrs"
4+
unstyled
5+
:class="buttonStyle"
6+
:disabled="disabled"
7+
@click="onClick"
8+
>
39
<slot></slot>
410
</Button>
511
</template>
@@ -20,6 +26,10 @@ interface IconButtonProps extends BaseButtonProps {
2026
onClick: (event: Event) => void
2127
}
2228
29+
defineOptions({
30+
inheritAttrs: false
31+
})
32+
2333
const {
2434
size = 'md',
2535
type = 'secondary',

src/components/button/IconTextButton.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<template>
2-
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
2+
<Button
3+
v-bind="$attrs"
4+
unstyled
5+
:class="buttonStyle"
6+
:disabled="disabled"
7+
@click="onClick"
8+
>
39
<slot v-if="iconPosition !== 'right'" name="icon"></slot>
410
<span>{{ label }}</span>
511
<slot v-if="iconPosition === 'right'" name="icon"></slot>
@@ -18,6 +24,10 @@ import {
1824
getButtonTypeClasses
1925
} from '@/types/buttonTypes'
2026
27+
defineOptions({
28+
inheritAttrs: false
29+
})
30+
2131
interface IconTextButtonProps extends BaseButtonProps {
2232
iconPosition?: 'left' | 'right'
2333
label: string

src/components/button/TextButton.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<template>
2-
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
2+
<Button
3+
v-bind="$attrs"
4+
unstyled
5+
:class="buttonStyle"
6+
:disabled="disabled"
7+
@click="onClick"
8+
>
39
<span>{{ label }}</span>
410
</Button>
511
</template>
@@ -21,6 +27,10 @@ interface TextButtonProps extends BaseButtonProps {
2127
onClick: () => void
2228
}
2329
30+
defineOptions({
31+
inheritAttrs: false
32+
})
33+
2434
const {
2535
size = 'md',
2636
type = 'primary',

src/components/dialog/content/LoadWorkflowWarning.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<NoResultsPlaceholder
33
class="pb-0"
44
icon="pi pi-exclamation-circle"
5-
title="Some Nodes Are Missing"
6-
message="When loading the graph, the following node types were not found"
5+
:title="$t('loadWorkflowWarning.missingNodesTitle')"
6+
:message="$t('loadWorkflowWarning.missingNodesDescription')"
77
/>
88
<MissingCoreNodesMessage :missing-core-nodes="missingCoreNodes" />
99
<ListBox

src/components/dialog/content/manager/PackVersionBadge.test.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { VueWrapper, mount } from '@vue/test-utils'
22
import { createPinia } from 'pinia'
33
import PrimeVue from 'primevue/config'
4+
import Tooltip from 'primevue/tooltip'
45
import { beforeEach, describe, expect, it, vi } from 'vitest'
56
import { nextTick } from 'vue'
67
import { createI18n } from 'vue-i18n'
@@ -31,11 +32,14 @@ const mockInstalledPacks = {
3132
'installed-pack': { ver: '2.0.0' }
3233
}
3334

35+
const mockIsPackEnabled = vi.fn(() => true)
36+
3437
vi.mock('@/stores/comfyManagerStore', () => ({
3538
useComfyManagerStore: vi.fn(() => ({
3639
installedPacks: mockInstalledPacks,
3740
isPackInstalled: (id: string) =>
38-
!!mockInstalledPacks[id as keyof typeof mockInstalledPacks]
41+
!!mockInstalledPacks[id as keyof typeof mockInstalledPacks],
42+
isPackEnabled: mockIsPackEnabled
3943
}))
4044
}))
4145

@@ -60,6 +64,7 @@ describe('PackVersionBadge', () => {
6064
beforeEach(() => {
6165
mockToggle.mockReset()
6266
mockHide.mockReset()
67+
mockIsPackEnabled.mockReturnValue(true) // Reset to default enabled state
6368
})
6469

6570
const mountComponent = ({
@@ -79,6 +84,9 @@ describe('PackVersionBadge', () => {
7984
},
8085
global: {
8186
plugins: [PrimeVue, createPinia(), i18n],
87+
directives: {
88+
tooltip: Tooltip
89+
},
8290
stubs: {
8391
Popover: PopoverStub,
8492
PackVersionSelectorPopover: true
@@ -229,4 +237,63 @@ describe('PackVersionBadge', () => {
229237
expect(mockHide).not.toHaveBeenCalled()
230238
})
231239
})
240+
241+
describe('disabled state', () => {
242+
beforeEach(() => {
243+
mockIsPackEnabled.mockReturnValue(false) // Set all packs as disabled for these tests
244+
})
245+
246+
it('adds disabled styles when pack is disabled', () => {
247+
const wrapper = mountComponent()
248+
249+
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
250+
expect(badge.exists()).toBe(true)
251+
expect(badge.classes()).toContain('cursor-not-allowed')
252+
expect(badge.classes()).toContain('opacity-60')
253+
})
254+
255+
it('does not show chevron icon when disabled', () => {
256+
const wrapper = mountComponent()
257+
258+
const chevronIcon = wrapper.find('.pi-chevron-right')
259+
expect(chevronIcon.exists()).toBe(false)
260+
})
261+
262+
it('does not show update arrow when disabled', () => {
263+
const wrapper = mountComponent()
264+
265+
const updateIcon = wrapper.find('.pi-arrow-circle-up')
266+
expect(updateIcon.exists()).toBe(false)
267+
})
268+
269+
it('does not toggle popover when clicked while disabled', async () => {
270+
const wrapper = mountComponent()
271+
272+
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
273+
expect(badge.exists()).toBe(true)
274+
await badge.trigger('click')
275+
276+
// Since it's disabled, the popover should not be toggled
277+
expect(mockToggle).not.toHaveBeenCalled()
278+
})
279+
280+
it('has correct tabindex when disabled', () => {
281+
const wrapper = mountComponent()
282+
283+
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
284+
expect(badge.exists()).toBe(true)
285+
expect(badge.attributes('tabindex')).toBe('-1')
286+
})
287+
288+
it('does not respond to keyboard events when disabled', async () => {
289+
const wrapper = mountComponent()
290+
291+
const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
292+
expect(badge.exists()).toBe(true)
293+
await badge.trigger('keydown.enter')
294+
await badge.trigger('keydown.space')
295+
296+
expect(mockToggle).not.toHaveBeenCalled()
297+
})
298+
})
232299
})

src/components/dialog/content/manager/PackVersionBadge.vue

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,28 @@
11
<template>
22
<div>
33
<div
4-
class="inline-flex items-center gap-1 rounded-2xl text-xs cursor-pointer py-1"
5-
:class="{ 'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill }"
6-
aria-haspopup="true"
7-
role="button"
8-
tabindex="0"
9-
@click="toggleVersionSelector"
10-
@keydown.enter="toggleVersionSelector"
11-
@keydown.space="toggleVersionSelector"
4+
v-tooltip.top="
5+
isDisabled ? $t('manager.enablePackToChangeVersion') : null
6+
"
7+
class="inline-flex items-center gap-1 rounded-2xl text-xs py-1"
8+
:class="{
9+
'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill,
10+
'cursor-pointer': !isDisabled,
11+
'cursor-not-allowed opacity-60': isDisabled
12+
}"
13+
:aria-haspopup="!isDisabled"
14+
:role="isDisabled ? 'text' : 'button'"
15+
:tabindex="isDisabled ? -1 : 0"
16+
@click="!isDisabled && toggleVersionSelector($event)"
17+
@keydown.enter="!isDisabled && toggleVersionSelector($event)"
18+
@keydown.space="!isDisabled && toggleVersionSelector($event)"
1219
>
1320
<i
1421
v-if="isUpdateAvailable"
1522
class="pi pi-arrow-circle-up text-blue-600 text-xs"
1623
/>
1724
<span>{{ installedVersion }}</span>
18-
<i class="pi pi-chevron-right text-xxs" />
25+
<i v-if="!isDisabled" class="pi pi-chevron-right text-xxs" />
1926
</div>
2027

2128
<Popover
@@ -61,6 +68,11 @@ const popoverRef = ref()
6168
6269
const managerStore = useComfyManagerStore()
6370
71+
const isInstalled = computed(() => managerStore.isPackInstalled(nodePack?.id))
72+
const isDisabled = computed(
73+
() => isInstalled.value && !managerStore.isPackEnabled(nodePack?.id)
74+
)
75+
6476
const installedVersion = computed(() => {
6577
if (!nodePack.id) return 'nightly'
6678
const version =

src/components/dialog/content/manager/button/PackUpdateButton.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<template>
22
<IconTextButton
3+
v-tooltip.top="
4+
hasDisabledUpdatePacks ? $t('manager.disabledNodesWontUpdate') : null
5+
"
36
v-bind="$attrs"
47
type="transparent"
58
:label="$t('manager.updateAll')"
@@ -24,8 +27,9 @@ import type { components } from '@/types/comfyRegistryTypes'
2427
2528
type NodePack = components['schemas']['Node']
2629
27-
const { nodePacks } = defineProps<{
30+
const { nodePacks, hasDisabledUpdatePacks } = defineProps<{
2831
nodePacks: NodePack[]
32+
hasDisabledUpdatePacks?: boolean
2933
}>()
3034
3135
const isUpdating = ref<boolean>(false)

src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
/>
3535
<PackUpdateButton
3636
v-if="isUpdateAvailableTab && hasUpdateAvailable"
37-
:node-packs="updateAvailableNodePacks"
37+
:node-packs="enabledUpdateAvailableNodePacks"
38+
:has-disabled-update-packs="hasDisabledUpdatePacks"
3839
/>
3940
</div>
4041
<div class="flex mt-3 text-sm">
@@ -103,8 +104,11 @@ const { t } = useI18n()
103104
const { missingNodePacks, isLoading, error } = useMissingNodes()
104105
105106
// Use the composable to get update available nodes
106-
const { hasUpdateAvailable, updateAvailableNodePacks } =
107-
useUpdateAvailableNodes()
107+
const {
108+
hasUpdateAvailable,
109+
enabledUpdateAvailableNodePacks,
110+
hasDisabledUpdatePacks
111+
} = useUpdateAvailableNodes()
108112
109113
const hasResults = computed(
110114
() => searchQuery.value?.trim() && searchResults?.length

src/composables/nodePack/useUpdateAvailableNodes.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,24 @@ export const useUpdateAvailableNodes = () => {
4444
return filterOutdatedPacks(installedPacks.value)
4545
})
4646

47-
// Check if there are any outdated packs
47+
// Filter only enabled outdated packs
48+
const enabledUpdateAvailableNodePacks = computed(() => {
49+
return updateAvailableNodePacks.value.filter((pack) =>
50+
comfyManagerStore.isPackEnabled(pack.id)
51+
)
52+
})
53+
54+
// Check if there are any enabled outdated packs
4855
const hasUpdateAvailable = computed(() => {
49-
return updateAvailableNodePacks.value.length > 0
56+
return enabledUpdateAvailableNodePacks.value.length > 0
57+
})
58+
59+
// Check if there are disabled packs with updates
60+
const hasDisabledUpdatePacks = computed(() => {
61+
return (
62+
updateAvailableNodePacks.value.length >
63+
enabledUpdateAvailableNodePacks.value.length
64+
)
5065
})
5166

5267
// Automatically fetch installed pack data when composable is used
@@ -58,7 +73,9 @@ export const useUpdateAvailableNodes = () => {
5873

5974
return {
6075
updateAvailableNodePacks,
76+
enabledUpdateAvailableNodePacks,
6177
hasUpdateAvailable,
78+
hasDisabledUpdatePacks,
6279
isLoading,
6380
error
6481
}

src/locales/en/main.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@
187187
"updateSelected": "Update Selected",
188188
"updateAll": "Update All",
189189
"updatingAllPacks": "Updating all packages",
190+
"disabledNodesWontUpdate": "Disabled nodes will not be updated",
191+
"enablePackToChangeVersion": "Enable this pack to change versions",
190192
"license": "License",
191193
"nightlyVersion": "Nightly",
192194
"latestVersion": "Latest",
@@ -1436,6 +1438,8 @@
14361438
"missingModelsMessage": "When loading the graph, the following models were not found"
14371439
},
14381440
"loadWorkflowWarning": {
1441+
"missingNodesTitle": "Some Nodes Are Missing",
1442+
"missingNodesDescription": "When loading the graph, the following node types were not found.\nThis may also happen if your installed version is lower and that node type can’t be found.",
14391443
"outdatedVersion": "Some nodes require a newer version of ComfyUI (current: {version}). Please update to use all nodes.",
14401444
"outdatedVersionGeneric": "Some nodes require a newer version of ComfyUI. Please update to use all nodes.",
14411445
"coreNodesFromVersion": "Requires ComfyUI {version}:"

0 commit comments

Comments
 (0)