mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
chore: manager dialog modified
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
v-tooltip.right="{
|
v-tooltip.right="{
|
||||||
value: tooltipText,
|
value: tooltipText,
|
||||||
disabled: !isOverflowing,
|
disabled: !isOverflowing,
|
||||||
pt: { text: { class: 'whitespace-nowrap' } }
|
pt: { text: { class: 'w-max whitespace-nowrap' } }
|
||||||
}"
|
}"
|
||||||
class="flex cursor-pointer items-center-safe gap-2 rounded-md px-4 py-3 text-sm transition-colors text-base-foreground"
|
class="flex cursor-pointer items-center-safe gap-2 rounded-md px-4 py-3 text-sm transition-colors text-base-foreground"
|
||||||
:class="
|
:class="
|
||||||
|
|||||||
@@ -295,6 +295,17 @@
|
|||||||
"changingVersion": "Changing version from {from} to {to}",
|
"changingVersion": "Changing version from {from} to {to}",
|
||||||
"dependencies": "Dependencies",
|
"dependencies": "Dependencies",
|
||||||
"inWorkflow": "In Workflow",
|
"inWorkflow": "In Workflow",
|
||||||
|
"nav": {
|
||||||
|
"allExtensions": "All Extensions",
|
||||||
|
"notInstalled": "Not Installed",
|
||||||
|
"installedSection": "INSTALLED",
|
||||||
|
"allInstalled": "All installed",
|
||||||
|
"updatesAvailable": "Updates Available",
|
||||||
|
"conflicting": "Conflicting",
|
||||||
|
"inWorkflowSection": "IN WORKFLOW",
|
||||||
|
"allInWorkflow": "All in: {workflowName}",
|
||||||
|
"missingNodes": "Missing Nodes"
|
||||||
|
},
|
||||||
"infoPanelEmpty": "Click an item to see the info",
|
"infoPanelEmpty": "Click an item to see the info",
|
||||||
"applyChanges": "Apply Changes",
|
"applyChanges": "Apply Changes",
|
||||||
"restartToApplyChanges": "To apply changes, please restart ComfyUI",
|
"restartToApplyChanges": "To apply changes, please restart ComfyUI",
|
||||||
|
|||||||
@@ -16,32 +16,63 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex w-full items-center justify-between gap-2">
|
||||||
<SingleSelect
|
<div class="flex items-center gap-2">
|
||||||
v-model="searchMode"
|
<SingleSelect
|
||||||
class="min-w-34"
|
v-model="searchMode"
|
||||||
:options="filterOptions"
|
class="min-w-34"
|
||||||
|
:options="filterOptions"
|
||||||
|
/>
|
||||||
|
<AutoCompletePlus
|
||||||
|
v-model.lazy="searchQuery"
|
||||||
|
:suggestions="suggestions"
|
||||||
|
:placeholder="$t('manager.searchPlaceholder')"
|
||||||
|
:complete-on-focus="false"
|
||||||
|
:delay="8"
|
||||||
|
option-label="query"
|
||||||
|
class="w-full min-w-md max-w-lg"
|
||||||
|
:pt="{
|
||||||
|
root: { class: 'relative' },
|
||||||
|
pcInputText: {
|
||||||
|
root: {
|
||||||
|
autofocus: true,
|
||||||
|
class:
|
||||||
|
'w-full h-10 rounded-lg bg-comfy-input text-comfy-input-foreground border-none outline-none text-sm'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
overlay: {
|
||||||
|
class: 'bg-comfy-input rounded-lg mt-1 shadow-lg border border-border-default'
|
||||||
|
},
|
||||||
|
list: { class: 'p-1' },
|
||||||
|
option: {
|
||||||
|
class:
|
||||||
|
'px-3 py-2 rounded hover:bg-button-hover-surface cursor-pointer text-sm'
|
||||||
|
},
|
||||||
|
loader: { style: 'display: none' }
|
||||||
|
}"
|
||||||
|
:show-empty-message="false"
|
||||||
|
@complete="stubTrue"
|
||||||
|
@option-select="onOptionSelect"
|
||||||
|
>
|
||||||
|
<template #dropdownicon>
|
||||||
|
<i
|
||||||
|
class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</AutoCompletePlus>
|
||||||
|
</div>
|
||||||
|
<PackInstallButton
|
||||||
|
v-if="isMissingTab && missingNodePacks.length > 0"
|
||||||
|
:disabled="isMissingLoading || !!missingError"
|
||||||
|
:node-packs="missingNodePacks"
|
||||||
|
size="lg"
|
||||||
|
:label="$t('manager.installAllMissingNodes')"
|
||||||
/>
|
/>
|
||||||
<AutoCompletePlus
|
<PackUpdateButton
|
||||||
v-model.lazy="searchQuery"
|
v-if="isUpdateAvailableTab && hasUpdateAvailable"
|
||||||
:suggestions="suggestions"
|
:node-packs="enabledUpdateAvailableNodePacks"
|
||||||
:placeholder="$t('manager.searchPlaceholder')"
|
:has-disabled-update-packs="hasDisabledUpdatePacks"
|
||||||
:complete-on-focus="false"
|
size="lg"
|
||||||
:delay="8"
|
|
||||||
option-label="query"
|
|
||||||
class="w-full min-w-md max-w-lg"
|
|
||||||
:pt="{
|
|
||||||
pcInputText: {
|
|
||||||
root: {
|
|
||||||
autofocus: true,
|
|
||||||
class: 'w-full rounded-lg h-10'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loader: { style: 'display: none' }
|
|
||||||
}"
|
|
||||||
:show-empty-message="false"
|
|
||||||
@complete="stubTrue"
|
|
||||||
@option-select="onOptionSelect"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -79,37 +110,18 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters Row -->
|
<!-- Sort Options -->
|
||||||
<div class="relative flex flex-wrap justify-between gap-2 px-6 pb-4">
|
<div class="flex justify-end px-6 pb-4">
|
||||||
<div>
|
<SingleSelect
|
||||||
<PackInstallButton
|
v-model="sortField"
|
||||||
v-if="isMissingTab && missingNodePacks.length > 0"
|
:label="$t('g.sort')"
|
||||||
:disabled="isMissingLoading || !!missingError"
|
:options="availableSortOptions"
|
||||||
:node-packs="missingNodePacks"
|
class="w-48"
|
||||||
size="lg"
|
>
|
||||||
:label="$t('manager.installAllMissingNodes')"
|
<template #icon>
|
||||||
/>
|
<i class="icon-[lucide--arrow-up-down] text-muted-foreground" />
|
||||||
<PackUpdateButton
|
</template>
|
||||||
v-if="isUpdateAvailableTab && hasUpdateAvailable"
|
</SingleSelect>
|
||||||
:node-packs="enabledUpdateAvailableNodePacks"
|
|
||||||
:has-disabled-update-packs="hasDisabledUpdatePacks"
|
|
||||||
size="lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sort Options on right -->
|
|
||||||
<div>
|
|
||||||
<SingleSelect
|
|
||||||
v-model="sortField"
|
|
||||||
:label="$t('g.sort')"
|
|
||||||
:options="availableSortOptions"
|
|
||||||
class="w-48"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-[lucide--arrow-up-down] text-muted-foreground" />
|
|
||||||
</template>
|
|
||||||
</SingleSelect>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -118,7 +130,7 @@
|
|||||||
<GridSkeleton :grid-style="GRID_STYLE" :skeleton-card-count />
|
<GridSkeleton :grid-style="GRID_STYLE" :skeleton-card-count />
|
||||||
</div>
|
</div>
|
||||||
<NoResultsPlaceholder
|
<NoResultsPlaceholder
|
||||||
v-else-if="searchResults.length === 0"
|
v-else-if="displayPacks.length === 0"
|
||||||
:title="
|
:title="
|
||||||
comfyManagerStore.error
|
comfyManagerStore.error
|
||||||
? $t('manager.errorConnecting')
|
? $t('manager.errorConnecting')
|
||||||
@@ -130,7 +142,7 @@
|
|||||||
: $t('manager.tryDifferentSearch')
|
: $t('manager.tryDifferentSearch')
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div v-else class="h-full" @click="handleGridContainerClick">
|
<div v-else class="h-full w-full" @click="handleGridContainerClick">
|
||||||
<VirtualGrid
|
<VirtualGrid
|
||||||
id="results-grid"
|
id="results-grid"
|
||||||
:items="resultsWithKeys"
|
:items="resultsWithKeys"
|
||||||
@@ -185,9 +197,10 @@ import Button from '@/components/ui/button/Button.vue'
|
|||||||
import BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'
|
import BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'
|
||||||
import LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'
|
import LeftSidePanel from '@/components/widget/panel/LeftSidePanel.vue'
|
||||||
import { useExternalLink } from '@/composables/useExternalLink'
|
import { useExternalLink } from '@/composables/useExternalLink'
|
||||||
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||||
import type { components } from '@/types/comfyRegistryTypes'
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import type { NavItemData } from '@/types/navTypes'
|
import type { NavGroupData, NavItemData } from '@/types/navTypes'
|
||||||
import { OnCloseKey } from '@/types/widgetTypes'
|
import { OnCloseKey } from '@/types/widgetTypes'
|
||||||
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
||||||
import PackUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackUpdateButton.vue'
|
import PackUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackUpdateButton.vue'
|
||||||
@@ -195,14 +208,13 @@ import InfoPanel from '@/workbench/extensions/manager/components/manager/infoPan
|
|||||||
import InfoPanelMultiItem from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelMultiItem.vue'
|
import InfoPanelMultiItem from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelMultiItem.vue'
|
||||||
import PackCard from '@/workbench/extensions/manager/components/manager/packCard/PackCard.vue'
|
import PackCard from '@/workbench/extensions/manager/components/manager/packCard/PackCard.vue'
|
||||||
import GridSkeleton from '@/workbench/extensions/manager/components/manager/skeleton/GridSkeleton.vue'
|
import GridSkeleton from '@/workbench/extensions/manager/components/manager/skeleton/GridSkeleton.vue'
|
||||||
import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks'
|
|
||||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
||||||
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
|
||||||
import { useUpdateAvailableNodes } from '@/workbench/extensions/manager/composables/nodePack/useUpdateAvailableNodes'
|
import { useUpdateAvailableNodes } from '@/workbench/extensions/manager/composables/nodePack/useUpdateAvailableNodes'
|
||||||
import { useWorkflowPacks } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
|
|
||||||
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
|
||||||
|
import { useManagerDisplayPacks } from '@/workbench/extensions/manager/composables/useManagerDisplayPacks'
|
||||||
import { useManagerStatePersistence } from '@/workbench/extensions/manager/composables/useManagerStatePersistence'
|
import { useManagerStatePersistence } from '@/workbench/extensions/manager/composables/useManagerStatePersistence'
|
||||||
import { useRegistrySearch } from '@/workbench/extensions/manager/composables/useRegistrySearch'
|
import { useRegistrySearch } from '@/workbench/extensions/manager/composables/useRegistrySearch'
|
||||||
|
import { useConflictDetectionStore } from '@/workbench/extensions/manager/stores/conflictDetectionStore'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||||
|
|
||||||
@@ -218,14 +230,16 @@ const { buildDocsUrl } = useExternalLink()
|
|||||||
const comfyManagerStore = useComfyManagerStore()
|
const comfyManagerStore = useComfyManagerStore()
|
||||||
const { getPackById } = useComfyRegistryStore()
|
const { getPackById } = useComfyRegistryStore()
|
||||||
const conflictAcknowledgment = useConflictAcknowledgment()
|
const conflictAcknowledgment = useConflictAcknowledgment()
|
||||||
|
const conflictDetectionStore = useConflictDetectionStore()
|
||||||
|
const workflowStore = useWorkflowStore()
|
||||||
const persistedState = useManagerStatePersistence()
|
const persistedState = useManagerStatePersistence()
|
||||||
const initialState = persistedState.loadStoredState()
|
const initialState = persistedState.loadStoredState()
|
||||||
|
|
||||||
const GRID_STYLE = {
|
const GRID_STYLE = {
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(17rem, 1fr))',
|
gridTemplateColumns: 'repeat(auto-fill, minmax(14rem, 1fr))',
|
||||||
gap: '1.5rem',
|
gap: '1rem',
|
||||||
padding: '0'
|
padding: '0.5rem'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -248,32 +262,84 @@ const {
|
|||||||
hasDisabledUpdatePacks
|
hasDisabledUpdatePacks
|
||||||
} = useUpdateAvailableNodes()
|
} = useUpdateAvailableNodes()
|
||||||
|
|
||||||
|
// Get the current workflow name for the nav item
|
||||||
|
const workflowName = computed(
|
||||||
|
() => workflowStore.activeWorkflow?.filename ?? t('manager.inWorkflow')
|
||||||
|
)
|
||||||
|
|
||||||
// Navigation items for LeftSidePanel
|
// Navigation items for LeftSidePanel
|
||||||
const navItems = computed<NavItemData[]>(() => [
|
const navItems = computed<(NavItemData | NavGroupData)[]>(() => [
|
||||||
{ id: ManagerTab.All, label: t('g.all'), icon: 'pi pi-list' },
|
|
||||||
{ id: ManagerTab.Installed, label: t('g.installed'), icon: 'pi pi-box' },
|
|
||||||
{
|
{
|
||||||
id: ManagerTab.Workflow,
|
id: ManagerTab.All,
|
||||||
label: t('manager.inWorkflow'),
|
label: t('manager.nav.allExtensions'),
|
||||||
icon: 'pi pi-folder'
|
icon: 'icon-[lucide--list]'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ManagerTab.Missing,
|
id: ManagerTab.NotInstalled,
|
||||||
label: t('g.missing'),
|
label: t('manager.nav.notInstalled'),
|
||||||
icon: 'pi pi-exclamation-circle'
|
icon: 'icon-[lucide--globe]'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ManagerTab.UpdateAvailable,
|
title: t('manager.nav.installedSection'),
|
||||||
label: t('g.updateAvailable'),
|
items: [
|
||||||
icon: 'pi pi-sync'
|
{
|
||||||
|
id: ManagerTab.AllInstalled,
|
||||||
|
label: t('manager.nav.allInstalled'),
|
||||||
|
icon: 'icon-[lucide--download]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ManagerTab.UpdateAvailable,
|
||||||
|
label: t('manager.nav.updatesAvailable'),
|
||||||
|
icon: 'icon-[lucide--refresh-cw]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ManagerTab.Conflicting,
|
||||||
|
label: t('manager.nav.conflicting'),
|
||||||
|
icon: 'icon-[lucide--triangle-alert]',
|
||||||
|
badge: conflictDetectionStore.conflictedPackages.length || undefined
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('manager.nav.inWorkflowSection'),
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: ManagerTab.Workflow,
|
||||||
|
label: t('manager.nav.allInWorkflow', {
|
||||||
|
workflowName: workflowName.value
|
||||||
|
}),
|
||||||
|
icon: 'icon-[lucide--share-2]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ManagerTab.Missing,
|
||||||
|
label: t('manager.nav.missingNodes'),
|
||||||
|
icon: 'icon-[lucide--triangle-alert]'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
const initialTabId = initialTab ?? initialState.selectedTabId ?? ManagerTab.All
|
const initialTabId = initialTab ?? initialState.selectedTabId ?? ManagerTab.All
|
||||||
const selectedNavId = ref<string | null>(initialTabId)
|
const selectedNavId = ref<string | null>(initialTabId)
|
||||||
|
|
||||||
|
// Helper to find a nav item by id in the nested structure
|
||||||
|
const findNavItemById = (
|
||||||
|
items: (NavItemData | NavGroupData)[],
|
||||||
|
id: string | null
|
||||||
|
): NavItemData | undefined => {
|
||||||
|
for (const item of items) {
|
||||||
|
if ('items' in item) {
|
||||||
|
const found = item.items.find((subItem) => subItem.id === id)
|
||||||
|
if (found) return found
|
||||||
|
} else if (item.id === id) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const selectedTab = computed(() =>
|
const selectedTab = computed(() =>
|
||||||
navItems.value.find((item) => item.id === selectedNavId.value)
|
findNavItemById(navItems.value, selectedNavId.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -318,120 +384,20 @@ const isInitialLoad = computed(
|
|||||||
() => searchResults.value.length === 0 && searchQuery.value === ''
|
() => searchResults.value.length === 0 && searchQuery.value === ''
|
||||||
)
|
)
|
||||||
|
|
||||||
const isEmptySearch = computed(() => searchQuery.value === '')
|
// Use the new composable for tab-based display packs
|
||||||
const displayPacks = ref<components['schemas']['Node'][]>([])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
startFetchInstalled,
|
displayPacks,
|
||||||
filterInstalledPack,
|
isLoading: isTabLoading,
|
||||||
installedPacks,
|
workflowPacks
|
||||||
isLoading: isLoadingInstalled,
|
} = useManagerDisplayPacks(selectedNavId, searchResults, searchQuery, sortField)
|
||||||
isReady: installedPacksReady
|
|
||||||
} = useInstalledPacks()
|
|
||||||
|
|
||||||
const {
|
|
||||||
startFetchWorkflowPacks,
|
|
||||||
filterWorkflowPack,
|
|
||||||
workflowPacks,
|
|
||||||
isLoading: isLoadingWorkflow,
|
|
||||||
isReady: workflowPacksReady
|
|
||||||
} = useWorkflowPacks()
|
|
||||||
|
|
||||||
const filterMissingPacks = (packs: components['schemas']['Node'][]) =>
|
|
||||||
packs.filter((pack) => !comfyManagerStore.isPackInstalled(pack.id))
|
|
||||||
|
|
||||||
|
// Tab helpers for template
|
||||||
const isUpdateAvailableTab = computed(
|
const isUpdateAvailableTab = computed(
|
||||||
() => selectedTab.value?.id === ManagerTab.UpdateAvailable
|
() => selectedTab.value?.id === ManagerTab.UpdateAvailable
|
||||||
)
|
)
|
||||||
const isInstalledTab = computed(
|
|
||||||
() => selectedTab.value?.id === ManagerTab.Installed
|
|
||||||
)
|
|
||||||
const isMissingTab = computed(
|
const isMissingTab = computed(
|
||||||
() => selectedTab.value?.id === ManagerTab.Missing
|
() => selectedTab.value?.id === ManagerTab.Missing
|
||||||
)
|
)
|
||||||
const isWorkflowTab = computed(
|
|
||||||
() => selectedTab.value?.id === ManagerTab.Workflow
|
|
||||||
)
|
|
||||||
const isAllTab = computed(() => selectedTab.value?.id === ManagerTab.All)
|
|
||||||
|
|
||||||
const isOutdatedPack = (pack: components['schemas']['Node']) => {
|
|
||||||
const { isUpdateAvailable } = usePackUpdateStatus(pack)
|
|
||||||
return isUpdateAvailable.value === true
|
|
||||||
}
|
|
||||||
const filterOutdatedPacks = (packs: components['schemas']['Node'][]) =>
|
|
||||||
packs.filter(isOutdatedPack)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
[isUpdateAvailableTab, installedPacks],
|
|
||||||
async () => {
|
|
||||||
if (!isUpdateAvailableTab.value) return
|
|
||||||
|
|
||||||
if (!isEmptySearch.value) {
|
|
||||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
|
||||||
} else if (
|
|
||||||
!installedPacks.value.length &&
|
|
||||||
!installedPacksReady.value &&
|
|
||||||
!isLoadingInstalled.value
|
|
||||||
) {
|
|
||||||
await startFetchInstalled()
|
|
||||||
} else {
|
|
||||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
[isInstalledTab, installedPacks],
|
|
||||||
async () => {
|
|
||||||
if (!isInstalledTab.value) return
|
|
||||||
|
|
||||||
if (!isEmptySearch.value) {
|
|
||||||
displayPacks.value = filterInstalledPack(searchResults.value)
|
|
||||||
} else if (
|
|
||||||
!installedPacks.value.length &&
|
|
||||||
!installedPacksReady.value &&
|
|
||||||
!isLoadingInstalled.value
|
|
||||||
) {
|
|
||||||
await startFetchInstalled()
|
|
||||||
} else {
|
|
||||||
displayPacks.value = installedPacks.value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
[isMissingTab, isWorkflowTab, workflowPacks, installedPacks],
|
|
||||||
async () => {
|
|
||||||
if (!isWorkflowTab.value && !isMissingTab.value) return
|
|
||||||
|
|
||||||
if (!isEmptySearch.value) {
|
|
||||||
displayPacks.value = isMissingTab.value
|
|
||||||
? filterMissingPacks(filterWorkflowPack(searchResults.value))
|
|
||||||
: filterWorkflowPack(searchResults.value)
|
|
||||||
} else if (
|
|
||||||
!workflowPacks.value.length &&
|
|
||||||
!isLoadingWorkflow.value &&
|
|
||||||
!workflowPacksReady.value
|
|
||||||
) {
|
|
||||||
await startFetchWorkflowPacks()
|
|
||||||
if (isMissingTab.value) {
|
|
||||||
await startFetchInstalled()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
displayPacks.value = isMissingTab.value
|
|
||||||
? filterMissingPacks(workflowPacks.value)
|
|
||||||
: workflowPacks.value
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch([isAllTab, searchResults], () => {
|
|
||||||
if (!isAllTab.value) return
|
|
||||||
displayPacks.value = searchResults.value
|
|
||||||
})
|
|
||||||
|
|
||||||
const onClickWarningLink = () => {
|
const onClickWarningLink = () => {
|
||||||
window.open(
|
window.open(
|
||||||
@@ -442,49 +408,9 @@ const onClickWarningLink = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onResultsChange = () => {
|
|
||||||
switch (selectedTab.value?.id) {
|
|
||||||
case ManagerTab.Installed:
|
|
||||||
displayPacks.value = isEmptySearch.value
|
|
||||||
? installedPacks.value
|
|
||||||
: filterInstalledPack(searchResults.value)
|
|
||||||
break
|
|
||||||
case ManagerTab.Workflow:
|
|
||||||
displayPacks.value = isEmptySearch.value
|
|
||||||
? workflowPacks.value
|
|
||||||
: filterWorkflowPack(searchResults.value)
|
|
||||||
break
|
|
||||||
case ManagerTab.Missing:
|
|
||||||
if (!isEmptySearch.value) {
|
|
||||||
displayPacks.value = filterMissingPacks(
|
|
||||||
filterWorkflowPack(searchResults.value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case ManagerTab.UpdateAvailable:
|
|
||||||
displayPacks.value = isEmptySearch.value
|
|
||||||
? filterOutdatedPacks(installedPacks.value)
|
|
||||||
: filterOutdatedPacks(searchResults.value)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
displayPacks.value = searchResults.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(searchResults, onResultsChange, { flush: 'post' })
|
|
||||||
watch(() => comfyManagerStore.installedPacksIds, onResultsChange)
|
|
||||||
|
|
||||||
const isLoading = computed(() => {
|
const isLoading = computed(() => {
|
||||||
if (isSearchLoading.value) return searchResults.value.length === 0
|
if (isSearchLoading.value) return searchResults.value.length === 0
|
||||||
if (selectedTab.value?.id === ManagerTab.Installed) {
|
if (isTabLoading.value) return true
|
||||||
return isLoadingInstalled.value
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
selectedTab.value?.id === ManagerTab.Workflow ||
|
|
||||||
selectedTab.value?.id === ManagerTab.Missing
|
|
||||||
) {
|
|
||||||
return isLoadingWorkflow.value
|
|
||||||
}
|
|
||||||
return isInitialLoad.value
|
return isInitialLoad.value
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -511,7 +437,7 @@ watch(
|
|||||||
|
|
||||||
const getLoadingCount = () => {
|
const getLoadingCount = () => {
|
||||||
switch (selectedTab.value?.id) {
|
switch (selectedTab.value?.id) {
|
||||||
case ManagerTab.Installed:
|
case ManagerTab.AllInstalled:
|
||||||
return comfyManagerStore.installedPacksIds?.size
|
return comfyManagerStore.installedPacksIds?.size
|
||||||
case ManagerTab.Workflow:
|
case ManagerTab.Workflow:
|
||||||
return workflowPacks.value?.length
|
return workflowPacks.value?.length
|
||||||
@@ -581,10 +507,6 @@ whenever(selectedNodePack, async () => {
|
|||||||
if (packIndex !== -1) {
|
if (packIndex !== -1) {
|
||||||
selectedNodePacks.value.splice(packIndex, 1, mergedPack)
|
selectedNodePacks.value.splice(packIndex, 1, mergedPack)
|
||||||
}
|
}
|
||||||
const idx = displayPacks.value.findIndex((p) => p.id === mergedPack.id)
|
|
||||||
if (idx !== -1) {
|
|
||||||
displayPacks.value.splice(idx, 1, mergedPack)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="primary"
|
||||||
:size
|
:size
|
||||||
:disabled="isLoading || isInstalling"
|
:disabled="isLoading || isInstalling"
|
||||||
@click="installAllPacks"
|
@click="installAllPacks"
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
duration="1s"
|
duration="1s"
|
||||||
:size="size === 'sm' ? 12 : 16"
|
:size="size === 'sm' ? 12 : 16"
|
||||||
/>
|
/>
|
||||||
|
<i v-else class="icon-[lucide--download]" />
|
||||||
<span>{{ computedLabel }}</span>
|
<span>{{ computedLabel }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
v-tooltip.top="$t('manager.tryUpdateTooltip')"
|
||||||
variant="textonly"
|
variant="inverted"
|
||||||
:size
|
:size
|
||||||
:disabled="isUpdating"
|
:disabled="isUpdating"
|
||||||
@click="tryUpdate"
|
@click="tryUpdate"
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Button
|
<Button
|
||||||
variant="textonly"
|
variant="destructive"
|
||||||
:size
|
:size
|
||||||
class="border border-red-500"
|
|
||||||
@click="uninstallItems"
|
@click="uninstallItems"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
v-tooltip.top="
|
v-tooltip.top="
|
||||||
hasDisabledUpdatePacks ? $t('manager.disabledNodesWontUpdate') : null
|
hasDisabledUpdatePacks ? $t('manager.disabledNodesWontUpdate') : null
|
||||||
"
|
"
|
||||||
class="border"
|
variant="primary"
|
||||||
:size
|
:size
|
||||||
:disabled="isUpdating"
|
:disabled="isUpdating"
|
||||||
@click="updateAllPacks"
|
@click="updateAllPacks"
|
||||||
>
|
>
|
||||||
<DotSpinner v-if="isUpdating" duration="1s" :size="12" />
|
<DotSpinner v-if="isUpdating" duration="1s" :size="12" />
|
||||||
|
<i v-else class="icon-[lucide--refresh-cw]" />
|
||||||
<span>{{ $t('manager.updateAll') }}</span>
|
<span>{{ $t('manager.updateAll') }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<template v-if="nodePack">
|
<template v-if="nodePack">
|
||||||
<div class="relative z-40 flex h-full flex-col overflow-hidden">
|
<div class="flex h-full flex-col overflow-hidden">
|
||||||
<div class="top-0 z-10 w-full px-6 pt-6">
|
<div class="w-full px-6 pt-6">
|
||||||
<InfoPanelHeader
|
<InfoPanelHeader
|
||||||
:node-packs="[nodePack]"
|
:node-packs="[nodePack]"
|
||||||
:has-conflict="hasCompatibilityIssues"
|
:has-conflict="hasCompatibilityIssues"
|
||||||
>
|
/>
|
||||||
<template v-if="canTryNightlyUpdate" #install-button>
|
|
||||||
<div class="flex w-full justify-center gap-2">
|
|
||||||
<PackTryUpdateButton :node-pack="nodePack" size="md" />
|
|
||||||
<PackUninstallButton :node-packs="[nodePack]" size="md" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</InfoPanelHeader>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref="scrollContainer"
|
ref="scrollContainer"
|
||||||
class="scrollbar-hide flex-1 overflow-y-auto p-6 pt-2 text-sm"
|
class="scrollbar-hide flex-1 flex flex-col p-6 pt-2 text-sm"
|
||||||
>
|
>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<MetadataRow
|
<MetadataRow
|
||||||
@@ -49,7 +42,7 @@
|
|||||||
<PackVersionBadge :node-pack="nodePack" :is-selected="true" />
|
<PackVersionBadge :node-pack="nodePack" :is-selected="true" />
|
||||||
</MetadataRow>
|
</MetadataRow>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-6 overflow-hidden">
|
<div class="mb-6 flex-1 overflow-hidden">
|
||||||
<InfoTabs
|
<InfoTabs
|
||||||
:node-pack="nodePack"
|
:node-pack="nodePack"
|
||||||
:has-compatibility-issues="hasCompatibilityIssues"
|
:has-compatibility-issues="hasCompatibilityIssues"
|
||||||
@@ -75,12 +68,9 @@ import type { components } from '@/types/comfyRegistryTypes'
|
|||||||
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
import PackStatusMessage from '@/workbench/extensions/manager/components/manager/PackStatusMessage.vue'
|
||||||
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
||||||
import PackEnableToggle from '@/workbench/extensions/manager/components/manager/button/PackEnableToggle.vue'
|
import PackEnableToggle from '@/workbench/extensions/manager/components/manager/button/PackEnableToggle.vue'
|
||||||
import PackTryUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue'
|
|
||||||
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
|
||||||
import InfoPanelHeader from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelHeader.vue'
|
import InfoPanelHeader from '@/workbench/extensions/manager/components/manager/infoPanel/InfoPanelHeader.vue'
|
||||||
import InfoTabs from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTabs.vue'
|
import InfoTabs from '@/workbench/extensions/manager/components/manager/infoPanel/InfoTabs.vue'
|
||||||
import MetadataRow from '@/workbench/extensions/manager/components/manager/infoPanel/MetadataRow.vue'
|
import MetadataRow from '@/workbench/extensions/manager/components/manager/infoPanel/MetadataRow.vue'
|
||||||
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
|
||||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||||
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
|
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
@@ -109,8 +99,6 @@ whenever(isInstalled, () => {
|
|||||||
isInstalling.value = false
|
isInstalling.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
const { canTryNightlyUpdate } = usePackUpdateStatus(() => nodePack)
|
|
||||||
|
|
||||||
const { checkNodeCompatibility } = useConflictDetection()
|
const { checkNodeCompatibility } = useConflictDetection()
|
||||||
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
const { getConflictsForPackageByID } = useConflictDetectionStore()
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="nodePacks?.length" class="flex flex-col items-center">
|
<div v-if="nodePacks?.length" class="flex flex-col items-center">
|
||||||
<slot name="thumbnail">
|
<PackIcon :node-pack="nodePacks[0]" width="204" height="106" />
|
||||||
<PackIcon :node-pack="nodePacks[0]" width="204" height="106" />
|
<p class="text-center text-base font-bold">{{ nodePacks[0].name }}</p>
|
||||||
</slot>
|
<div v-if="!importFailed" class="flex justify-center gap-2">
|
||||||
<h2
|
<template v-if="canTryNightlyUpdate">
|
||||||
class="mt-4 mb-2 text-center text-2xl font-bold"
|
<PackTryUpdateButton :node-pack="nodePacks[0]" size="md" />
|
||||||
style="word-break: break-all"
|
<PackUninstallButton :node-packs="nodePacks" size="md" />
|
||||||
>
|
</template>
|
||||||
<slot name="title">
|
<template v-else-if="isAllInstalled">
|
||||||
<span class="inline-block text-base">{{ nodePacks[0].name }}</span>
|
<PackUninstallButton v-bind="$attrs" size="md" :node-packs="nodePacks" />
|
||||||
</slot>
|
</template>
|
||||||
</h2>
|
<template v-else>
|
||||||
<div
|
|
||||||
v-if="!importFailed"
|
|
||||||
class="mt-2 mb-4 flex w-full max-w-xs justify-center"
|
|
||||||
>
|
|
||||||
<slot name="install-button">
|
|
||||||
<PackUninstallButton
|
|
||||||
v-if="isAllInstalled"
|
|
||||||
v-bind="$attrs"
|
|
||||||
size="md"
|
|
||||||
:node-packs="nodePacks"
|
|
||||||
/>
|
|
||||||
<PackInstallButton
|
<PackInstallButton
|
||||||
v-else
|
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
size="md"
|
size="md"
|
||||||
:node-packs="nodePacks"
|
:node-packs="nodePacks"
|
||||||
:has-conflict="hasConflict || computedHasConflict"
|
:has-conflict="hasConflict || computedHasConflict"
|
||||||
:conflict-info="conflictInfo"
|
:conflict-info="conflictInfo"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col items-center">
|
<div v-else class="flex flex-col items-center">
|
||||||
@@ -47,8 +35,10 @@ import { computed, inject, ref, watch } from 'vue'
|
|||||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||||
import type { components } from '@/types/comfyRegistryTypes'
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
|
||||||
|
import PackTryUpdateButton from '@/workbench/extensions/manager/components/manager/button/PackTryUpdateButton.vue'
|
||||||
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
import PackUninstallButton from '@/workbench/extensions/manager/components/manager/button/PackUninstallButton.vue'
|
||||||
import PackIcon from '@/workbench/extensions/manager/components/manager/packIcon/PackIcon.vue'
|
import PackIcon from '@/workbench/extensions/manager/components/manager/packIcon/PackIcon.vue'
|
||||||
|
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
||||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||||
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
import type { ConflictDetail } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
import type { ConflictDetail } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
|
||||||
@@ -76,6 +66,9 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check if nightly update is available for the first pack
|
||||||
|
const { canTryNightlyUpdate } = usePackUpdateStatus(() => nodePacks[0])
|
||||||
|
|
||||||
// Add conflict detection for install button dialog
|
// Add conflict detection for install button dialog
|
||||||
const { checkNodeCompatibility } = useConflictDetection()
|
const { checkNodeCompatibility } = useConflictDetection()
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden h-full flex flex-col">
|
||||||
<Tabs :value="activeTab">
|
<div class="flex-1 min-h-0">
|
||||||
<TabList class="scrollbar-hide overflow-x-auto">
|
<TabList v-model="activeTab" class="scrollbar-hide overflow-x-auto">
|
||||||
<Tab
|
<Tab v-if="hasCompatibilityIssues" value="warning">
|
||||||
v-if="hasCompatibilityIssues"
|
|
||||||
value="warning"
|
|
||||||
class="mr-6 p-2 font-inter"
|
|
||||||
>
|
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<span>⚠️</span>
|
<span>⚠️</span>
|
||||||
{{ importFailed ? $t('g.error') : $t('g.warning') }}
|
{{ importFailed ? $t('g.error') : $t('g.warning') }}
|
||||||
</div>
|
</div>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="description" class="mr-6 p-2 font-inter">
|
<Tab value="description">
|
||||||
{{ $t('g.description') }}
|
{{ $t('g.description') }}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="nodes" class="p-2 font-inter">
|
<Tab value="nodes">
|
||||||
{{ $t('g.nodes') }}
|
{{ $t('g.nodes') }}
|
||||||
</Tab>
|
</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanels class="overflow-auto px-2 py-4">
|
</div>
|
||||||
<TabPanel
|
|
||||||
v-if="hasCompatibilityIssues"
|
<div class="p-2 scrollbar-custom">
|
||||||
value="warning"
|
<WarningTabPanel
|
||||||
class="bg-transparent"
|
v-if="activeTab === 'warning' && hasCompatibilityIssues"
|
||||||
>
|
:node-pack="nodePack"
|
||||||
<WarningTabPanel
|
:conflict-result="conflictResult"
|
||||||
:node-pack="nodePack"
|
/>
|
||||||
:conflict-result="conflictResult"
|
<DescriptionTabPanel
|
||||||
/>
|
v-else-if="activeTab === 'description'"
|
||||||
</TabPanel>
|
:node-pack="nodePack"
|
||||||
<TabPanel value="description">
|
/>
|
||||||
<DescriptionTabPanel :node-pack="nodePack" />
|
<NodesTabPanel
|
||||||
</TabPanel>
|
v-else-if="activeTab === 'nodes'"
|
||||||
<TabPanel value="nodes">
|
:node-pack="nodePack"
|
||||||
<NodesTabPanel :node-pack="nodePack" :node-names="nodeNames" />
|
:node-names="nodeNames"
|
||||||
</TabPanel>
|
/>
|
||||||
</TabPanels>
|
</div>
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Tab from 'primevue/tab'
|
|
||||||
import TabList from 'primevue/tablist'
|
|
||||||
import TabPanel from 'primevue/tabpanel'
|
|
||||||
import TabPanels from 'primevue/tabpanels'
|
|
||||||
import Tabs from 'primevue/tabs'
|
|
||||||
import { computed, inject, ref, watchEffect } from 'vue'
|
import { computed, inject, ref, watchEffect } from 'vue'
|
||||||
|
|
||||||
|
import Tab from '@/components/tab/Tab.vue'
|
||||||
|
import TabList from '@/components/tab/TabList.vue'
|
||||||
import type { components } from '@/types/comfyRegistryTypes'
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
import DescriptionTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/DescriptionTabPanel.vue'
|
import DescriptionTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/DescriptionTabPanel.vue'
|
||||||
import NodesTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/NodesTabPanel.vue'
|
import NodesTabPanel from '@/workbench/extensions/manager/components/manager/infoPanel/tabs/NodesTabPanel.vue'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<template v-if="conflict.type === 'import_failed'">
|
<template v-if="conflict.type === 'import_failed'">
|
||||||
<div
|
<div
|
||||||
v-if="conflict.required_value"
|
v-if="conflict.required_value"
|
||||||
class="max-h-64 overflow-x-hidden scrollbar-custom overflow-y-auto rounded px-2"
|
class="overflow-x-hidden rounded px-2"
|
||||||
>
|
>
|
||||||
<p class="text-xs text-muted-foreground break-all font-mono">
|
<p class="text-xs text-muted-foreground break-all font-mono">
|
||||||
{{ conflict.required_value }}
|
{{ conflict.required_value }}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="aspect-7/3 w-full overflow-hidden">
|
<div class="aspect-7/3 w-full overflow-hidden z-0">
|
||||||
<!-- default banner show -->
|
<!-- default banner show -->
|
||||||
<div v-if="showDefaultBanner" class="h-full w-full">
|
<div v-if="showDefaultBanner" class="h-full w-full">
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,31 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<Card
|
<div
|
||||||
class="shadow-elevation-3 inline-flex size-full flex-col items-start justify-between overflow-hidden rounded-lg transition-all duration-200"
|
:class="
|
||||||
:class="{
|
cn(
|
||||||
'selected-card': isSelected,
|
'flex size-full flex-col overflow-hidden rounded-lg bg-modal-card-background transition-colors duration-200 cursor-pointer select-none',
|
||||||
'opacity-60': isDisabled
|
isSelected
|
||||||
}"
|
? 'ring-3 ring-modal-card-border-highlighted'
|
||||||
:pt="{
|
: 'hover:bg-modal-card-background-hovered',
|
||||||
body: { class: 'p-0 flex flex-col w-full h-full rounded-lg gap-0' },
|
isDisabled && 'opacity-60'
|
||||||
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 flex flex-col gap-0',
|
|
||||||
style: {
|
|
||||||
borderTop: isLightTheme ? '1px solid #f4f4f4' : '1px solid #2C2C2C'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<template #title>
|
<!-- Banner -->
|
||||||
|
<div class="w-full cursor-pointer rounded-t-lg">
|
||||||
<PackBanner :node-pack="nodePack" />
|
<PackBanner :node-pack="nodePack" />
|
||||||
</template>
|
</div>
|
||||||
<template #content>
|
|
||||||
<div class="h-full w-full px-4 pt-4 pb-3">
|
<!-- Content -->
|
||||||
|
<div class="flex flex-1 flex-col rounded-lg min-h-0">
|
||||||
|
<div class="h-full w-full py-2 px-3">
|
||||||
<div class="flex h-full w-full flex-col gap-y-1">
|
<div class="flex h-full w-full flex-col gap-y-1">
|
||||||
<span
|
<span class="truncate overflow-hidden text-xs font-bold text-ellipsis">
|
||||||
class="truncate overflow-hidden text-sm font-bold text-ellipsis"
|
|
||||||
>
|
|
||||||
{{ nodePack.name }}
|
{{ nodePack.name }}
|
||||||
</span>
|
</span>
|
||||||
<p
|
<p
|
||||||
@@ -63,19 +57,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template #footer>
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="border-t border-border-default">
|
||||||
<PackCardFooter :node-pack="nodePack" :is-installing="isInstalling" />
|
<PackCardFooter :node-pack="nodePack" :is-installing="isInstalling" />
|
||||||
</template>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Card from 'primevue/card'
|
|
||||||
import { computed, provide } from 'vue'
|
import { computed, provide } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
import PackVersionBadge from '@/workbench/extensions/manager/components/manager/PackVersionBadge.vue'
|
||||||
import PackBanner from '@/workbench/extensions/manager/components/manager/packBanner/PackBanner.vue'
|
import PackBanner from '@/workbench/extensions/manager/components/manager/packBanner/PackBanner.vue'
|
||||||
import PackCardFooter from '@/workbench/extensions/manager/components/manager/packCard/PackCardFooter.vue'
|
import PackCardFooter from '@/workbench/extensions/manager/components/manager/packCard/PackCardFooter.vue'
|
||||||
@@ -96,11 +91,6 @@ const { nodePack, isSelected = false } = defineProps<{
|
|||||||
|
|
||||||
const { d, t } = useI18n()
|
const { d, t } = useI18n()
|
||||||
|
|
||||||
const colorPaletteStore = useColorPaletteStore()
|
|
||||||
const isLightTheme = computed(
|
|
||||||
() => colorPaletteStore.completedActivePalette.light_theme
|
|
||||||
)
|
|
||||||
|
|
||||||
const { isPackInstalled, isPackEnabled, isPackInstalling } =
|
const { isPackInstalled, isPackEnabled, isPackInstalling } =
|
||||||
useComfyManagerStore()
|
useComfyManagerStore()
|
||||||
|
|
||||||
@@ -133,22 +123,3 @@ const formattedLatestVersionDate = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.selected-card {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-card::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
border: 4px solid var(--p-primary-color);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import { whenever } from '@vueuse/core'
|
||||||
|
import { orderBy } from 'es-toolkit/compat'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { useRegistrySearchGateway } from '@/services/gateway/registrySearchGateway'
|
||||||
|
import type { components } from '@/types/comfyRegistryTypes'
|
||||||
|
import { useInstalledPacks } from '@/workbench/extensions/manager/composables/nodePack/useInstalledPacks'
|
||||||
|
import { usePackUpdateStatus } from '@/workbench/extensions/manager/composables/nodePack/usePackUpdateStatus'
|
||||||
|
import { useWorkflowPacks } from '@/workbench/extensions/manager/composables/nodePack/useWorkflowPacks'
|
||||||
|
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
|
||||||
|
import { useConflictDetectionStore } from '@/workbench/extensions/manager/stores/conflictDetectionStore'
|
||||||
|
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
|
||||||
|
|
||||||
|
type NodePack = components['schemas']['Node']
|
||||||
|
|
||||||
|
export function useManagerDisplayPacks(
|
||||||
|
selectedTabId: Ref<string | null>,
|
||||||
|
searchResults: Ref<NodePack[]>,
|
||||||
|
searchQuery: Ref<string>,
|
||||||
|
sortField: Ref<string>
|
||||||
|
) {
|
||||||
|
const comfyManagerStore = useComfyManagerStore()
|
||||||
|
const conflictDetectionStore = useConflictDetectionStore()
|
||||||
|
const { getSortValue, getSortableFields } = useRegistrySearchGateway()
|
||||||
|
|
||||||
|
const {
|
||||||
|
startFetchInstalled,
|
||||||
|
filterInstalledPack,
|
||||||
|
installedPacks,
|
||||||
|
isLoading: isLoadingInstalled,
|
||||||
|
isReady: installedPacksReady
|
||||||
|
} = useInstalledPacks()
|
||||||
|
|
||||||
|
const {
|
||||||
|
startFetchWorkflowPacks,
|
||||||
|
filterWorkflowPack,
|
||||||
|
workflowPacks,
|
||||||
|
isLoading: isLoadingWorkflow,
|
||||||
|
isReady: workflowPacksReady
|
||||||
|
} = useWorkflowPacks()
|
||||||
|
|
||||||
|
const tabType = computed(() => selectedTabId.value as ManagerTab | null)
|
||||||
|
const isEmptySearch = computed(() => searchQuery.value === '')
|
||||||
|
|
||||||
|
// Sorting function for packs not from searchResults
|
||||||
|
const sortPacks = (packs: NodePack[]) => {
|
||||||
|
if (!sortField.value || packs.length === 0) return packs
|
||||||
|
|
||||||
|
const sortableFields = getSortableFields()
|
||||||
|
const fieldConfig = sortableFields.find((f) => f.id === sortField.value)
|
||||||
|
const direction = fieldConfig?.direction || 'desc'
|
||||||
|
|
||||||
|
return orderBy(
|
||||||
|
packs,
|
||||||
|
[(pack) => getSortValue(pack, sortField.value)],
|
||||||
|
[direction]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter functions
|
||||||
|
const filterNotInstalled = (packs: NodePack[]) =>
|
||||||
|
packs.filter((p) => !comfyManagerStore.isPackInstalled(p.id))
|
||||||
|
|
||||||
|
const filterConflicting = (packs: NodePack[]) =>
|
||||||
|
packs.filter(
|
||||||
|
(p) =>
|
||||||
|
!!p.id &&
|
||||||
|
conflictDetectionStore.conflictedPackages.some(
|
||||||
|
(c) => c.package_id === p.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const filterOutdated = (packs: NodePack[]) =>
|
||||||
|
packs.filter((p) => {
|
||||||
|
const { isUpdateAvailable } = usePackUpdateStatus(p)
|
||||||
|
return isUpdateAvailable.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterMissing = (packs: NodePack[]) =>
|
||||||
|
packs.filter((p) => !comfyManagerStore.isPackInstalled(p.id))
|
||||||
|
|
||||||
|
// Data fetching triggers using whenever
|
||||||
|
const needsInstalledPacks = computed(() =>
|
||||||
|
[
|
||||||
|
ManagerTab.AllInstalled,
|
||||||
|
ManagerTab.UpdateAvailable,
|
||||||
|
ManagerTab.Conflicting
|
||||||
|
].includes(tabType.value as ManagerTab)
|
||||||
|
)
|
||||||
|
|
||||||
|
const needsWorkflowPacks = computed(() =>
|
||||||
|
[ManagerTab.Workflow, ManagerTab.Missing].includes(
|
||||||
|
tabType.value as ManagerTab
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
whenever(
|
||||||
|
() =>
|
||||||
|
needsInstalledPacks.value &&
|
||||||
|
!installedPacksReady.value &&
|
||||||
|
!isLoadingInstalled.value,
|
||||||
|
() => startFetchInstalled()
|
||||||
|
)
|
||||||
|
|
||||||
|
whenever(
|
||||||
|
() =>
|
||||||
|
needsWorkflowPacks.value &&
|
||||||
|
!workflowPacksReady.value &&
|
||||||
|
!isLoadingWorkflow.value,
|
||||||
|
() => startFetchWorkflowPacks()
|
||||||
|
)
|
||||||
|
|
||||||
|
// For Missing tab, also need installed packs to determine what's missing
|
||||||
|
whenever(
|
||||||
|
() =>
|
||||||
|
tabType.value === ManagerTab.Missing &&
|
||||||
|
!installedPacksReady.value &&
|
||||||
|
!isLoadingInstalled.value,
|
||||||
|
() => startFetchInstalled()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Single computed for display packs - replaces 7 watches
|
||||||
|
const displayPacks = computed(() => {
|
||||||
|
const tab = tabType.value
|
||||||
|
const hasSearch = !isEmptySearch.value
|
||||||
|
|
||||||
|
switch (tab) {
|
||||||
|
case ManagerTab.All:
|
||||||
|
return searchResults.value
|
||||||
|
|
||||||
|
case ManagerTab.NotInstalled:
|
||||||
|
return filterNotInstalled(searchResults.value)
|
||||||
|
|
||||||
|
case ManagerTab.AllInstalled:
|
||||||
|
return hasSearch
|
||||||
|
? filterInstalledPack(searchResults.value)
|
||||||
|
: sortPacks(installedPacks.value)
|
||||||
|
|
||||||
|
case ManagerTab.UpdateAvailable:
|
||||||
|
return sortPacks(
|
||||||
|
filterOutdated(
|
||||||
|
hasSearch
|
||||||
|
? filterInstalledPack(searchResults.value)
|
||||||
|
: installedPacks.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case ManagerTab.Conflicting:
|
||||||
|
return sortPacks(
|
||||||
|
filterConflicting(
|
||||||
|
hasSearch
|
||||||
|
? filterInstalledPack(searchResults.value)
|
||||||
|
: installedPacks.value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case ManagerTab.Workflow: {
|
||||||
|
return hasSearch
|
||||||
|
? filterWorkflowPack(searchResults.value)
|
||||||
|
: sortPacks(workflowPacks.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case ManagerTab.Missing: {
|
||||||
|
const base = hasSearch
|
||||||
|
? filterWorkflowPack(searchResults.value)
|
||||||
|
: workflowPacks.value
|
||||||
|
return sortPacks(filterMissing(base))
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return searchResults.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Loading state - single computed
|
||||||
|
const isLoading = computed(() => {
|
||||||
|
const tab = tabType.value
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
ManagerTab.AllInstalled,
|
||||||
|
ManagerTab.UpdateAvailable,
|
||||||
|
ManagerTab.Conflicting
|
||||||
|
].includes(tab as ManagerTab)
|
||||||
|
) {
|
||||||
|
return isLoadingInstalled.value
|
||||||
|
}
|
||||||
|
if ([ManagerTab.Workflow, ManagerTab.Missing].includes(tab as ManagerTab)) {
|
||||||
|
return isLoadingWorkflow.value
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
const missingNodePacks = computed(() => filterMissing(workflowPacks.value))
|
||||||
|
|
||||||
|
return {
|
||||||
|
displayPacks,
|
||||||
|
isLoading,
|
||||||
|
isLoadingInstalled,
|
||||||
|
isLoadingWorkflow,
|
||||||
|
installedPacks,
|
||||||
|
workflowPacks,
|
||||||
|
filterInstalledPack,
|
||||||
|
filterWorkflowPack,
|
||||||
|
missingNodePacks
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,7 +90,10 @@ export function useRegistrySearch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onQueryChange = () => updateSearchResults({ append: false })
|
const onQueryChange = () => updateSearchResults({ append: false })
|
||||||
const onPageChange = () => updateSearchResults({ append: true })
|
const onPageChange = () => {
|
||||||
|
if (pageNumber.value === 0) return
|
||||||
|
updateSearchResults({ append: true })
|
||||||
|
}
|
||||||
|
|
||||||
watch([sortField, searchMode], onQueryChange)
|
watch([sortField, searchMode], onQueryChange)
|
||||||
watch(pageNumber, onPageChange)
|
watch(pageNumber, onPageChange)
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ export const IsInstallingKey: InjectionKey<Ref<boolean>> =
|
|||||||
|
|
||||||
export enum ManagerTab {
|
export enum ManagerTab {
|
||||||
All = 'all',
|
All = 'all',
|
||||||
Installed = 'installed',
|
NotInstalled = 'notInstalled',
|
||||||
|
AllInstalled = 'allInstalled',
|
||||||
|
UpdateAvailable = 'updateAvailable',
|
||||||
|
Conflicting = 'conflicting',
|
||||||
Workflow = 'workflow',
|
Workflow = 'workflow',
|
||||||
Missing = 'missing',
|
Missing = 'missing'
|
||||||
UpdateAvailable = 'updateAvailable'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaskLog = {
|
export type TaskLog = {
|
||||||
|
|||||||
Reference in New Issue
Block a user