[refactor] Unify small modal dialog styles with showSmallLayoutDialog (#8834)

## Summary
Extract a shared `showSmallLayoutDialog` utility and move
dialog-specific logic into composables, unifying the duplicated `pt`
configurations across small modal dialogs.

## Changes
- **`showSmallLayoutDialog`**: Added to `dialogService.ts` with a single
unified `pt` config for all small modal dialogs (missing nodes, missing
models, import failed, node conflict)
- **Composables**: Extracted 4 dialog functions from `dialogService`
into dedicated composables following the `useSettingsDialog` /
`useModelSelectorDialog` pattern:
  - `useMissingNodesDialog`
  - `useMissingModelsDialog`
  - `useImportFailedNodeDialog`
  - `useNodeConflictDialog`
- Each composable uses direct imports, synchronous `show()`, `hide()`,
and a `DIALOG_KEY` constant
- Updated all call sites (`app.ts`, `useHelpCenter`, `PackEnableToggle`,
`PackInstallButton`, `useImportFailedDetection`)

## Review Focus
- Unified `pt` config removes minor style variations between dialogs —
intentional design unification

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8834-refactor-Unify-small-modal-dialog-styles-with-showSmallLayoutDialog-3056d73d365081b6963beffc0e5943bf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Jin Yi
2026-02-20 13:58:59 +09:00
committed by GitHub
parent fe78bc6043
commit 44733f010d
15 changed files with 244 additions and 267 deletions

View File

@@ -4,11 +4,11 @@ import { computed, onMounted } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
import { useDialogService } from '@/services/dialogService'
import { useHelpCenterStore } from '@/stores/helpCenterStore'
import type { HelpCenterTriggerLocation } from '@/stores/helpCenterStore'
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
import { useNodeConflictDialog } from '@/workbench/extensions/manager/composables/useNodeConflictDialog'
export function useHelpCenter(
triggerFrom: HelpCenterTriggerLocation = 'sidebar'
@@ -21,7 +21,7 @@ export function useHelpCenter(
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
const conflictDetection = useConflictDetection()
const { showNodeConflictDialog } = useDialogService()
const { show: showNodeConflictDialog } = useNodeConflictDialog()
// Use conflict acknowledgment state from composable - call only once
const { shouldShowRedDot: shouldShowConflictRedDot, markConflictsAsSeen } =

View File

@@ -0,0 +1,26 @@
import type { ComponentAttrs } from 'vue-component-type-helpers'
import MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue'
import { useDialogService } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'
const DIALOG_KEY = 'global-missing-models-warning'
export function useMissingModelsDialog() {
const { showSmallLayoutDialog } = useDialogService()
const dialogStore = useDialogStore()
function hide() {
dialogStore.closeDialog({ key: DIALOG_KEY })
}
function show(props: ComponentAttrs<typeof MissingModelsWarning>) {
showSmallLayoutDialog({
key: DIALOG_KEY,
component: MissingModelsWarning,
props
})
}
return { show, hide }
}

View File

@@ -0,0 +1,30 @@
import type { ComponentAttrs } from 'vue-component-type-helpers'
import MissingNodesContent from '@/components/dialog/content/MissingNodesContent.vue'
import MissingNodesFooter from '@/components/dialog/content/MissingNodesFooter.vue'
import MissingNodesHeader from '@/components/dialog/content/MissingNodesHeader.vue'
import { useDialogService } from '@/services/dialogService'
import { useDialogStore } from '@/stores/dialogStore'
const DIALOG_KEY = 'global-missing-nodes'
export function useMissingNodesDialog() {
const { showSmallLayoutDialog } = useDialogService()
const dialogStore = useDialogStore()
function hide() {
dialogStore.closeDialog({ key: DIALOG_KEY })
}
function show(props: ComponentAttrs<typeof MissingNodesContent>) {
showSmallLayoutDialog({
key: DIALOG_KEY,
headerComponent: MissingNodesHeader,
footerComponent: MissingNodesFooter,
component: MissingNodesContent,
props
})
}
return { show, hide }
}

View File

@@ -9,16 +9,21 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { app } from '@/scripts/app'
const { mockShowLoadWorkflowWarning, mockShowMissingModelsWarning } =
vi.hoisted(() => ({
mockShowLoadWorkflowWarning: vi.fn(),
mockShowMissingModelsWarning: vi.fn()
}))
const { mockShowMissingNodes, mockShowMissingModels } = vi.hoisted(() => ({
mockShowMissingNodes: vi.fn(),
mockShowMissingModels: vi.fn()
}))
vi.mock('@/composables/useMissingNodesDialog', () => ({
useMissingNodesDialog: () => ({ show: mockShowMissingNodes, hide: vi.fn() })
}))
vi.mock('@/composables/useMissingModelsDialog', () => ({
useMissingModelsDialog: () => ({ show: mockShowMissingModels, hide: vi.fn() })
}))
vi.mock('@/services/dialogService', () => ({
useDialogService: () => ({
showLoadWorkflowWarning: mockShowLoadWorkflowWarning,
showMissingModelsWarning: mockShowMissingModelsWarning,
prompt: vi.fn(),
confirm: vi.fn()
})
@@ -114,8 +119,8 @@ describe('useWorkflowService', () => {
const workflow = createWorkflow(null)
useWorkflowService().showPendingWarnings(workflow)
expect(mockShowLoadWorkflowWarning).not.toHaveBeenCalled()
expect(mockShowMissingModelsWarning).not.toHaveBeenCalled()
expect(mockShowMissingNodes).not.toHaveBeenCalled()
expect(mockShowMissingModels).not.toHaveBeenCalled()
})
it('should show missing nodes dialog and clear warnings', () => {
@@ -124,7 +129,7 @@ describe('useWorkflowService', () => {
useWorkflowService().showPendingWarnings(workflow)
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledWith({
expect(mockShowMissingNodes).toHaveBeenCalledWith({
missingNodeTypes
})
expect(workflow.pendingWarnings).toBeNull()
@@ -135,7 +140,7 @@ describe('useWorkflowService', () => {
useWorkflowService().showPendingWarnings(workflow)
expect(mockShowMissingModelsWarning).toHaveBeenCalledWith(MISSING_MODELS)
expect(mockShowMissingModels).toHaveBeenCalledWith(MISSING_MODELS)
expect(workflow.pendingWarnings).toBeNull()
})
@@ -149,8 +154,8 @@ describe('useWorkflowService', () => {
useWorkflowService().showPendingWarnings(workflow)
expect(mockShowLoadWorkflowWarning).not.toHaveBeenCalled()
expect(mockShowMissingModelsWarning).not.toHaveBeenCalled()
expect(mockShowMissingNodes).not.toHaveBeenCalled()
expect(mockShowMissingModels).not.toHaveBeenCalled()
expect(workflow.pendingWarnings).toBeNull()
})
@@ -163,7 +168,7 @@ describe('useWorkflowService', () => {
service.showPendingWarnings(workflow)
service.showPendingWarnings(workflow)
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledTimes(1)
expect(mockShowMissingNodes).toHaveBeenCalledTimes(1)
})
})
@@ -188,7 +193,7 @@ describe('useWorkflowService', () => {
{ loadable: true }
)
expect(mockShowLoadWorkflowWarning).not.toHaveBeenCalled()
expect(mockShowMissingNodes).not.toHaveBeenCalled()
await useWorkflowService().openWorkflow(workflow)
@@ -199,7 +204,7 @@ describe('useWorkflowService', () => {
workflow,
expect.objectContaining({ deferWarnings: true })
)
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledWith({
expect(mockShowMissingNodes).toHaveBeenCalledWith({
missingNodeTypes: ['CustomNode1']
})
expect(workflow.pendingWarnings).toBeNull()
@@ -218,16 +223,16 @@ describe('useWorkflowService', () => {
const service = useWorkflowService()
await service.openWorkflow(workflow1)
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledTimes(1)
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledWith({
expect(mockShowMissingNodes).toHaveBeenCalledTimes(1)
expect(mockShowMissingNodes).toHaveBeenCalledWith({
missingNodeTypes: ['MissingNodeA']
})
expect(workflow1.pendingWarnings).toBeNull()
expect(workflow2.pendingWarnings).not.toBeNull()
await service.openWorkflow(workflow2)
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledTimes(2)
expect(mockShowLoadWorkflowWarning).toHaveBeenLastCalledWith({
expect(mockShowMissingNodes).toHaveBeenCalledTimes(2)
expect(mockShowMissingNodes).toHaveBeenLastCalledWith({
missingNodeTypes: ['MissingNodeB']
})
expect(workflow2.pendingWarnings).toBeNull()
@@ -242,10 +247,10 @@ describe('useWorkflowService', () => {
const service = useWorkflowService()
await service.openWorkflow(workflow, { force: true })
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledTimes(1)
expect(mockShowMissingNodes).toHaveBeenCalledTimes(1)
await service.openWorkflow(workflow, { force: true })
expect(mockShowLoadWorkflowWarning).toHaveBeenCalledTimes(1)
expect(mockShowMissingNodes).toHaveBeenCalledTimes(1)
})
})
})

View File

@@ -17,6 +17,8 @@ import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail'
import { app } from '@/scripts/app'
import { blankGraph, defaultGraph } from '@/scripts/defaultGraph'
import { useMissingModelsDialog } from '@/composables/useMissingModelsDialog'
import { useMissingNodesDialog } from '@/composables/useMissingNodesDialog'
import { useDialogService } from '@/services/dialogService'
import { useDomWidgetStore } from '@/stores/domWidgetStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
@@ -27,6 +29,8 @@ export const useWorkflowService = () => {
const workflowStore = useWorkflowStore()
const toastStore = useToastStore()
const dialogService = useDialogService()
const missingModelsDialog = useMissingModelsDialog()
const missingNodesDialog = useMissingNodesDialog()
const workflowThumbnail = useWorkflowThumbnail()
const domWidgetStore = useDomWidgetStore()
const workflowDraftStore = useWorkflowDraftStore()
@@ -455,13 +459,13 @@ export const useWorkflowService = () => {
missingNodeTypes?.length &&
settingStore.get('Comfy.Workflow.ShowMissingNodesWarning')
) {
void dialogService.showLoadWorkflowWarning({ missingNodeTypes })
missingNodesDialog.show({ missingNodeTypes })
}
if (
missingModels &&
settingStore.get('Comfy.Workflow.ShowMissingModelsWarning')
) {
void dialogService.showMissingModelsWarning(missingModels)
missingModelsDialog.show(missingModels)
}
}

View File

@@ -52,6 +52,7 @@ import {
} from '@/scripts/domWidget'
import { useDialogService } from '@/services/dialogService'
import { useBillingContext } from '@/composables/billing/useBillingContext'
import { useMissingNodesDialog } from '@/composables/useMissingNodesDialog'
import { useExtensionService } from '@/services/extensionService'
import { useLitegraphService } from '@/services/litegraphService'
import { useSubgraphService } from '@/services/subgraphService'
@@ -1068,7 +1069,7 @@ export class ComfyApp {
private showMissingNodesError(missingNodeTypes: MissingNodeType[]) {
if (useSettingStore().get('Comfy.Workflow.ShowMissingNodesWarning')) {
useDialogService().showLoadWorkflowWarning({ missingNodeTypes })
useMissingNodesDialog().show({ missingNodeTypes })
}
}

View File

@@ -16,22 +16,9 @@ import type {
ShowDialogOptions
} from '@/stores/dialogStore'
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
import type { ComponentAttrs } from 'vue-component-type-helpers'
// Type-only imports for ComponentAttrs inference (no runtime cost)
import type MissingNodesContent from '@/components/dialog/content/MissingNodesContent.vue'
import type MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue'
// Lazy loaders for dialogs - components are loaded on first use
const lazyMissingNodesContent = () =>
import('@/components/dialog/content/MissingNodesContent.vue')
const lazyMissingNodesHeader = () =>
import('@/components/dialog/content/MissingNodesHeader.vue')
const lazyMissingNodesFooter = () =>
import('@/components/dialog/content/MissingNodesFooter.vue')
const lazyMissingModelsWarning = () =>
import('@/components/dialog/content/MissingModelsWarning.vue')
const lazyApiNodesSignInContent = () =>
import('@/components/dialog/content/ApiNodesSignInContent.vue')
const lazySignInContent = () =>
@@ -40,18 +27,6 @@ const lazyUpdatePasswordContent = () =>
import('@/components/dialog/content/UpdatePasswordContent.vue')
const lazyComfyOrgHeader = () =>
import('@/components/dialog/header/ComfyOrgHeader.vue')
const lazyImportFailedNodeContent = () =>
import('@/workbench/extensions/manager/components/manager/ImportFailedNodeContent.vue')
const lazyImportFailedNodeHeader = () =>
import('@/workbench/extensions/manager/components/manager/ImportFailedNodeHeader.vue')
const lazyImportFailedNodeFooter = () =>
import('@/workbench/extensions/manager/components/manager/ImportFailedNodeFooter.vue')
const lazyNodeConflictDialogContent = () =>
import('@/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue')
const lazyNodeConflictHeader = () =>
import('@/workbench/extensions/manager/components/manager/NodeConflictHeader.vue')
const lazyNodeConflictFooter = () =>
import('@/workbench/extensions/manager/components/manager/NodeConflictFooter.vue')
export type ConfirmationDialogType =
| 'default'
@@ -77,56 +52,6 @@ export interface ExecutionErrorDialogInput {
export const useDialogService = () => {
const dialogStore = useDialogStore()
async function showLoadWorkflowWarning(
props: ComponentAttrs<typeof MissingNodesContent>
) {
const [
{ default: MissingNodesContent },
{ default: MissingNodesHeader },
{ default: MissingNodesFooter }
] = await Promise.all([
lazyMissingNodesContent(),
lazyMissingNodesHeader(),
lazyMissingNodesFooter()
])
dialogStore.showDialog({
key: 'global-missing-nodes',
headerComponent: MissingNodesHeader,
footerComponent: MissingNodesFooter,
component: MissingNodesContent,
dialogComponentProps: {
closable: true,
pt: {
root: { class: 'bg-base-background border-border-default' },
header: { class: '!p-0 !m-0' },
content: { class: '!p-0 overflow-y-hidden' },
footer: { class: '!p-0' },
pcCloseButton: {
root: {
class: '!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5'
}
}
}
},
props,
footerProps: {
missingNodeTypes: props.missingNodeTypes
}
})
}
async function showMissingModelsWarning(
props: ComponentAttrs<typeof MissingModelsWarning>
) {
const { default: MissingModelsWarning } = await lazyMissingModelsWarning()
dialogStore.showDialog({
key: 'global-missing-models-warning',
component: MissingModelsWarning,
props
})
}
function showExecutionErrorDialog(executionError: ExecutionErrorDialogInput) {
const props: ComponentAttrs<typeof ErrorDialogContent> = {
error: {
@@ -440,29 +365,15 @@ export const useDialogService = () => {
})
}
async function showImportFailedNodeDialog(
options: {
conflictedPackages?: ConflictDetectionResult[]
dialogComponentProps?: DialogComponentProps
} = {}
function showSmallLayoutDialog(
options: Omit<ShowDialogOptions, 'dialogComponentProps'> & {
dialogComponentProps?: Omit<DialogComponentProps, 'pt'>
}
) {
const [
{ default: ImportFailedNodeHeader },
{ default: ImportFailedNodeFooter },
{ default: ImportFailedNodeContent }
] = await Promise.all([
lazyImportFailedNodeHeader(),
lazyImportFailedNodeFooter(),
lazyImportFailedNodeContent()
])
const { dialogComponentProps, conflictedPackages } = options
const { dialogComponentProps: callerProps, ...rest } = options
return dialogStore.showDialog({
key: 'global-import-failed',
headerComponent: ImportFailedNodeHeader,
footerComponent: ImportFailedNodeFooter,
component: ImportFailedNodeContent,
...rest,
dialogComponentProps: {
closable: true,
pt: {
@@ -476,71 +387,7 @@ export const useDialogService = () => {
}
}
},
...dialogComponentProps
},
props: {
conflictedPackages: conflictedPackages ?? []
},
footerProps: {
conflictedPackages: conflictedPackages ?? []
}
})
}
async function showNodeConflictDialog(
options: {
showAfterWhatsNew?: boolean
conflictedPackages?: ConflictDetectionResult[]
dialogComponentProps?: DialogComponentProps
buttonText?: string
onButtonClick?: () => void
} = {}
) {
const [
{ default: NodeConflictHeader },
{ default: NodeConflictFooter },
{ default: NodeConflictDialogContent }
] = await Promise.all([
lazyNodeConflictHeader(),
lazyNodeConflictFooter(),
lazyNodeConflictDialogContent()
])
const {
dialogComponentProps,
buttonText,
onButtonClick,
showAfterWhatsNew,
conflictedPackages
} = options
return dialogStore.showDialog({
key: 'global-node-conflict',
headerComponent: NodeConflictHeader,
footerComponent: NodeConflictFooter,
component: NodeConflictDialogContent,
dialogComponentProps: {
closable: true,
pt: {
header: { class: '!p-0 !m-0' },
content: { class: '!p-0 overflow-y-hidden' },
footer: { class: '!p-0' },
pcCloseButton: {
root: {
class:
'!w-7 !h-7 !border-none !outline-none !p-2 !m-1.5 bg-dialog-surface text-white'
}
}
},
...dialogComponentProps
},
props: {
showAfterWhatsNew,
conflictedPackages
},
footerProps: {
buttonText,
onButtonClick
...callerProps
}
})
}
@@ -715,8 +562,6 @@ export const useDialogService = () => {
}
return {
showLoadWorkflowWarning,
showMissingModelsWarning,
showExecutionErrorDialog,
showApiNodesSignInDialog,
showSignInDialog,
@@ -728,8 +573,7 @@ export const useDialogService = () => {
showErrorDialog,
confirm,
showLayoutDialog,
showImportFailedNodeDialog,
showNodeConflictDialog,
showSmallLayoutDialog,
showDeleteWorkspaceDialog,
showCreateWorkspaceDialog,
showLeaveWorkspaceDialog,

View File

@@ -36,11 +36,11 @@ import ToggleSwitch from 'primevue/toggleswitch'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useDialogService } from '@/services/dialogService'
import type { components } from '@/types/comfyRegistryTypes'
import { useConflictAcknowledgment } from '@/workbench/extensions/manager/composables/useConflictAcknowledgment'
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { useNodeConflictDialog } from '@/workbench/extensions/manager/composables/useNodeConflictDialog'
import { useConflictDetectionStore } from '@/workbench/extensions/manager/stores/conflictDetectionStore'
import type { components as ManagerComponents } from '@/workbench/extensions/manager/types/generatedManagerTypes'
@@ -54,7 +54,7 @@ const { t } = useI18n()
const { isPackEnabled, enablePack, disablePack, installedPacks } =
useComfyManagerStore()
const { getConflictsForPackageByID } = useConflictDetectionStore()
const { showNodeConflictDialog } = useDialogService()
const { show: showNodeConflictDialog } = useNodeConflictDialog()
const { acknowledgmentState, markConflictsAsSeen } = useConflictAcknowledgment()
const { showImportFailedDialog } = useImportFailedDetection(nodePack.id || '')

View File

@@ -26,9 +26,9 @@ import { useI18n } from 'vue-i18n'
import DotSpinner from '@/components/common/DotSpinner.vue'
import Button from '@/components/ui/button/Button.vue'
import type { ButtonVariants } from '@/components/ui/button/button.variants'
import { useDialogService } from '@/services/dialogService'
import type { components } from '@/types/comfyRegistryTypes'
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
import { useNodeConflictDialog } from '@/workbench/extensions/manager/composables/useNodeConflictDialog'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import type {
ConflictDetail,
@@ -55,7 +55,7 @@ const {
}>()
const managerStore = useComfyManagerStore()
const { showNodeConflictDialog } = useDialogService()
const { show: showNodeConflictDialog } = useNodeConflictDialog()
const { t } = useI18n()
// Check if any of the packs are currently being installed

View File

@@ -3,15 +3,30 @@ import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { computed, ref } from 'vue'
import * as dialogService from '@/services/dialogService'
import { useImportFailedDetection } from '@/workbench/extensions/manager/composables/useImportFailedDetection'
import * as comfyManagerStore from '@/workbench/extensions/manager/stores/comfyManagerStore'
import * as conflictDetectionStore from '@/workbench/extensions/manager/stores/conflictDetectionStore'
// Mock the stores and services
vi.mock('@/workbench/extensions/manager/stores/comfyManagerStore')
vi.mock('@/workbench/extensions/manager/stores/conflictDetectionStore')
vi.mock('@/services/dialogService')
const mockIsPackInstalled = vi.fn()
const mockGetConflictsForPackageByID = vi.fn()
const mockShow = vi.fn()
vi.mock('@/workbench/extensions/manager/stores/comfyManagerStore', () => ({
useComfyManagerStore: () => ({
isPackInstalled: mockIsPackInstalled
})
}))
vi.mock('@/workbench/extensions/manager/stores/conflictDetectionStore', () => ({
useConflictDetectionStore: () => ({
getConflictsForPackageByID: mockGetConflictsForPackageByID
})
}))
vi.mock(
'@/workbench/extensions/manager/composables/useImportFailedNodeDialog',
() => ({
useImportFailedNodeDialog: () => ({
show: mockShow
})
})
)
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual('vue-i18n')
return {
@@ -23,43 +38,13 @@ vi.mock('vue-i18n', async () => {
})
describe('useImportFailedDetection', () => {
let mockComfyManagerStore: ReturnType<
typeof comfyManagerStore.useComfyManagerStore
>
let mockConflictDetectionStore: ReturnType<
typeof conflictDetectionStore.useConflictDetectionStore
>
let mockDialogService: ReturnType<typeof dialogService.useDialogService>
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }))
mockComfyManagerStore = {
isPackInstalled: vi.fn()
} as unknown as ReturnType<typeof comfyManagerStore.useComfyManagerStore>
mockConflictDetectionStore = {
getConflictsForPackageByID: vi.fn()
} as unknown as ReturnType<
typeof conflictDetectionStore.useConflictDetectionStore
>
mockDialogService = {
showErrorDialog: vi.fn(),
showImportFailedNodeDialog: vi.fn()
} as unknown as ReturnType<typeof dialogService.useDialogService>
vi.mocked(comfyManagerStore.useComfyManagerStore).mockReturnValue(
mockComfyManagerStore
)
vi.mocked(conflictDetectionStore.useConflictDetectionStore).mockReturnValue(
mockConflictDetectionStore
)
vi.mocked(dialogService.useDialogService).mockReturnValue(mockDialogService)
vi.clearAllMocks()
})
it('should return false for importFailed when package is not installed', () => {
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(false)
mockIsPackInstalled.mockReturnValue(false)
const { importFailed } = useImportFailedDetection('test-package')
@@ -67,10 +52,8 @@ describe('useImportFailedDetection', () => {
})
it('should return false for importFailed when no conflicts exist', () => {
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(true)
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue(undefined)
mockIsPackInstalled.mockReturnValue(true)
mockGetConflictsForPackageByID.mockReturnValue(undefined)
const { importFailed } = useImportFailedDetection('test-package')
@@ -78,10 +61,8 @@ describe('useImportFailedDetection', () => {
})
it('should return false for importFailed when conflicts exist but no import_failed type', () => {
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(true)
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue({
mockIsPackInstalled.mockReturnValue(true)
mockGetConflictsForPackageByID.mockReturnValue({
package_id: 'test-package',
package_name: 'Test Package',
has_conflict: true,
@@ -106,10 +87,8 @@ describe('useImportFailedDetection', () => {
})
it('should return true for importFailed when import_failed conflicts exist', () => {
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(true)
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue({
mockIsPackInstalled.mockReturnValue(true)
mockGetConflictsForPackageByID.mockReturnValue({
package_id: 'test-package',
package_name: 'Test Package',
has_conflict: true,
@@ -135,10 +114,8 @@ describe('useImportFailedDetection', () => {
it('should work with computed ref packageId', () => {
const packageId = ref('test-package')
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(true)
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue({
mockIsPackInstalled.mockReturnValue(true)
mockGetConflictsForPackageByID.mockReturnValue({
package_id: 'test-package',
package_name: 'Test Package',
has_conflict: true,
@@ -160,9 +137,7 @@ describe('useImportFailedDetection', () => {
// Change packageId
packageId.value = 'another-package'
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue(undefined)
mockGetConflictsForPackageByID.mockReturnValue(undefined)
expect(importFailed.value).toBe(false)
})
@@ -181,10 +156,8 @@ describe('useImportFailedDetection', () => {
}
]
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(true)
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue({
mockIsPackInstalled.mockReturnValue(true)
mockGetConflictsForPackageByID.mockReturnValue({
package_id: 'test-package',
package_name: 'Test Package',
has_conflict: true,
@@ -213,10 +186,8 @@ describe('useImportFailedDetection', () => {
}
]
vi.mocked(mockComfyManagerStore.isPackInstalled).mockReturnValue(true)
vi.mocked(
mockConflictDetectionStore.getConflictsForPackageByID
).mockReturnValue({
mockIsPackInstalled.mockReturnValue(true)
mockGetConflictsForPackageByID.mockReturnValue({
package_id: 'test-package',
package_name: 'Test Package',
has_conflict: true,
@@ -228,7 +199,7 @@ describe('useImportFailedDetection', () => {
showImportFailedDialog()
expect(mockDialogService.showImportFailedNodeDialog).toHaveBeenCalledWith({
expect(mockShow).toHaveBeenCalledWith({
conflictedPackages: expect.arrayContaining([
expect.objectContaining({
package_id: 'test-package',

View File

@@ -1,7 +1,7 @@
import { computed, unref } from 'vue'
import type { ComputedRef } from 'vue'
import { useDialogService } from '@/services/dialogService'
import { useImportFailedNodeDialog } from '@/workbench/extensions/manager/composables/useImportFailedNodeDialog'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { useConflictDetectionStore } from '@/workbench/extensions/manager/stores/conflictDetectionStore'
import type {
@@ -26,14 +26,14 @@ function extractImportFailedConflicts(conflicts?: ConflictDetail[] | null) {
* Creating import failed dialog
*/
function createImportFailedDialog() {
const { showImportFailedNodeDialog } = useDialogService()
const { show } = useImportFailedNodeDialog()
return (
conflictedPackages: ConflictDetectionResult[] | null,
onClose?: () => void
) => {
if (conflictedPackages && conflictedPackages.length > 0) {
void showImportFailedNodeDialog({
void show({
conflictedPackages,
dialogComponentProps: {
onClose

View File

@@ -0,0 +1,43 @@
import { useDialogService } from '@/services/dialogService'
import type { DialogComponentProps } from '@/stores/dialogStore'
import { useDialogStore } from '@/stores/dialogStore'
import ImportFailedNodeContent from '@/workbench/extensions/manager/components/manager/ImportFailedNodeContent.vue'
import ImportFailedNodeFooter from '@/workbench/extensions/manager/components/manager/ImportFailedNodeFooter.vue'
import ImportFailedNodeHeader from '@/workbench/extensions/manager/components/manager/ImportFailedNodeHeader.vue'
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
const DIALOG_KEY = 'global-import-failed'
export function useImportFailedNodeDialog() {
const { showSmallLayoutDialog } = useDialogService()
const dialogStore = useDialogStore()
function hide() {
dialogStore.closeDialog({ key: DIALOG_KEY })
}
function show(
options: {
conflictedPackages?: ConflictDetectionResult[]
dialogComponentProps?: Omit<DialogComponentProps, 'pt'>
} = {}
) {
const { dialogComponentProps, conflictedPackages = [] } = options
showSmallLayoutDialog({
key: DIALOG_KEY,
headerComponent: ImportFailedNodeHeader,
footerComponent: ImportFailedNodeFooter,
component: ImportFailedNodeContent,
dialogComponentProps,
props: {
conflictedPackages
},
footerProps: {
conflictedPackages
}
})
}
return { show, hide }
}

View File

@@ -0,0 +1,54 @@
import { useDialogService } from '@/services/dialogService'
import type { DialogComponentProps } from '@/stores/dialogStore'
import { useDialogStore } from '@/stores/dialogStore'
import NodeConflictDialogContent from '@/workbench/extensions/manager/components/manager/NodeConflictDialogContent.vue'
import NodeConflictFooter from '@/workbench/extensions/manager/components/manager/NodeConflictFooter.vue'
import NodeConflictHeader from '@/workbench/extensions/manager/components/manager/NodeConflictHeader.vue'
import type { ConflictDetectionResult } from '@/workbench/extensions/manager/types/conflictDetectionTypes'
const DIALOG_KEY = 'global-node-conflict'
export function useNodeConflictDialog() {
const { showSmallLayoutDialog } = useDialogService()
const dialogStore = useDialogStore()
function hide() {
dialogStore.closeDialog({ key: DIALOG_KEY })
}
function show(
options: {
showAfterWhatsNew?: boolean
conflictedPackages?: ConflictDetectionResult[]
dialogComponentProps?: Omit<DialogComponentProps, 'pt'>
buttonText?: string
onButtonClick?: () => void
} = {}
) {
const {
dialogComponentProps,
buttonText,
onButtonClick,
showAfterWhatsNew,
conflictedPackages
} = options
showSmallLayoutDialog({
key: DIALOG_KEY,
headerComponent: NodeConflictHeader,
footerComponent: NodeConflictFooter,
component: NodeConflictDialogContent,
dialogComponentProps,
props: {
showAfterWhatsNew,
conflictedPackages
},
footerProps: {
buttonText,
onButtonClick
}
})
}
return { show, hide }
}