Files
ComfyUI_frontend/src/components/dialog/content/LoadWorkflowWarning.vue
Christian Byrne 002fac0232 [refactor] Migrate manager code to DDD structure (#5662)
## Summary

Reorganized custom nodes manager functionality from scattered technical
layers into a cohesive domain-focused module following [domain-driven
design](https://en.wikipedia.org/wiki/Domain-driven_design) principles.

## Changes

- **What**: Migrated all manager code from technical layers
(`src/components/`, `src/stores/`, etc.) to unified domain structure at
`src/workbench/extensions/manager/`
- **Breaking**: Import paths changed for all manager-related modules
(40+ files updated)

## Review Focus

Verify all import path updates are correct and no circular dependencies
introduced. Check that [Vue 3 composition
API](https://vuejs.org/guide/reusability/composables.html) patterns
remain consistent across relocated composables.


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5662-refactor-Migrate-manager-code-to-DDD-structure-2736d73d3650812c87faf6ed0fffb196)
by [Unito](https://www.unito.io)
2025-09-19 00:03:05 -07:00

173 lines
5.0 KiB
Vue

<template>
<NoResultsPlaceholder
class="pb-0"
icon="pi pi-exclamation-circle"
:title="$t('loadWorkflowWarning.missingNodesTitle')"
:message="$t('loadWorkflowWarning.missingNodesDescription')"
/>
<MissingCoreNodesMessage :missing-core-nodes="missingCoreNodes" />
<ListBox
:options="uniqueNodes"
option-label="label"
scroll-height="100%"
class="comfy-missing-nodes"
:pt="{
list: { class: 'border-none' }
}"
>
<template #option="slotProps">
<div class="flex align-items-center">
<span class="node-type">{{ slotProps.option.label }}</span>
<span v-if="slotProps.option.hint" class="node-hint">{{
slotProps.option.hint
}}</span>
<Button
v-if="slotProps.option.action"
:label="slotProps.option.action.text"
size="small"
outlined
@click="slotProps.option.action.callback"
/>
</div>
</template>
</ListBox>
<div v-if="showManagerButtons" class="flex justify-end py-3">
<PackInstallButton
v-if="showInstallAllButton"
size="md"
:disabled="
isLoading || !!error || missingNodePacks.length === 0 || isInstalling
"
:is-loading="isLoading"
:node-packs="missingNodePacks"
:label="
isLoading
? $t('manager.gettingInfo')
: $t('manager.installAllMissingNodes')
"
/>
<Button label="Open Manager" size="small" outlined @click="openManager" />
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import ListBox from 'primevue/listbox'
import { computed, nextTick, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
import { useMissingNodes } from '@/composables/nodePack/useMissingNodes'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useDialogStore } from '@/stores/dialogStore'
import type { MissingNodeType } from '@/types/comfy'
import PackInstallButton from '@/workbench/extensions/manager/components/manager/button/PackInstallButton.vue'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { ManagerTab } from '@/workbench/extensions/manager/types/comfyManagerTypes'
const props = defineProps<{
missingNodeTypes: MissingNodeType[]
}>()
// Get missing node packs from workflow with loading and error states
const { missingNodePacks, isLoading, error, missingCoreNodes } =
useMissingNodes()
const comfyManagerStore = useComfyManagerStore()
const managerState = useManagerState()
// Check if any of the missing packs are currently being installed
const isInstalling = computed(() => {
if (!missingNodePacks.value?.length) return false
return missingNodePacks.value.some((pack) =>
comfyManagerStore.isPackInstalling(pack.id)
)
})
const uniqueNodes = computed(() => {
const seenTypes = new Set()
return props.missingNodeTypes
.filter((node) => {
const type = typeof node === 'object' ? node.type : node
if (seenTypes.has(type)) return false
seenTypes.add(type)
return true
})
.map((node) => {
if (typeof node === 'object') {
return {
label: node.type,
hint: node.hint,
action: node.action
}
}
return { label: node }
})
})
// Show manager buttons unless manager is disabled
const showManagerButtons = computed(() => {
return managerState.shouldShowManagerButtons.value
})
// Only show Install All button for NEW_UI (new manager with v4 support)
const showInstallAllButton = computed(() => {
return managerState.shouldShowInstallButton.value
})
const openManager = async () => {
await managerState.openManager({
initialTab: ManagerTab.Missing,
showToastOnLegacyError: true
})
}
const { t } = useI18n()
const dialogStore = useDialogStore()
// Computed to check if all missing nodes have been installed
const allMissingNodesInstalled = computed(() => {
return (
!isLoading.value &&
!isInstalling.value &&
missingNodePacks.value?.length === 0
)
})
// Watch for completion and close dialog
watch(allMissingNodesInstalled, async (allInstalled) => {
if (allInstalled && showInstallAllButton.value) {
// Use nextTick to ensure state updates are complete
await nextTick()
dialogStore.closeDialog({ key: 'global-load-workflow-warning' })
// Show success toast
useToastStore().add({
severity: 'success',
summary: t('g.success'),
detail: t('manager.allMissingNodesInstalled'),
life: 3000
})
}
})
</script>
<style scoped>
.comfy-missing-nodes {
max-height: 300px;
overflow-y: auto;
}
.node-hint {
margin-left: 0.5rem;
font-style: italic;
color: var(--text-color-secondary);
}
:deep(.p-button) {
margin-left: auto;
}
</style>