Files
ComfyUI_frontend/src/components/dialog/content/manager/packCard/PackCard.vue
bymyself 9d028a4c53 [manager] Update UI components for new manager interface
Updated manager dialog components, pack cards, version selectors, and action buttons to work with the new manager API and state management structure.
2025-06-23 04:46:24 -07:00

191 lines
6.1 KiB
Vue

<template>
<Card
class="w-full h-full inline-flex flex-col justify-between items-start overflow-hidden rounded-lg shadow-elevation-3 dark-theme:bg-dark-elevation-2 transition-all duration-200"
:class="{
'selected-card': isSelected,
'opacity-60': isDisabled
}"
:pt="{
body: { class: 'p-0 flex flex-col w-full h-full rounded-lg gap-0' },
content: { class: 'flex-1 flex flex-col rounded-lg min-h-0' },
title: { class: 'w-full h-full rounded-t-lg cursor-pointer' },
footer: { class: 'p-0 m-0' }
}"
>
<template #title>
<PackBanner :node-pack="nodePack" />
</template>
<template #content>
<template v-if="isInstalling">
<div
class="self-stretch inline-flex flex-col justify-center items-center gap-2 h-full"
>
<ProgressSpinner />
<div
class="self-stretch text-center justify-start text-sm font-medium leading-none"
>
{{ $t('g.installing') }}...
</div>
</div>
</template>
<template v-else>
<div
class="self-stretch inline-flex flex-col justify-start items-start"
>
<div
class="px-4 py-3 inline-flex justify-start items-start cursor-pointer w-full"
>
<div
class="inline-flex flex-col justify-start items-start overflow-hidden gap-y-3 w-full"
>
<span
class="text-base font-bold truncate overflow-hidden text-ellipsis"
>
{{ nodePack.name }}
</span>
<p
v-if="nodePack.description"
class="flex-1 justify-start text-muted text-sm font-medium break-words overflow-hidden min-h-12 line-clamp-3 my-0 leading-5"
>
{{ nodePack.description }}
</p>
<div class="flex flex-col gap-y-2">
<div
class="self-stretch inline-flex justify-start items-center gap-1"
>
<div
v-if="nodesCount"
class="pr-2 py-1 flex justify-center text-sm items-center gap-1"
>
<div
class="text-center justify-center font-medium leading-3"
>
{{ nodesCount }} {{ $t('g.nodes') }}
</div>
</div>
<div class="px-2 py-1 flex justify-center items-center gap-1">
<div
v-if="isUpdateAvailable"
class="w-4 h-4 relative overflow-hidden"
>
<i class="pi pi-arrow-circle-up text-blue-600" />
</div>
<PackVersionBadge
:node-pack="nodePack"
:is-selected="isSelected"
/>
</div>
<div
v-if="formattedLatestVersionDate"
class="px-2 py-1 flex justify-center items-center gap-1 text-xs text-muted font-medium"
>
{{ formattedLatestVersionDate }}
</div>
</div>
<div class="flex">
<span
v-if="publisherName"
class="text-xs text-muted font-medium leading-3 max-w-40 truncate"
>
{{ publisherName }}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
</template>
<template #footer>
<ContentDivider :width="0.1" />
<PackCardFooter :node-pack="nodePack" />
</template>
</Card>
</template>
<script setup lang="ts">
import { whenever } from '@vueuse/core'
import Card from 'primevue/card'
import ProgressSpinner from 'primevue/progressspinner'
import { computed, provide, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import ContentDivider from '@/components/common/ContentDivider.vue'
import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBadge.vue'
import PackBanner from '@/components/dialog/content/manager/packBanner/PackBanner.vue'
import PackCardFooter from '@/components/dialog/content/manager/packCard/PackCardFooter.vue'
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import {
IsInstallingKey,
type MergedNodePack,
type RegistryPack,
isMergedNodePack
} from '@/types/comfyManagerTypes'
const { nodePack, isSelected = false } = defineProps<{
nodePack: MergedNodePack | RegistryPack
isSelected?: boolean
}>()
const { d } = useI18n()
const isInstalling = ref(false)
provide(IsInstallingKey, isInstalling)
const { isPackInstalled, isPackEnabled } = useComfyManagerStore()
const { isUpdateAvailable } = usePackUpdateStatus(nodePack)
const isDisabled = ref(false)
const managerStore = useComfyManagerStore()
// Watch the installedPacks object directly (which gets updated from WebSocket)
whenever(
() => managerStore.installedPacksIds,
() => {
const isInstalled = isPackInstalled(nodePack?.id)
isDisabled.value = isInstalled && !isPackEnabled(nodePack?.id)
// Update isInstalling state after installation is complete
if (isInstalling.value && isInstalled) isInstalling.value = false
}
)
const nodesCount = computed(() =>
isMergedNodePack(nodePack) ? nodePack.comfy_nodes?.length : undefined
)
const publisherName = computed(() => {
if (!nodePack) return null
const { publisher, author } = nodePack
return publisher?.name ?? publisher?.id ?? author
})
const formattedLatestVersionDate = computed(() => {
if (!nodePack.latest_version?.createdAt) return null
return d(new Date(nodePack.latest_version.createdAt), {
dateStyle: 'medium'
})
})
</script>
<style scoped>
.selected-card {
position: relative;
}
.selected-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 3px solid var(--p-primary-color);
border-radius: 0.5rem;
pointer-events: none;
z-index: 100;
}
</style>