Files
ComfyUI_frontend/src/workbench/extensions/manager/components/manager/PackVersionBadge.vue
dante01yoon d834f2e578 refactor: rename text-xxxs/text-xxs to text-3xs/text-2xs
tailwind-merge incorrectly classifies text-xxxs as a color class,
causing it to clobber actual text color utilities like text-white.
Renaming to text-3xs/text-2xs follows Tailwind sizing conventions
and is correctly detected by tailwind-merge as font-size.
2026-03-27 22:45:24 +09:00

167 lines
4.8 KiB
Vue

<template>
<div>
<div
v-tooltip.top="
isDisabled ? $t('manager.enablePackToChangeVersion') : null
"
class="inline-flex items-center gap-1 rounded-2xl py-1 text-xs"
:class="{
'bg-dialog-surface 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-xs text-blue-600"
/>
<span>{{ installedVersion }}</span>
<i v-if="!isDisabled" class="pi pi-chevron-right text-2xs" />
</div>
<Popover
ref="popoverRef"
append-to="body"
:pt="{
root: {
class: 'before:hidden after:hidden [&_.p-popover-arrow]:hidden'
},
content: { class: 'p-0 shadow-lg' }
}"
@show="fixPopoverIntoViewport"
>
<PackVersionSelectorPopover
:installed-version="installedVersion"
:node-pack="nodePack"
@cancel="closeVersionSelector"
@submit="closeVersionSelector"
/>
</Popover>
</div>
</template>
<script setup lang="ts">
import Popover from 'primevue/popover'
import { valid as validSemver } from 'semver'
import { computed, nextTick, ref, watch } from 'vue'
import type { components } from '@/types/comfyRegistryTypes'
import PackVersionSelectorPopover from '@/workbench/extensions/manager/components/manager/PackVersionSelectorPopover.vue'
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
const TRUNCATED_HASH_LENGTH = 7
const {
nodePack,
isSelected,
fill = true
} = defineProps<{
nodePack: components['schemas']['Node']
isSelected: boolean
fill?: boolean
}>()
const { isUpdateAvailable } = usePackUpdateStatus(() => nodePack)
const popoverRef = ref()
const lastTargetEl = ref<HTMLElement | null>(null)
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 =
managerStore.installedPacks[nodePack.id]?.ver ??
nodePack.latest_version?.version ??
'nightly'
// If Git hash, truncate to 7 characters
return validSemver(version)
? version
: version.slice(0, TRUNCATED_HASH_LENGTH)
})
const toggleVersionSelector = (event: Event) => {
lastTargetEl.value = event.currentTarget as HTMLElement
popoverRef.value.toggle(event)
}
const closeVersionSelector = () => {
popoverRef.value.hide()
}
const fixPopoverIntoViewport = async () => {
await nextTick()
const popoverEl: HTMLElement | undefined =
popoverRef.value?.container ?? popoverRef.value?.$el
const targetEl = lastTargetEl.value
if (!popoverEl || !targetEl) return
requestAnimationFrame(() => {
const boundaryEl =
targetEl.closest('.p-dialog') ??
targetEl.closest('.manager-dialog') ??
targetEl.closest('[role="dialog"]') ??
document.documentElement
const boundary = boundaryEl.getBoundingClientRect()
const targetRect = targetEl.getBoundingClientRect()
popoverEl.style.transform = ''
const rect = popoverEl.getBoundingClientRect()
const M = 8 // keep away from dialog edge
const GAP = 10
const spaceBelow = boundary.bottom - targetRect.bottom
const spaceAbove = targetRect.top - boundary.top
// Decide side (below by default; flip to above if needed)
const placeAbove = spaceBelow < rect.height + GAP && spaceAbove > spaceBelow
// 1) Top with a GAP (prevents covering trigger)
let top = placeAbove
? targetRect.top - rect.height - GAP
: targetRect.bottom + GAP
// Clamp vertically
top = Math.min(top, boundary.bottom - rect.height - M)
top = Math.max(top, boundary.top + M)
// 2) Left (align to trigger, then clamp)
let left = targetRect.left
left = Math.min(left, boundary.right - rect.width - M)
left = Math.max(left, boundary.left + M)
// Apply position
popoverEl.style.top = `${Math.round(top)}px`
popoverEl.style.left = `${Math.round(left)}px`
popoverEl.classList.toggle('p-popover-flipped', placeAbove)
})
}
// If the card is unselected, automatically close the version selector popover
watch(
() => isSelected,
(isSelected, wasSelected) => {
if (wasSelected && !isSelected) {
closeVersionSelector()
}
}
)
</script>