Skip to content

Commit 63181a1

Browse files
viva-jinyiclaude
andauthored
[Manager] Standardize Card Aspect Ratios & Enhance UI (#4271)
Co-authored-by: Claude <[email protected]>
1 parent e17ca7c commit 63181a1

File tree

6 files changed

+97
-110
lines changed

6 files changed

+97
-110
lines changed

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

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { VueWrapper, mount } from '@vue/test-utils'
22
import { createPinia } from 'pinia'
3-
import Button from 'primevue/button'
43
import PrimeVue from 'primevue/config'
54
import { beforeEach, describe, expect, it, vi } from 'vitest'
65
import { nextTick } from 'vue'
@@ -33,6 +32,12 @@ vi.mock('@/stores/comfyManagerStore', () => ({
3332
}))
3433
}))
3534

35+
vi.mock('@/composables/nodePack/usePackUpdateStatus', () => ({
36+
usePackUpdateStatus: vi.fn(() => ({
37+
isUpdateAvailable: false
38+
}))
39+
}))
40+
3641
const mockToggle = vi.fn()
3742
const mockHide = vi.fn()
3843
const PopoverStub = {
@@ -78,9 +83,9 @@ describe('PackVersionBadge', () => {
7883
it('renders with installed version from store', () => {
7984
const wrapper = mountComponent()
8085

81-
const button = wrapper.findComponent(Button)
82-
expect(button.exists()).toBe(true)
83-
expect(button.props('label')).toBe('1.5.0') // From mockInstalledPacks
86+
const badge = wrapper.find('[role="button"]')
87+
expect(badge.exists()).toBe(true)
88+
expect(badge.find('span').text()).toBe('1.5.0') // From mockInstalledPacks
8489
})
8590

8691
it('falls back to latest_version when not installed', () => {
@@ -97,9 +102,9 @@ describe('PackVersionBadge', () => {
97102
props: { nodePack: uninstalledPack }
98103
})
99104

100-
const button = wrapper.findComponent(Button)
101-
expect(button.exists()).toBe(true)
102-
expect(button.props('label')).toBe('3.0.0') // From latest_version
105+
const badge = wrapper.find('[role="button"]')
106+
expect(badge.exists()).toBe(true)
107+
expect(badge.find('span').text()).toBe('3.0.0') // From latest_version
103108
})
104109

105110
it('falls back to NIGHTLY when no latest_version and not installed', () => {
@@ -113,9 +118,9 @@ describe('PackVersionBadge', () => {
113118
props: { nodePack: noVersionPack }
114119
})
115120

116-
const button = wrapper.findComponent(Button)
117-
expect(button.exists()).toBe(true)
118-
expect(button.props('label')).toBe(SelectedVersion.NIGHTLY)
121+
const badge = wrapper.find('[role="button"]')
122+
expect(badge.exists()).toBe(true)
123+
expect(badge.find('span').text()).toBe(SelectedVersion.NIGHTLY)
119124
})
120125

121126
it('falls back to NIGHTLY when nodePack.id is missing', () => {
@@ -127,16 +132,16 @@ describe('PackVersionBadge', () => {
127132
props: { nodePack: invalidPack }
128133
})
129134

130-
const button = wrapper.findComponent(Button)
131-
expect(button.exists()).toBe(true)
132-
expect(button.props('label')).toBe(SelectedVersion.NIGHTLY)
135+
const badge = wrapper.find('[role="button"]')
136+
expect(badge.exists()).toBe(true)
137+
expect(badge.find('span').text()).toBe(SelectedVersion.NIGHTLY)
133138
})
134139

135140
it('toggles the popover when button is clicked', async () => {
136141
const wrapper = mountComponent()
137142

138-
// Click the button
139-
await wrapper.findComponent(Button).trigger('click')
143+
// Click the badge
144+
await wrapper.find('[role="button"]').trigger('click')
140145

141146
// Verify that the toggle method was called
142147
expect(mockToggle).toHaveBeenCalled()

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

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
<template>
2-
<div class="relative">
3-
<Button
4-
:label="installedVersion"
5-
severity="secondary"
6-
icon="pi pi-chevron-right"
7-
icon-pos="right"
8-
class="rounded-xl text-xs tracking-tighter p-0"
9-
:pt="{
10-
label: { class: 'pl-2 pr-0 py-0.5' },
11-
icon: { class: 'text-xs pl-0 pr-2 py-0.5' }
12-
}"
2+
<div>
3+
<div
4+
class="inline-flex items-center gap-1 rounded-2xl text-xs cursor-pointer px-2 py-1"
5+
:class="{ 'bg-gray-100 dark-theme:bg-neutral-700': fill }"
136
aria-haspopup="true"
7+
role="button"
8+
tabindex="0"
149
@click="toggleVersionSelector"
15-
/>
10+
@keydown.enter="toggleVersionSelector"
11+
@keydown.space="toggleVersionSelector"
12+
>
13+
<i
14+
v-if="isUpdateAvailable"
15+
class="pi pi-arrow-circle-up text-blue-600"
16+
style="font-size: 8px"
17+
/>
18+
<span>{{ installedVersion }}</span>
19+
<i class="pi pi-chevron-right" style="font-size: 8px" />
20+
</div>
1621

1722
<Popover
1823
ref="popoverRef"
@@ -31,23 +36,29 @@
3136
</template>
3237

3338
<script setup lang="ts">
34-
import Button from 'primevue/button'
3539
import Popover from 'primevue/popover'
3640
import { computed, ref, watch } from 'vue'
3741
3842
import PackVersionSelectorPopover from '@/components/dialog/content/manager/PackVersionSelectorPopover.vue'
43+
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
3944
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
4045
import { SelectedVersion } from '@/types/comfyManagerTypes'
4146
import { components } from '@/types/comfyRegistryTypes'
4247
import { isSemVer } from '@/utils/formatUtil'
4348
4449
const TRUNCATED_HASH_LENGTH = 7
4550
46-
const { nodePack, isSelected } = defineProps<{
51+
const {
52+
nodePack,
53+
isSelected,
54+
fill = true
55+
} = defineProps<{
4756
nodePack: components['schemas']['Node']
4857
isSelected: boolean
58+
fill?: boolean
4959
}>()
5060
61+
const { isUpdateAvailable } = usePackUpdateStatus(nodePack)
5162
const popoverRef = ref()
5263
5364
const managerStore = useComfyManagerStore()

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<Button
33
outlined
4-
class="!m-0 p-0 rounded-lg"
4+
class="!m-0 p-0 rounded-lg text-gray-900 dark-theme:text-gray-50"
55
:class="[
66
variant === 'black'
77
? 'bg-neutral-900 text-white border-neutral-900'
@@ -12,7 +12,7 @@
1212
v-bind="$attrs"
1313
@click="onClick"
1414
>
15-
<span class="py-2.5 px-3 whitespace-nowrap">
15+
<span class="py-2 px-3 whitespace-nowrap">
1616
<template v-if="loading">
1717
{{ loadingMessage ?? $t('g.loading') }}
1818
</template>

src/components/dialog/content/manager/packBanner/PackBanner.vue

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div :style="{ width: cssWidth, height: cssHeight }" class="overflow-hidden">
2+
<div class="w-full aspect-[7/3] overflow-hidden">
33
<!-- default banner show -->
44
<div v-if="showDefaultBanner" class="w-full h-full">
55
<img
@@ -41,24 +41,12 @@ import { components } from '@/types/comfyRegistryTypes'
4141
4242
const DEFAULT_BANNER = '/assets/images/fallback-gradient-avatar.svg'
4343
44-
const {
45-
nodePack,
46-
width = '100%',
47-
height = '12rem'
48-
} = defineProps<{
44+
const { nodePack } = defineProps<{
4945
nodePack: components['schemas']['Node']
50-
width?: string
51-
height?: string
5246
}>()
5347
5448
const isImageError = ref(false)
5549
5650
const showDefaultBanner = computed(() => !nodePack.banner_url && !nodePack.icon)
5751
const imgSrc = computed(() => nodePack.banner_url || nodePack.icon)
58-
59-
const convertToCssValue = (value: string | number) =>
60-
typeof value === 'number' ? `${value}rem` : value
61-
62-
const cssWidth = computed(() => convertToCssValue(width))
63-
const cssHeight = computed(() => convertToCssValue(height))
6452
</script>

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

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
body: { class: 'p-0 flex flex-col w-full h-full rounded-lg gap-0' },
1010
content: { class: 'flex-1 flex flex-col rounded-lg min-h-0' },
1111
title: { class: 'w-full h-full rounded-t-lg cursor-pointer' },
12-
footer: { class: 'p-0 m-0' }
12+
footer: {
13+
class: 'p-0 m-0 flex flex-col gap-0',
14+
style: {
15+
borderTop: isLightTheme ? '1px solid #f4f4f4' : '1px solid #2C2C2C'
16+
}
17+
}
1318
}"
1419
>
1520
<template #title>
@@ -29,75 +34,50 @@
2934
</div>
3035
</template>
3136
<template v-else>
32-
<div
33-
class="self-stretch inline-flex flex-col justify-start items-start"
34-
>
35-
<div
36-
class="px-4 py-3 inline-flex justify-start items-start cursor-pointer w-full"
37-
>
38-
<div
39-
class="inline-flex flex-col justify-start items-start overflow-hidden gap-y-3 w-full"
37+
<div class="pt-4 px-4 pb-3 w-full h-full">
38+
<div class="flex flex-col gap-y-1 w-full h-full">
39+
<span
40+
class="text-sm font-bold truncate overflow-hidden text-ellipsis"
41+
>
42+
{{ nodePack.name }}
43+
</span>
44+
<p
45+
v-if="nodePack.description"
46+
class="flex-1 text-muted text-xs font-medium break-words overflow-hidden min-h-12 line-clamp-3 my-0 leading-4 mb-1 overflow-hidden"
4047
>
41-
<span
42-
class="text-base font-bold truncate overflow-hidden text-ellipsis"
43-
>
44-
{{ nodePack.name }}
45-
</span>
46-
<p
47-
v-if="nodePack.description"
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"
49-
>
50-
{{ nodePack.description }}
51-
</p>
52-
<div class="flex flex-col gap-y-2">
48+
{{ nodePack.description }}
49+
</p>
50+
<div class="flex flex-col gap-y-2">
51+
<div class="flex-1 flex items-center gap-2">
52+
<div v-if="nodesCount" class="p-2 pl-0 text-xs">
53+
{{ nodesCount }} {{ $t('g.nodes') }}
54+
</div>
55+
<PackVersionBadge
56+
:node-pack="nodePack"
57+
:is-selected="isSelected"
58+
:fill="false"
59+
/>
5360
<div
54-
class="self-stretch inline-flex justify-start items-center gap-1"
61+
v-if="formattedLatestVersionDate"
62+
class="px-2 py-1 flex justify-center items-center gap-1 text-xs text-muted font-medium"
5563
>
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
74-
:node-pack="nodePack"
75-
:is-selected="isSelected"
76-
/>
77-
</div>
78-
<div
79-
v-if="formattedLatestVersionDate"
80-
class="px-2 py-1 flex justify-center items-center gap-1 text-xs text-muted font-medium"
81-
>
82-
{{ formattedLatestVersionDate }}
83-
</div>
84-
</div>
85-
<div class="flex">
86-
<span
87-
v-if="publisherName"
88-
class="text-xs text-muted font-medium leading-3 max-w-40 truncate"
89-
>
90-
{{ publisherName }}
91-
</span>
64+
{{ formattedLatestVersionDate }}
9265
</div>
9366
</div>
67+
<div class="flex">
68+
<span
69+
v-if="publisherName"
70+
class="text-xs text-muted font-medium leading-3 max-w-40 truncate"
71+
>
72+
{{ publisherName }}
73+
</span>
74+
</div>
9475
</div>
9576
</div>
9677
</div>
9778
</template>
9879
</template>
9980
<template #footer>
100-
<ContentDivider :width="0.1" />
10181
<PackCardFooter :node-pack="nodePack" />
10282
</template>
10383
</Card>
@@ -110,12 +90,11 @@ import ProgressSpinner from 'primevue/progressspinner'
11090
import { computed, provide, ref } from 'vue'
11191
import { useI18n } from 'vue-i18n'
11292
113-
import ContentDivider from '@/components/common/ContentDivider.vue'
11493
import PackVersionBadge from '@/components/dialog/content/manager/PackVersionBadge.vue'
11594
import PackBanner from '@/components/dialog/content/manager/packBanner/PackBanner.vue'
11695
import PackCardFooter from '@/components/dialog/content/manager/packCard/PackCardFooter.vue'
117-
import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus'
11896
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
97+
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
11998
import {
12099
IsInstallingKey,
121100
type MergedNodePack,
@@ -130,11 +109,15 @@ const { nodePack, isSelected = false } = defineProps<{
130109
131110
const { d } = useI18n()
132111
112+
const colorPaletteStore = useColorPaletteStore()
113+
const isLightTheme = computed(
114+
() => colorPaletteStore.completedActivePalette.light_theme
115+
)
116+
133117
const isInstalling = ref(false)
134118
provide(IsInstallingKey, isInstalling)
135119
136120
const { isPackInstalled, isPackEnabled } = useComfyManagerStore()
137-
const { isUpdateAvailable } = usePackUpdateStatus(nodePack)
138121
139122
const isInstalled = computed(() => isPackInstalled(nodePack?.id))
140123
const isDisabled = computed(
@@ -167,14 +150,14 @@ const formattedLatestVersionDate = computed(() => {
167150
position: relative;
168151
}
169152
170-
.selected-card::before {
153+
.selected-card::after {
171154
content: '';
172155
position: absolute;
173156
top: 0;
174157
left: 0;
175158
right: 0;
176159
bottom: 0;
177-
border: 3px solid var(--p-primary-color);
160+
border: 4px solid var(--p-primary-color);
178161
border-radius: 0.5rem;
179162
pointer-events: none;
180163
z-index: 100;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div
3-
class="flex justify-between items-center px-4 py-2 text-xs text-muted font-medium leading-3"
3+
class="min-h-12 flex justify-between items-center px-4 py-2 text-xs text-muted font-medium leading-3"
44
>
55
<div v-if="nodePack.downloads" class="flex items-center gap-1.5">
66
<i class="pi pi-download text-muted"></i>

0 commit comments

Comments
 (0)