Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/components/button/IconButton.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<template>
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
<Button
v-bind="$attrs"
unstyled
:class="buttonStyle"
:disabled="disabled"
@click="onClick"
>
<slot></slot>
</Button>
</template>
Expand All @@ -20,6 +26,10 @@ interface IconButtonProps extends BaseButtonProps {
onClick: (event: Event) => void
}

defineOptions({
inheritAttrs: false
})

const {
size = 'md',
type = 'secondary',
Expand Down
12 changes: 11 additions & 1 deletion src/components/button/IconTextButton.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<template>
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
<Button
v-bind="$attrs"
unstyled
:class="buttonStyle"
:disabled="disabled"
@click="onClick"
>
<slot v-if="iconPosition !== 'right'" name="icon"></slot>
<span>{{ label }}</span>
<slot v-if="iconPosition === 'right'" name="icon"></slot>
Expand All @@ -18,6 +24,10 @@ import {
getButtonTypeClasses
} from '@/types/buttonTypes'

defineOptions({
inheritAttrs: false
})

interface IconTextButtonProps extends BaseButtonProps {
iconPosition?: 'left' | 'right'
label: string
Expand Down
12 changes: 11 additions & 1 deletion src/components/button/TextButton.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<template>
<Button unstyled :class="buttonStyle" :disabled="disabled" @click="onClick">
<Button
v-bind="$attrs"
unstyled
:class="buttonStyle"
:disabled="disabled"
@click="onClick"
>
<span>{{ label }}</span>
</Button>
</template>
Expand All @@ -21,6 +27,10 @@ interface TextButtonProps extends BaseButtonProps {
onClick: () => void
}

defineOptions({
inheritAttrs: false
})

const {
size = 'md',
type = 'primary',
Expand Down
4 changes: 2 additions & 2 deletions src/components/dialog/content/LoadWorkflowWarning.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<NoResultsPlaceholder
class="pb-0"
icon="pi pi-exclamation-circle"
title="Some Nodes Are Missing"
message="When loading the graph, the following node types were not found"
:title="$t('loadWorkflowWarning.missingNodesTitle')"
:message="$t('loadWorkflowWarning.missingNodesDescription')"
/>
<MissingCoreNodesMessage :missing-core-nodes="missingCoreNodes" />
<ListBox
Expand Down
69 changes: 68 additions & 1 deletion src/components/dialog/content/manager/PackVersionBadge.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { VueWrapper, mount } from '@vue/test-utils'
import { createPinia } from 'pinia'
import PrimeVue from 'primevue/config'
import Tooltip from 'primevue/tooltip'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import { createI18n } from 'vue-i18n'
Expand Down Expand Up @@ -31,11 +32,14 @@ const mockInstalledPacks = {
'installed-pack': { ver: '2.0.0' }
}

const mockIsPackEnabled = vi.fn(() => true)

vi.mock('@/stores/comfyManagerStore', () => ({
useComfyManagerStore: vi.fn(() => ({
installedPacks: mockInstalledPacks,
isPackInstalled: (id: string) =>
!!mockInstalledPacks[id as keyof typeof mockInstalledPacks]
!!mockInstalledPacks[id as keyof typeof mockInstalledPacks],
isPackEnabled: mockIsPackEnabled
}))
}))

Expand All @@ -60,6 +64,7 @@ describe('PackVersionBadge', () => {
beforeEach(() => {
mockToggle.mockReset()
mockHide.mockReset()
mockIsPackEnabled.mockReturnValue(true) // Reset to default enabled state
})

const mountComponent = ({
Expand All @@ -79,6 +84,9 @@ describe('PackVersionBadge', () => {
},
global: {
plugins: [PrimeVue, createPinia(), i18n],
directives: {
tooltip: Tooltip
},
stubs: {
Popover: PopoverStub,
PackVersionSelectorPopover: true
Expand Down Expand Up @@ -229,4 +237,63 @@ describe('PackVersionBadge', () => {
expect(mockHide).not.toHaveBeenCalled()
})
})

describe('disabled state', () => {
beforeEach(() => {
mockIsPackEnabled.mockReturnValue(false) // Set all packs as disabled for these tests
})

it('adds disabled styles when pack is disabled', () => {
const wrapper = mountComponent()

const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
expect(badge.exists()).toBe(true)
expect(badge.classes()).toContain('cursor-not-allowed')
expect(badge.classes()).toContain('opacity-60')
})

it('does not show chevron icon when disabled', () => {
const wrapper = mountComponent()

const chevronIcon = wrapper.find('.pi-chevron-right')
expect(chevronIcon.exists()).toBe(false)
})

it('does not show update arrow when disabled', () => {
const wrapper = mountComponent()

const updateIcon = wrapper.find('.pi-arrow-circle-up')
expect(updateIcon.exists()).toBe(false)
})

it('does not toggle popover when clicked while disabled', async () => {
const wrapper = mountComponent()

const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
expect(badge.exists()).toBe(true)
await badge.trigger('click')

// Since it's disabled, the popover should not be toggled
expect(mockToggle).not.toHaveBeenCalled()
})

it('has correct tabindex when disabled', () => {
const wrapper = mountComponent()

const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
expect(badge.exists()).toBe(true)
expect(badge.attributes('tabindex')).toBe('-1')
})

it('does not respond to keyboard events when disabled', async () => {
const wrapper = mountComponent()

const badge = wrapper.find('[role="text"]') // role changes to "text" when disabled
expect(badge.exists()).toBe(true)
await badge.trigger('keydown.enter')
await badge.trigger('keydown.space')

expect(mockToggle).not.toHaveBeenCalled()
})
})
})
30 changes: 21 additions & 9 deletions src/components/dialog/content/manager/PackVersionBadge.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
<template>
<div>
<div
class="inline-flex items-center gap-1 rounded-2xl text-xs cursor-pointer py-1"
:class="{ 'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill }"
aria-haspopup="true"
role="button"
tabindex="0"
@click="toggleVersionSelector"
@keydown.enter="toggleVersionSelector"
@keydown.space="toggleVersionSelector"
v-tooltip.top="
isDisabled ? $t('manager.enablePackToChangeVersion') : null
"
class="inline-flex items-center gap-1 rounded-2xl text-xs py-1"
:class="{
'bg-gray-100 dark-theme:bg-neutral-700 px-1.5': fill,
'cursor-pointer': !isDisabled,
'cursor-not-allowed opacity-60': isDisabled
}"
:aria-haspopup="!isDisabled"
:role="isDisabled ? 'text' : 'button'"
:tabindex="isDisabled ? -1 : 0"
@click="!isDisabled && toggleVersionSelector($event)"
@keydown.enter="!isDisabled && toggleVersionSelector($event)"
@keydown.space="!isDisabled && toggleVersionSelector($event)"
>
<i
v-if="isUpdateAvailable"
class="pi pi-arrow-circle-up text-blue-600 text-xs"
/>
<span>{{ installedVersion }}</span>
<i class="pi pi-chevron-right text-xxs" />
<i v-if="!isDisabled" class="pi pi-chevron-right text-xxs" />
</div>

<Popover
Expand Down Expand Up @@ -61,6 +68,11 @@ const popoverRef = ref()

const managerStore = useComfyManagerStore()

const isInstalled = computed(() => managerStore.isPackInstalled(nodePack?.id))
const isDisabled = computed(
() => isInstalled.value && !managerStore.isPackEnabled(nodePack?.id)
)

const installedVersion = computed(() => {
if (!nodePack.id) return 'nightly'
const version =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<IconTextButton
v-tooltip.top="
hasDisabledUpdatePacks ? $t('manager.disabledNodesWontUpdate') : null
"
v-bind="$attrs"
type="transparent"
:label="$t('manager.updateAll')"
Expand All @@ -24,8 +27,9 @@ import type { components } from '@/types/comfyRegistryTypes'

type NodePack = components['schemas']['Node']

const { nodePacks } = defineProps<{
const { nodePacks, hasDisabledUpdatePacks } = defineProps<{
nodePacks: NodePack[]
hasDisabledUpdatePacks?: boolean
}>()

const isUpdating = ref<boolean>(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
/>
<PackUpdateButton
v-if="isUpdateAvailableTab && hasUpdateAvailable"
:node-packs="updateAvailableNodePacks"
:node-packs="enabledUpdateAvailableNodePacks"
:has-disabled-update-packs="hasDisabledUpdatePacks"
/>
</div>
<div class="flex mt-3 text-sm">
Expand Down Expand Up @@ -103,8 +104,11 @@ const { t } = useI18n()
const { missingNodePacks, isLoading, error } = useMissingNodes()

// Use the composable to get update available nodes
const { hasUpdateAvailable, updateAvailableNodePacks } =
useUpdateAvailableNodes()
const {
hasUpdateAvailable,
enabledUpdateAvailableNodePacks,
hasDisabledUpdatePacks
} = useUpdateAvailableNodes()

const hasResults = computed(
() => searchQuery.value?.trim() && searchResults?.length
Expand Down
21 changes: 19 additions & 2 deletions src/composables/nodePack/useUpdateAvailableNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,24 @@ export const useUpdateAvailableNodes = () => {
return filterOutdatedPacks(installedPacks.value)
})

// Check if there are any outdated packs
// Filter only enabled outdated packs
const enabledUpdateAvailableNodePacks = computed(() => {
return updateAvailableNodePacks.value.filter((pack) =>
comfyManagerStore.isPackEnabled(pack.id)
)
})

// Check if there are any enabled outdated packs
const hasUpdateAvailable = computed(() => {
return updateAvailableNodePacks.value.length > 0
return enabledUpdateAvailableNodePacks.value.length > 0
})

// Check if there are disabled packs with updates
const hasDisabledUpdatePacks = computed(() => {
return (
updateAvailableNodePacks.value.length >
enabledUpdateAvailableNodePacks.value.length
)
})

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

return {
updateAvailableNodePacks,
enabledUpdateAvailableNodePacks,
hasUpdateAvailable,
hasDisabledUpdatePacks,
isLoading,
error
}
Expand Down
4 changes: 4 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@
"updateSelected": "Update Selected",
"updateAll": "Update All",
"updatingAllPacks": "Updating all packages",
"disabledNodesWontUpdate": "Disabled nodes will not be updated",
"enablePackToChangeVersion": "Enable this pack to change versions",
"license": "License",
"nightlyVersion": "Nightly",
"latestVersion": "Latest",
Expand Down Expand Up @@ -1436,6 +1438,8 @@
"missingModelsMessage": "When loading the graph, the following models were not found"
},
"loadWorkflowWarning": {
"missingNodesTitle": "Some Nodes Are Missing",
"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.",
"outdatedVersion": "Some nodes require a newer version of ComfyUI (current: {version}). Please update to use all nodes.",
"outdatedVersionGeneric": "Some nodes require a newer version of ComfyUI. Please update to use all nodes.",
"coreNodesFromVersion": "Requires ComfyUI {version}:"
Expand Down
Loading