Skip to content

Commit 65289b1

Browse files
Update to new card design (#4065)
Co-authored-by: filtered <[email protected]>
1 parent 9e2180d commit 65289b1

File tree

4 files changed

+137
-64
lines changed

4 files changed

+137
-64
lines changed

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -408,19 +408,30 @@ const handleGridContainerClick = (event: MouseEvent) => {
408408
409409
const hasMultipleSelections = computed(() => selectedNodePacks.value.length > 1)
410410
411+
// Track the last pack ID for which we've fetched full registry data
412+
const lastFetchedPackId = ref<string | null>(null)
413+
414+
// Whenever a single pack is selected, fetch its full info once
411415
whenever(selectedNodePack, async () => {
412416
// Cancel any in-flight requests from previously selected node pack
413417
getPackById.cancel()
414-
415-
if (!selectedNodePack.value?.id) return
416-
417418
// If only a single node pack is selected, fetch full node pack info from registry
419+
const pack = selectedNodePack.value
420+
if (!pack?.id) return
418421
if (hasMultipleSelections.value) return
419-
const data = await getPackById.call(selectedNodePack.value.id)
420-
421-
if (data?.id === selectedNodePack.value?.id) {
422-
// If selected node hasn't changed since request, merge registry & Algolia data
423-
selectedNodePacks.value = [merge(selectedNodePack.value, data)]
422+
// Only fetch if we haven't already for this pack
423+
if (lastFetchedPackId.value === pack.id) return
424+
const data = await getPackById.call(pack.id)
425+
// If selected node hasn't changed since request, merge registry & Algolia data
426+
if (data?.id === pack.id) {
427+
lastFetchedPackId.value = pack.id
428+
const mergedPack = merge({}, pack, data)
429+
selectedNodePacks.value = [mergedPack]
430+
// Replace pack in displayPacks so that children receive a fresh prop reference
431+
const idx = displayPacks.value.findIndex((p) => p.id === mergedPack.id)
432+
if (idx !== -1) {
433+
displayPacks.value.splice(idx, 1, mergedPack)
434+
}
424435
}
425436
})
426437
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<template>
2+
<img
3+
:src="isImageError ? DEFAULT_BANNER : imgSrc"
4+
:alt="nodePack.name + ' banner'"
5+
class="object-cover"
6+
:style="{ width: cssWidth, height: cssHeight }"
7+
@error="isImageError = true"
8+
/>
9+
</template>
10+
11+
<script setup lang="ts">
12+
import { computed, ref } from 'vue'
13+
14+
import { components } from '@/types/comfyRegistryTypes'
15+
16+
const DEFAULT_BANNER = '/assets/images/fallback-gradient-avatar.svg'
17+
18+
const {
19+
nodePack,
20+
width = '100%',
21+
height = '12rem'
22+
} = defineProps<{
23+
nodePack: components['schemas']['Node'] & { banner?: string } // Temporary measure until banner is in backend
24+
width?: string
25+
height?: string
26+
}>()
27+
28+
const isImageError = ref(false)
29+
const shouldShowFallback = computed(
30+
() => !nodePack.banner || nodePack.banner.trim() === '' || isImageError.value
31+
)
32+
const imgSrc = computed(() =>
33+
shouldShowFallback.value ? DEFAULT_BANNER : nodePack.banner
34+
)
35+
36+
const convertToCssValue = (value: string | number) =>
37+
typeof value === 'number' ? `${value}rem` : value
38+
39+
const cssWidth = computed(() => convertToCssValue(width))
40+
const cssHeight = computed(() => convertToCssValue(height))
41+
</script>

src/components/dialog/content/manager/packCard/PackCard.vue

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,15 @@
77
}"
88
:pt="{
99
body: { class: 'p-0 flex flex-col w-full h-full rounded-2xl gap-0' },
10-
content: { class: 'flex-1 flex flex-col rounded-2xl' },
11-
title: {
12-
class:
13-
'self-stretch w-full px-4 py-3 inline-flex justify-start items-center gap-6'
14-
},
10+
content: { class: 'flex-1 flex flex-col rounded-2xl min-h-0' },
11+
title: { class: 'w-full h-full rounded-t-lg cursor-pointer' },
1512
footer: { class: 'p-0 m-0' }
1613
}"
1714
>
1815
<template #title>
19-
<PackCardHeader :node-pack="nodePack" />
16+
<PackBanner :node-pack="nodePack" />
2017
</template>
2118
<template #content>
22-
<ContentDivider />
2319
<template v-if="isInstalling">
2420
<div
2521
class="self-stretch inline-flex flex-col justify-center items-center gap-2 h-full"
@@ -34,46 +30,63 @@
3430
</template>
3531
<template v-else>
3632
<div
37-
class="self-stretch px-4 py-3 inline-flex justify-start items-start cursor-pointer"
33+
class="self-stretch inline-flex flex-col justify-start items-start"
3834
>
39-
<PackIcon :node-pack="nodePack" />
4035
<div
41-
class="px-4 inline-flex flex-col justify-start items-start overflow-hidden"
36+
class="px-4 py-3 inline-flex justify-start items-start cursor-pointer w-full"
4237
>
43-
<span
44-
class="text-sm font-bold truncate overflow-hidden text-ellipsis"
45-
>
46-
{{ nodePack.name }}
47-
</span>
4838
<div
49-
class="self-stretch inline-flex justify-center items-center gap-2.5"
39+
class="inline-flex flex-col justify-start items-start overflow-hidden gap-y-3 w-full"
5040
>
41+
<span
42+
class="text-base font-bold truncate overflow-hidden text-ellipsis"
43+
>
44+
{{ nodePack.name }}
45+
</span>
5146
<p
5247
v-if="nodePack.description"
53-
class="flex-1 justify-start text-muted text-sm font-medium leading-3 break-words overflow-hidden min-h-12 line-clamp-3"
48+
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"
5449
>
5550
{{ nodePack.description }}
5651
</p>
57-
</div>
58-
<div
59-
class="self-stretch inline-flex justify-start items-center gap-2"
60-
>
61-
<div
62-
v-if="nodesCount"
63-
class="px-2 py-1 flex justify-center text-sm items-center gap-1"
64-
>
65-
<div class="text-center justify-center font-medium leading-3">
66-
{{ nodesCount }} {{ $t('g.nodes') }}
67-
</div>
68-
</div>
69-
<div class="px-2 py-1 flex justify-center items-center gap-1">
52+
<div class="flex flex-col gap-y-2">
7053
<div
71-
v-if="isUpdateAvailable"
72-
class="w-4 h-4 relative overflow-hidden"
54+
class="self-stretch inline-flex justify-start items-center gap-1"
7355
>
74-
<i class="pi pi-arrow-circle-up text-blue-600" />
56+
<div
57+
v-if="nodesCount"
58+
class="pr-2 py-1 flex justify-center text-sm items-center gap-1"
59+
>
60+
<div
61+
class="text-center justify-center font-medium leading-3"
62+
>
63+
{{ nodesCount }} {{ $t('g.nodes') }}
64+
</div>
65+
</div>
66+
<div class="px-2 py-1 flex justify-center items-center gap-1">
67+
<div
68+
v-if="isUpdateAvailable"
69+
class="w-4 h-4 relative overflow-hidden"
70+
>
71+
<i class="pi pi-arrow-circle-up text-blue-600" />
72+
</div>
73+
<PackVersionBadge :node-pack="nodePack" />
74+
</div>
75+
<div
76+
v-if="formattedLatestVersionDate"
77+
class="px-2 py-1 flex justify-center items-center gap-1 text-xs text-muted font-medium"
78+
>
79+
{{ formattedLatestVersionDate }}
80+
</div>
81+
</div>
82+
<div class="flex">
83+
<span
84+
v-if="publisherName"
85+
class="text-xs text-muted font-medium leading-3 max-w-40 truncate"
86+
>
87+
{{ publisherName }}
88+
</span>
7589
</div>
76-
<PackVersionBadge :node-pack="nodePack" />
7790
</div>
7891
</div>
7992
</div>
@@ -92,11 +105,12 @@ import { whenever } from '@vueuse/core'
92105
import Card from 'primevue/card'
93106
import ProgressSpinner from 'primevue/progressspinner'
94107
import { computed, provide, ref } from 'vue'
108+
import { useI18n } from 'vue-i18n'
95109
96110
import ContentDivider from '@/components/common/ContentDivider.vue'
97111
import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBadge.vue'
112+
import PackBanner from '@/components/dialog/content/manager/packBanner/PackBanner.vue'
98113
import PackCardFooter from '@/components/dialog/content/manager/packCard/PackCardFooter.vue'
99-
import PackIcon from '@/components/dialog/content/manager/packIcon/PackIcon.vue'
100114
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
101115
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
102116
import { IsInstallingKey } from '@/types/comfyManagerTypes'
@@ -107,6 +121,8 @@ const { nodePack, isSelected = false } = defineProps<{
107121
isSelected?: boolean
108122
}>()
109123
124+
const { d } = useI18n()
125+
110126
const isInstalling = ref(false)
111127
provide(IsInstallingKey, isInstalling)
112128
@@ -122,4 +138,19 @@ whenever(isInstalled, () => (isInstalling.value = false))
122138
123139
// TODO: remove type assertion once comfy_nodes is added to node (pack) info type in backend
124140
const nodesCount = computed(() => (nodePack as any).comfy_nodes?.length)
141+
142+
const publisherName = computed(() => {
143+
if (!nodePack) return null
144+
145+
const { publisher, author } = nodePack
146+
return publisher?.name ?? publisher?.id ?? author
147+
})
148+
149+
const formattedLatestVersionDate = computed(() => {
150+
if (!nodePack.latest_version?.createdAt) return null
151+
152+
return d(new Date(nodePack.latest_version.createdAt), {
153+
dateStyle: 'medium'
154+
})
155+
})
125156
</script>
Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,29 @@
11
<template>
22
<div
3-
class="flex justify-between px-5 py-4 text-xs text-muted font-medium leading-3"
3+
class="flex justify-between items-center px-4 py-2 text-xs text-muted font-medium leading-3"
44
>
5-
<div class="flex items-center gap-2 cursor-pointer">
6-
<span v-if="publisherName" class="max-w-40 truncate">
7-
{{ publisherName }}
8-
</span>
9-
</div>
10-
<div
11-
v-if="nodePack.latest_version?.createdAt"
12-
class="flex items-center gap-2 truncate"
13-
>
14-
{{ $t('g.updated') }}
15-
{{
16-
$d(new Date(nodePack.latest_version.createdAt), {
17-
dateStyle: 'medium'
18-
})
19-
}}
5+
<div v-if="nodePack.downloads" class="flex items-center gap-1.5">
6+
<i class="pi pi-download text-muted"></i>
7+
<span>{{ formattedDownloads }}</span>
208
</div>
9+
<PackInstallButton :node-packs="[nodePack]" />
2110
</div>
2211
</template>
2312

2413
<script setup lang="ts">
2514
import { computed } from 'vue'
15+
import { useI18n } from 'vue-i18n'
2616
17+
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
2718
import type { components } from '@/types/comfyRegistryTypes'
2819
2920
const { nodePack } = defineProps<{
3021
nodePack: components['schemas']['Node']
3122
}>()
3223
33-
const publisherName = computed(() => {
34-
if (!nodePack) return null
24+
const { n } = useI18n()
3525
36-
const { publisher, author } = nodePack
37-
return publisher?.name ?? publisher?.id ?? author
38-
})
26+
const formattedDownloads = computed(() =>
27+
nodePack.downloads ? n(nodePack.downloads) : ''
28+
)
3929
</script>

0 commit comments

Comments
 (0)