mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-09 01:20:09 +00:00
Add node pack version selector dropdown (#2973)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -1,23 +1,85 @@
|
||||
<template>
|
||||
<Button
|
||||
v-if="version"
|
||||
:label="version"
|
||||
severity="secondary"
|
||||
icon="pi pi-chevron-right"
|
||||
icon-pos="right"
|
||||
class="rounded-xl text-xs tracking-tighter"
|
||||
:pt="{
|
||||
root: { class: 'p-0' },
|
||||
label: { class: 'pl-2 pr-0 py-0.5' },
|
||||
icon: { class: 'text-xs pl-0 pr-2 py-0.5' }
|
||||
}"
|
||||
/>
|
||||
<div class="relative">
|
||||
<Button
|
||||
v-if="displayVersion"
|
||||
:label="displayVersion"
|
||||
severity="secondary"
|
||||
icon="pi pi-chevron-right"
|
||||
icon-pos="right"
|
||||
class="rounded-xl text-xs tracking-tighter p-0"
|
||||
:pt="{
|
||||
label: { class: 'pl-2 pr-0 py-0.5' },
|
||||
icon: { class: 'text-xs pl-0 pr-2 py-0.5' }
|
||||
}"
|
||||
aria-haspopup="true"
|
||||
@click="toggleVersionSelector"
|
||||
/>
|
||||
|
||||
<Popover
|
||||
ref="popoverRef"
|
||||
:pt="{
|
||||
content: { class: 'px-0' }
|
||||
}"
|
||||
>
|
||||
<PackVersionSelectorPopover
|
||||
:selected-version="selectedVersion"
|
||||
:node-pack="nodePack"
|
||||
@select="onSelect"
|
||||
@cancel="closeVersionSelector"
|
||||
@apply="applyVersionSelection"
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { whenever } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import Popover from 'primevue/popover'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
version: string | undefined
|
||||
import PackVersionSelectorPopover from '@/components/dialog/content/manager/PackVersionSelectorPopover.vue'
|
||||
import { SelectedVersion } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { nodePack, version = SelectedVersion.NIGHTLY } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
version?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:version': [version: string]
|
||||
}>()
|
||||
|
||||
const popoverRef = ref()
|
||||
const selectedVersion = ref<string>(version)
|
||||
|
||||
const displayVersion = computed(() => {
|
||||
if (selectedVersion.value === SelectedVersion.LATEST) {
|
||||
// If there is no version, treat as unclaimed GitHub pack and use nightly
|
||||
return nodePack?.latest_version?.version || SelectedVersion.NIGHTLY
|
||||
}
|
||||
return selectedVersion.value
|
||||
})
|
||||
|
||||
const toggleVersionSelector = (event: Event) => {
|
||||
popoverRef.value.toggle(event)
|
||||
}
|
||||
|
||||
const closeVersionSelector = () => {
|
||||
popoverRef.value.hide()
|
||||
}
|
||||
|
||||
const onSelect = (newVersion: string) => {
|
||||
selectedVersion.value = newVersion
|
||||
}
|
||||
|
||||
const applyVersionSelection = (newVersion: string) => {
|
||||
selectedVersion.value = newVersion
|
||||
emit('update:version', newVersion)
|
||||
// TODO: after manager store added, install the pack here
|
||||
closeVersionSelector()
|
||||
}
|
||||
|
||||
whenever(() => version, onSelect)
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="w-64 mt-2">
|
||||
<span class="pl-3 text-muted text-md font-semibold opacity-70">
|
||||
{{ $t('manager.selectVersion') }}
|
||||
</span>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="text-center text-muted py-4 flex flex-col items-center"
|
||||
>
|
||||
<ProgressSpinner class="w-8 h-8 mb-2" />
|
||||
{{ $t('manager.loadingVersions') }}
|
||||
</div>
|
||||
<div v-else-if="allVersionOptions.length === 0" class="py-2">
|
||||
<NoResultsPlaceholder
|
||||
:title="$t('g.noResultsFound')"
|
||||
:message="$t('manager.tryAgainLater')"
|
||||
icon="pi pi-exclamation-circle"
|
||||
class="p-0"
|
||||
/>
|
||||
</div>
|
||||
<Listbox
|
||||
v-else
|
||||
v-model="currentSelection"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
:options="allVersionOptions"
|
||||
:highlight-on-select="false"
|
||||
class="my-3 w-full max-h-[50vh] border-none"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div class="flex justify-between items-center w-full p-1">
|
||||
<span>{{ slotProps.option.label }}</span>
|
||||
<i
|
||||
v-if="currentSelection === slotProps.option.value"
|
||||
class="pi pi-check text-highlight"
|
||||
></i>
|
||||
</div>
|
||||
</template>
|
||||
</Listbox>
|
||||
<ContentDivider class="my-2" />
|
||||
<div class="flex justify-end gap-2 p-1 px-3">
|
||||
<Button
|
||||
text
|
||||
severity="secondary"
|
||||
:label="$t('g.cancel')"
|
||||
@click="emit('cancel')"
|
||||
/>
|
||||
<Button
|
||||
severity="secondary"
|
||||
:label="$t('g.install')"
|
||||
@click="emit('apply', currentSelection ?? SelectedVersion.LATEST)"
|
||||
class="py-3 px-4 dark-theme:bg-unset bg-black/80 dark-theme:text-unset text-neutral-100 rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import Button from 'primevue/button'
|
||||
import Listbox from 'primevue/listbox'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import ContentDivider from '@/components/common/ContentDivider.vue'
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import { useComfyRegistryService } from '@/services/comfyRegistryService'
|
||||
import { SelectedVersion } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { nodePack, selectedVersion = SelectedVersion.NIGHTLY } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
selectedVersion?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
cancel: []
|
||||
apply: [version: string]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const registryService = useComfyRegistryService()
|
||||
|
||||
const currentSelection = ref<string>(selectedVersion)
|
||||
|
||||
const fetchVersions = async () => {
|
||||
if (!nodePack?.id) return []
|
||||
return (await registryService.getPackVersions(nodePack.id)) || []
|
||||
}
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
state: versions,
|
||||
execute: startFetchVersions
|
||||
} = useAsyncState(fetchVersions, [])
|
||||
|
||||
const specialOptions = computed(() => [
|
||||
{
|
||||
value: SelectedVersion.NIGHTLY,
|
||||
label: t('manager.nightlyVersion')
|
||||
},
|
||||
{
|
||||
value: SelectedVersion.LATEST,
|
||||
label: t('manager.latestVersion')
|
||||
}
|
||||
])
|
||||
|
||||
const versionOptions = computed(() =>
|
||||
versions.value.map((version) => ({
|
||||
value: version.version,
|
||||
label: version.version
|
||||
}))
|
||||
)
|
||||
|
||||
const allVersionOptions = computed(() => [
|
||||
...specialOptions.value,
|
||||
...versionOptions.value
|
||||
])
|
||||
|
||||
watch(
|
||||
() => nodePack,
|
||||
() => startFetchVersions(),
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,159 @@
|
||||
import { VueWrapper, mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import Button from 'primevue/button'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Popover from 'primevue/popover'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import enMessages from '@/locales/en/main.json'
|
||||
import { SelectedVersion } from '@/types/comfyManagerTypes'
|
||||
|
||||
import PackVersionBadge from '../PackVersionBadge.vue'
|
||||
import PackVersionSelectorPopover from '../PackVersionSelectorPopover.vue'
|
||||
|
||||
const mockNodePack = {
|
||||
id: 'test-pack',
|
||||
name: 'Test Pack',
|
||||
latest_version: {
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
describe('PackVersionBadge', () => {
|
||||
const mountComponent = ({
|
||||
props = {}
|
||||
}: Record<string, any> = {}): VueWrapper => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: enMessages }
|
||||
})
|
||||
|
||||
return mount(PackVersionBadge, {
|
||||
props: {
|
||||
nodePack: mockNodePack,
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
components: {
|
||||
Popover,
|
||||
PackVersionSelectorPopover
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders with default version (NIGHTLY)', () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
const button = wrapper.findComponent(Button)
|
||||
expect(button.exists()).toBe(true)
|
||||
expect(button.props('label')).toBe(SelectedVersion.NIGHTLY)
|
||||
})
|
||||
|
||||
it('renders with provided version', () => {
|
||||
const version = '2.0.0'
|
||||
const wrapper = mountComponent({ props: { version } })
|
||||
|
||||
const button = wrapper.findComponent(Button)
|
||||
expect(button.exists()).toBe(true)
|
||||
expect(button.props('label')).toBe(version)
|
||||
})
|
||||
|
||||
it('shows actual latest (semantic) version prop when version is set to latest', () => {
|
||||
const wrapper = mountComponent({
|
||||
props: { version: SelectedVersion.LATEST }
|
||||
})
|
||||
|
||||
const button = wrapper.findComponent(Button)
|
||||
expect(button.exists()).toBe(true)
|
||||
expect(button.props('label')).toBe(mockNodePack.latest_version.version)
|
||||
})
|
||||
|
||||
it('toggles the popover when button is clicked', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Spy on the toggle method
|
||||
const popoverToggleSpy = vi.fn()
|
||||
const popover = wrapper.findComponent(Popover)
|
||||
popover.vm.toggle = popoverToggleSpy
|
||||
|
||||
// Open the popover
|
||||
await wrapper.findComponent(Button).trigger('click')
|
||||
|
||||
// Verify that the toggle method was called
|
||||
expect(popoverToggleSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('emits update:version event when version is selected', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Open the popover
|
||||
await wrapper.findComponent(Button).trigger('click')
|
||||
|
||||
// Simulate the popover emitting an apply event
|
||||
wrapper.findComponent(PackVersionSelectorPopover).vm.$emit('apply', '3.0.0')
|
||||
await nextTick()
|
||||
|
||||
// Check if the update:version event was emitted with the correct value
|
||||
expect(wrapper.emitted('update:version')).toBeTruthy()
|
||||
expect(wrapper.emitted('update:version')![0]).toEqual(['3.0.0'])
|
||||
})
|
||||
|
||||
it('closes the popover when cancel is clicked', async () => {
|
||||
const wrapper = mountComponent()
|
||||
|
||||
// Open the popover
|
||||
await wrapper.findComponent(Button).trigger('click')
|
||||
|
||||
// Simulate the popover emitting a cancel event
|
||||
wrapper.findComponent(PackVersionSelectorPopover).vm.$emit('cancel')
|
||||
await nextTick()
|
||||
|
||||
// Check if the popover is hidden
|
||||
expect(wrapper.findComponent(Popover).isVisible()).toBe(false)
|
||||
})
|
||||
|
||||
it('updates displayed version when version prop changes', async () => {
|
||||
const wrapper = mountComponent({ props: { version: '1.0.0' } })
|
||||
|
||||
expect(wrapper.findComponent(Button).props('label')).toBe('1.0.0')
|
||||
|
||||
// Update the version prop
|
||||
await wrapper.setProps({ version: '2.0.0' })
|
||||
|
||||
// Check if the displayed version was updated
|
||||
expect(wrapper.findComponent(Button).props('label')).toBe('2.0.0')
|
||||
})
|
||||
|
||||
it('handles null or undefined nodePack', async () => {
|
||||
const wrapper = mountComponent({ props: { nodePack: null } })
|
||||
|
||||
const button = wrapper.findComponent(Button)
|
||||
expect(button.exists()).toBe(true)
|
||||
expect(button.props('label')).toBe(SelectedVersion.NIGHTLY)
|
||||
|
||||
// Should not crash when clicking the button
|
||||
await button.trigger('click')
|
||||
expect(wrapper.findComponent(Popover).isVisible()).toBe(false)
|
||||
})
|
||||
|
||||
it('handles missing latest_version (unclaimed pack) by falling back to NIGHTLY', async () => {
|
||||
const incompleteNodePack = { id: 'test-pack', name: 'Test Pack' }
|
||||
const wrapper = mountComponent({
|
||||
props: {
|
||||
nodePack: incompleteNodePack,
|
||||
version: SelectedVersion.LATEST
|
||||
}
|
||||
})
|
||||
|
||||
const button = wrapper.findComponent(Button)
|
||||
expect(button.exists()).toBe(true)
|
||||
|
||||
// Should fallback to nightly string when latest_version is missing
|
||||
expect(button.props('label')).toBe(SelectedVersion.NIGHTLY)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,190 @@
|
||||
import { VueWrapper, mount } from '@vue/test-utils'
|
||||
import { createPinia } from 'pinia'
|
||||
import Button from 'primevue/button'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import Listbox from 'primevue/listbox'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import enMessages from '@/locales/en/main.json'
|
||||
import { SelectedVersion } from '@/types/comfyManagerTypes'
|
||||
|
||||
import PackVersionSelectorPopover from '../PackVersionSelectorPopover.vue'
|
||||
|
||||
const mockVersions = [
|
||||
{ version: '1.0.0', createdAt: '2023-01-01' },
|
||||
{ version: '0.9.0', createdAt: '2022-12-01' },
|
||||
{ version: '0.8.0', createdAt: '2022-11-01' }
|
||||
]
|
||||
|
||||
const mockNodePack = {
|
||||
id: 'test-pack',
|
||||
name: 'Test Pack',
|
||||
latest_version: { version: '1.0.0' }
|
||||
}
|
||||
|
||||
const mockGetPackVersions = vi.fn().mockResolvedValue(mockVersions)
|
||||
|
||||
vi.mock('@/services/comfyRegistryService', () => ({
|
||||
useComfyRegistryService: vi.fn(() => ({
|
||||
getPackVersions: mockGetPackVersions
|
||||
}))
|
||||
}))
|
||||
|
||||
const waitForPromises = async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 16))
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
describe('PackVersionSelectorPopover', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockGetPackVersions.mockClear()
|
||||
mockGetPackVersions.mockResolvedValue(mockVersions)
|
||||
})
|
||||
|
||||
const mountComponent = ({
|
||||
props = {}
|
||||
}: Record<string, any> = {}): VueWrapper => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: { en: enMessages }
|
||||
})
|
||||
|
||||
return mount(PackVersionSelectorPopover, {
|
||||
props: {
|
||||
nodePack: mockNodePack,
|
||||
selectedVersion: SelectedVersion.NIGHTLY,
|
||||
...props
|
||||
},
|
||||
global: {
|
||||
plugins: [PrimeVue, createPinia(), i18n],
|
||||
components: {
|
||||
Listbox
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('fetches versions on mount', async () => {
|
||||
mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
expect(mockGetPackVersions).toHaveBeenCalledWith(mockNodePack.id)
|
||||
})
|
||||
|
||||
it('shows loading state while fetching versions', async () => {
|
||||
// Delay the promise resolution
|
||||
mockGetPackVersions.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => setTimeout(() => resolve(mockVersions), 1000))
|
||||
)
|
||||
|
||||
const wrapper = mountComponent()
|
||||
|
||||
expect(wrapper.text()).toContain('Loading versions...')
|
||||
})
|
||||
|
||||
it('displays special options and version options in the listbox', async () => {
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
const listbox = wrapper.findComponent(Listbox)
|
||||
expect(listbox.exists()).toBe(true)
|
||||
|
||||
const options = listbox.props('options')!
|
||||
expect(options.length).toBe(5) // 2 special options + 3 version options
|
||||
|
||||
// Check special options
|
||||
expect(options[0].value).toBe(SelectedVersion.NIGHTLY)
|
||||
expect(options[1].value).toBe(SelectedVersion.LATEST)
|
||||
|
||||
// Check version options
|
||||
expect(options[2].value).toBe('1.0.0')
|
||||
expect(options[3].value).toBe('0.9.0')
|
||||
expect(options[4].value).toBe('0.8.0')
|
||||
})
|
||||
|
||||
it('initializes with the provided selectedVersion prop', async () => {
|
||||
const selectedVersion = '0.9.0'
|
||||
const wrapper = mountComponent({ props: { selectedVersion } })
|
||||
await waitForPromises()
|
||||
|
||||
// Check that the listbox has the correct model value
|
||||
const listbox = wrapper.findComponent(Listbox)
|
||||
expect(listbox.props('modelValue')).toBe(selectedVersion)
|
||||
})
|
||||
|
||||
it('emits cancel event when cancel button is clicked', async () => {
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
const cancelButton = wrapper.findAllComponents(Button)[0]
|
||||
await cancelButton.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('cancel')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('emits apply event with current selection when apply button is clicked', async () => {
|
||||
const selectedVersion = '0.9.0'
|
||||
const wrapper = mountComponent({ props: { selectedVersion } })
|
||||
await waitForPromises()
|
||||
|
||||
const applyButton = wrapper.findAllComponents(Button)[1]
|
||||
await applyButton.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('apply')).toBeTruthy()
|
||||
expect(wrapper.emitted('apply')![0]).toEqual([selectedVersion])
|
||||
})
|
||||
|
||||
it('emits apply event with LATEST when no selection and apply button is clicked', async () => {
|
||||
const wrapper = mountComponent({ props: { selectedVersion: null } })
|
||||
await waitForPromises()
|
||||
|
||||
const applyButton = wrapper.findAllComponents(Button)[1]
|
||||
await applyButton.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('apply')).toBeTruthy()
|
||||
expect(wrapper.emitted('apply')![0]).toEqual([SelectedVersion.LATEST])
|
||||
})
|
||||
|
||||
it('is reactive to nodePack prop changes', async () => {
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
// Clear mock calls to check if getPackVersions is called again
|
||||
mockGetPackVersions.mockClear()
|
||||
|
||||
// Update the nodePack prop
|
||||
const newNodePack = { ...mockNodePack, id: 'new-test-pack' }
|
||||
await wrapper.setProps({ nodePack: newNodePack })
|
||||
await waitForPromises()
|
||||
|
||||
// Should fetch versions for the new nodePack
|
||||
expect(mockGetPackVersions).toHaveBeenCalledWith(newNodePack.id)
|
||||
})
|
||||
|
||||
describe('Unclaimed GitHub packs handling', () => {
|
||||
it('falls back to nightly when comfy-api returns null when fetching versions', async () => {
|
||||
mockGetPackVersions.mockResolvedValueOnce(null)
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
const listbox = wrapper.findComponent(Listbox)
|
||||
expect(listbox.props('modelValue')).toBe(SelectedVersion.NIGHTLY)
|
||||
})
|
||||
|
||||
it('falls back to nightly when component mounts with no versions (unclaimed pack)', async () => {
|
||||
mockGetPackVersions.mockResolvedValueOnce([])
|
||||
|
||||
const wrapper = mountComponent()
|
||||
await waitForPromises()
|
||||
|
||||
const listbox = wrapper.findComponent(Listbox)
|
||||
expect(listbox.props('modelValue')).toBe(SelectedVersion.NIGHTLY)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -21,7 +21,11 @@
|
||||
/>
|
||||
</MetadataRow>
|
||||
<MetadataRow :label="t('manager.version')">
|
||||
<PackVersionBadge :version="nodePack.latest_version?.version" />
|
||||
<PackVersionBadge
|
||||
:node-pack="nodePack"
|
||||
:version="selectedVersion"
|
||||
@update:version="updateSelectedVersion"
|
||||
/>
|
||||
</MetadataRow>
|
||||
</div>
|
||||
<div class="mb-6 overflow-hidden">
|
||||
@@ -32,7 +36,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import PackStatusMessage from '@/components/dialog/content/manager/PackStatusMessage.vue'
|
||||
@@ -40,6 +44,7 @@ import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBad
|
||||
import InfoTabs from '@/components/dialog/content/manager/infoPanel/InfoTabs.vue'
|
||||
import MetadataRow from '@/components/dialog/content/manager/infoPanel/MetadataRow.vue'
|
||||
import PackCardHeader from '@/components/dialog/content/manager/packCard/PackCardHeader.vue'
|
||||
import { SelectedVersion } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import { formatNumber } from '@/utils/formatUtil'
|
||||
|
||||
@@ -49,16 +54,29 @@ interface InfoItem {
|
||||
value: string | number | undefined
|
||||
}
|
||||
|
||||
const { t, d } = useI18n()
|
||||
|
||||
const { nodePack } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
}>()
|
||||
|
||||
const { t, d } = useI18n()
|
||||
|
||||
const packCardHeaderRef = ref(null)
|
||||
|
||||
const selectedVersion = ref<string>(
|
||||
nodePack.latest_version?.version || SelectedVersion.NIGHTLY
|
||||
)
|
||||
const updateSelectedVersion = (version: string) => {
|
||||
selectedVersion.value = version
|
||||
if (packCardHeaderRef.value) {
|
||||
packCardHeaderRef.value.updateVersion?.(version)
|
||||
}
|
||||
}
|
||||
|
||||
const infoItems = computed<InfoItem[]>(() => [
|
||||
{
|
||||
key: 'publisher',
|
||||
label: `${t('manager.createdBy')}`,
|
||||
label: t('manager.createdBy'),
|
||||
// TODO: handle all Comfy Registry publisher types dynamically (e.g., organizations, multiple authors)
|
||||
value: nodePack.publisher?.name
|
||||
},
|
||||
{
|
||||
|
||||
@@ -66,7 +66,12 @@
|
||||
<span v-if="nodePack.publisher?.name">
|
||||
{{ nodePack.publisher.name }}
|
||||
</span>
|
||||
<span v-if="nodePack.latest_version">
|
||||
<PackVersionBadge
|
||||
v-if="isPackInstalled"
|
||||
:node-pack="nodePack"
|
||||
v-model:version="selectedVersion"
|
||||
/>
|
||||
<span v-else-if="nodePack.latest_version">
|
||||
{{ nodePack.latest_version.version }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -88,15 +93,24 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Card from 'primevue/card'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import ContentDivider from '@/components/common/ContentDivider.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/PackInstallButton.vue'
|
||||
import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBadge.vue'
|
||||
import PackIcon from '@/components/dialog/content/manager/packIcon/PackIcon.vue'
|
||||
import type { components } from '@/types/comfyRegistryTypes'
|
||||
import { formatNumber } from '@/utils/formatUtil'
|
||||
|
||||
defineProps<{
|
||||
const { nodePack, isSelected = false } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
isSelected?: boolean
|
||||
}>()
|
||||
const selectedVersion = ref<string | undefined>(undefined)
|
||||
const isPackInstalled = computed(
|
||||
() =>
|
||||
// TODO: after manager store added
|
||||
// managerStore.isPackInstalled(nodePack?.id)
|
||||
true
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -89,13 +89,18 @@
|
||||
"name": "Name",
|
||||
"category": "Category",
|
||||
"sort": "Sort",
|
||||
"filter": "Filter"
|
||||
"filter": "Filter",
|
||||
"apply": "Apply"
|
||||
},
|
||||
"manager": {
|
||||
"title": "Custom Nodes Manager",
|
||||
"loadingVersions": "Loading versions...",
|
||||
"selectVersion": "Select Version",
|
||||
"downloads": "Downloads",
|
||||
"repository": "Repository",
|
||||
"license": "License",
|
||||
"nightlyVersion": "Nightly",
|
||||
"latestVersion": "Latest",
|
||||
"createdBy": "Created By",
|
||||
"totalNodes": "Total Nodes",
|
||||
"discoverCommunityContent": "Discover community-made Node Packs, Extensions, and more...",
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"about": "À propos",
|
||||
"add": "Ajouter",
|
||||
"all": "Tout",
|
||||
"apply": "Appliquer",
|
||||
"back": "Retour",
|
||||
"cancel": "Annuler",
|
||||
"capture": "capture",
|
||||
@@ -383,13 +384,17 @@
|
||||
},
|
||||
"installSelected": "Installer sélectionné",
|
||||
"lastUpdated": "Dernière mise à jour",
|
||||
"latestVersion": "Dernière",
|
||||
"license": "Licence",
|
||||
"loadingVersions": "Chargement des versions...",
|
||||
"nightlyVersion": "Nocturne",
|
||||
"noDescription": "Aucune description disponible",
|
||||
"noResultsFound": "Aucun résultat trouvé correspondant à votre recherche.",
|
||||
"nodePack": "Pack de Nœuds",
|
||||
"packsSelected": "Packs sélectionnés",
|
||||
"repository": "Référentiel",
|
||||
"searchPlaceholder": "Recherche",
|
||||
"selectVersion": "Sélectionner la version",
|
||||
"sort": {
|
||||
"downloads": "Le plus populaire",
|
||||
"rating": "Évaluation"
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"about": "情報",
|
||||
"add": "追加",
|
||||
"all": "すべて",
|
||||
"apply": "適用する",
|
||||
"back": "戻る",
|
||||
"cancel": "キャンセル",
|
||||
"capture": "キャプチャ",
|
||||
@@ -383,13 +384,17 @@
|
||||
},
|
||||
"installSelected": "選択したものをインストール",
|
||||
"lastUpdated": "最終更新日",
|
||||
"latestVersion": "最新",
|
||||
"license": "ライセンス",
|
||||
"loadingVersions": "バージョンを読み込んでいます...",
|
||||
"nightlyVersion": "ナイトリー",
|
||||
"noDescription": "説明はありません",
|
||||
"noResultsFound": "検索に一致する結果が見つかりませんでした。",
|
||||
"nodePack": "ノードパック",
|
||||
"packsSelected": "選択したパック",
|
||||
"repository": "リポジトリ",
|
||||
"searchPlaceholder": "検索",
|
||||
"selectVersion": "バージョンを選択",
|
||||
"sort": {
|
||||
"downloads": "最も人気",
|
||||
"rating": "評価"
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"about": "정보",
|
||||
"add": "추가",
|
||||
"all": "모두",
|
||||
"apply": "적용",
|
||||
"back": "뒤로",
|
||||
"cancel": "취소",
|
||||
"capture": "캡처",
|
||||
@@ -383,13 +384,17 @@
|
||||
},
|
||||
"installSelected": "선택한 항목 설치",
|
||||
"lastUpdated": "마지막 업데이트",
|
||||
"latestVersion": "최신",
|
||||
"license": "라이선스",
|
||||
"loadingVersions": "버전 로딩 중...",
|
||||
"nightlyVersion": "야간",
|
||||
"noDescription": "설명이 없습니다",
|
||||
"noResultsFound": "검색과 일치하는 결과가 없습니다.",
|
||||
"nodePack": "노드 팩",
|
||||
"packsSelected": "선택한 팩",
|
||||
"repository": "저장소",
|
||||
"searchPlaceholder": "검색",
|
||||
"selectVersion": "버전 선택",
|
||||
"sort": {
|
||||
"downloads": "가장 인기 있는",
|
||||
"rating": "평점"
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"about": "О программе",
|
||||
"add": "Добавить",
|
||||
"all": "Все",
|
||||
"apply": "Применить",
|
||||
"back": "Назад",
|
||||
"cancel": "Отмена",
|
||||
"capture": "захват",
|
||||
@@ -383,13 +384,17 @@
|
||||
},
|
||||
"installSelected": "Установить выбранное",
|
||||
"lastUpdated": "Последнее обновление",
|
||||
"latestVersion": "Последняя",
|
||||
"license": "Лицензия",
|
||||
"loadingVersions": "Загрузка версий...",
|
||||
"nightlyVersion": "Ночная",
|
||||
"noDescription": "Описание отсутствует",
|
||||
"noResultsFound": "По вашему запросу ничего не найдено.",
|
||||
"nodePack": "Пакет Узлов",
|
||||
"packsSelected": "Выбрано пакетов",
|
||||
"repository": "Репозиторий",
|
||||
"searchPlaceholder": "Поиск",
|
||||
"selectVersion": "Выберите версию",
|
||||
"sort": {
|
||||
"downloads": "Самые популярные",
|
||||
"rating": "Рейтинг"
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"about": "关于",
|
||||
"add": "添加",
|
||||
"all": "全部",
|
||||
"apply": "应用",
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"capture": "捕获",
|
||||
@@ -383,13 +384,17 @@
|
||||
},
|
||||
"installSelected": "安装选定",
|
||||
"lastUpdated": "最后更新",
|
||||
"latestVersion": "最新",
|
||||
"license": "许可证",
|
||||
"loadingVersions": "正在加载版本...",
|
||||
"nightlyVersion": "每夜",
|
||||
"noDescription": "无可用描述",
|
||||
"noResultsFound": "未找到符合您搜索的结果。",
|
||||
"nodePack": "节点包",
|
||||
"packsSelected": "选定的包",
|
||||
"repository": "仓库",
|
||||
"searchPlaceholder": "搜索",
|
||||
"selectVersion": "选择版本",
|
||||
"sort": {
|
||||
"downloads": "最受欢迎",
|
||||
"rating": "评级"
|
||||
|
||||
Reference in New Issue
Block a user