mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-04 07:00:23 +00:00
[Manager] Keep progress dialog on top using priority system (#4225)
This commit is contained in:
@@ -154,6 +154,7 @@ export const useDialogService = () => {
|
||||
headerComponent: ManagerProgressHeader,
|
||||
footerComponent: ManagerProgressFooter,
|
||||
props: options?.props,
|
||||
priority: 2,
|
||||
dialogComponentProps: {
|
||||
closable: false,
|
||||
modal: false,
|
||||
|
||||
@@ -40,6 +40,7 @@ interface DialogInstance {
|
||||
contentProps: Record<string, any>
|
||||
footerComponent?: Component
|
||||
dialogComponentProps: DialogComponentProps
|
||||
priority: number
|
||||
}
|
||||
|
||||
export interface ShowDialogOptions {
|
||||
@@ -50,6 +51,12 @@ export interface ShowDialogOptions {
|
||||
component: Component
|
||||
props?: Record<string, any>
|
||||
dialogComponentProps?: DialogComponentProps
|
||||
/**
|
||||
* Optional priority for dialog stacking.
|
||||
* A dialog will never be shown above a dialog with a higher priority.
|
||||
* @default 1
|
||||
*/
|
||||
priority?: number
|
||||
}
|
||||
|
||||
export const useDialogStore = defineStore('dialog', () => {
|
||||
@@ -57,13 +64,29 @@ export const useDialogStore = defineStore('dialog', () => {
|
||||
|
||||
const genDialogKey = () => `dialog-${Math.random().toString(36).slice(2, 9)}`
|
||||
|
||||
/**
|
||||
* Inserts a dialog into the stack at the correct position based on priority.
|
||||
* Higher priority dialogs are placed before lower priority ones.
|
||||
*/
|
||||
function insertDialogByPriority(dialog: DialogInstance) {
|
||||
const insertIndex = dialogStack.value.findIndex(
|
||||
(d) => d.priority <= dialog.priority
|
||||
)
|
||||
|
||||
dialogStack.value.splice(
|
||||
insertIndex === -1 ? dialogStack.value.length : insertIndex,
|
||||
0,
|
||||
dialog
|
||||
)
|
||||
}
|
||||
|
||||
function riseDialog(options: { key: string }) {
|
||||
const dialogKey = options.key
|
||||
|
||||
const index = dialogStack.value.findIndex((d) => d.key === dialogKey)
|
||||
if (index !== -1) {
|
||||
const dialogs = dialogStack.value.splice(index, 1)
|
||||
dialogStack.value.push(...dialogs)
|
||||
const [dialog] = dialogStack.value.splice(index, 1)
|
||||
insertDialogByPriority(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,12 +108,13 @@ export const useDialogStore = defineStore('dialog', () => {
|
||||
component: Component
|
||||
props?: Record<string, any>
|
||||
dialogComponentProps?: DialogComponentProps
|
||||
priority?: number
|
||||
}) {
|
||||
if (dialogStack.value.length >= 10) {
|
||||
dialogStack.value.shift()
|
||||
}
|
||||
|
||||
const dialog = {
|
||||
const dialog: DialogInstance = {
|
||||
key: options.key,
|
||||
visible: true,
|
||||
title: options.title,
|
||||
@@ -102,6 +126,7 @@ export const useDialogStore = defineStore('dialog', () => {
|
||||
: undefined,
|
||||
component: markRaw(options.component),
|
||||
contentProps: { ...options.props },
|
||||
priority: options.priority ?? 1,
|
||||
dialogComponentProps: {
|
||||
maximizable: false,
|
||||
modal: true,
|
||||
@@ -110,6 +135,7 @@ export const useDialogStore = defineStore('dialog', () => {
|
||||
dismissableMask: true,
|
||||
...options.dialogComponentProps,
|
||||
maximized: false,
|
||||
// @ts-expect-error TODO: fix this
|
||||
onMaximize: () => {
|
||||
dialog.dialogComponentProps.maximized = true
|
||||
},
|
||||
@@ -128,7 +154,8 @@ export const useDialogStore = defineStore('dialog', () => {
|
||||
})
|
||||
}
|
||||
}
|
||||
dialogStack.value.push(dialog)
|
||||
|
||||
insertDialogByPriority(dialog)
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
175
tests-ui/tests/store/dialogStore.test.ts
Normal file
175
tests-ui/tests/store/dialogStore.test.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const MockComponent = defineComponent({
|
||||
name: 'MockComponent',
|
||||
template: '<div>Mock</div>'
|
||||
})
|
||||
|
||||
describe('dialogStore', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
describe('priority system', () => {
|
||||
it('should create dialogs in correct priority order', () => {
|
||||
const store = useDialogStore()
|
||||
|
||||
// Create dialogs with different priorities
|
||||
store.showDialog({
|
||||
key: 'low-priority',
|
||||
component: MockComponent,
|
||||
priority: 0
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'high-priority',
|
||||
component: MockComponent,
|
||||
priority: 10
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'medium-priority',
|
||||
component: MockComponent,
|
||||
priority: 5
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'no-priority',
|
||||
component: MockComponent
|
||||
})
|
||||
|
||||
// Check order: high (2) -> medium (1) -> low (0)
|
||||
expect(store.dialogStack.map((d) => d.key)).toEqual([
|
||||
'high-priority',
|
||||
'medium-priority',
|
||||
'no-priority',
|
||||
'low-priority'
|
||||
])
|
||||
})
|
||||
|
||||
it('should maintain priority order when rising dialogs', () => {
|
||||
const store = useDialogStore()
|
||||
|
||||
// Create dialogs with different priorities
|
||||
store.showDialog({
|
||||
key: 'priority-2',
|
||||
component: MockComponent,
|
||||
priority: 2
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'priority-1',
|
||||
component: MockComponent,
|
||||
priority: 1
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'priority-0',
|
||||
component: MockComponent,
|
||||
priority: 0
|
||||
})
|
||||
|
||||
// Try to rise the lowest priority dialog
|
||||
store.riseDialog({ key: 'priority-0' })
|
||||
|
||||
// Should still be at the bottom because of its priority
|
||||
expect(store.dialogStack.map((d) => d.key)).toEqual([
|
||||
'priority-2',
|
||||
'priority-1',
|
||||
'priority-0'
|
||||
])
|
||||
|
||||
// Rise the medium priority dialog
|
||||
store.riseDialog({ key: 'priority-1' })
|
||||
|
||||
// Should be above priority-0 but below priority-2
|
||||
expect(store.dialogStack.map((d) => d.key)).toEqual([
|
||||
'priority-2',
|
||||
'priority-1',
|
||||
'priority-0'
|
||||
])
|
||||
})
|
||||
|
||||
it('should keep high priority dialogs on top when creating new lower priority dialogs', () => {
|
||||
const store = useDialogStore()
|
||||
|
||||
// Create a high priority dialog (like manager progress)
|
||||
store.showDialog({
|
||||
key: 'manager-progress',
|
||||
component: MockComponent,
|
||||
priority: 10
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'dialog-2',
|
||||
component: MockComponent,
|
||||
priority: 0
|
||||
})
|
||||
|
||||
store.showDialog({
|
||||
key: 'dialog-3',
|
||||
component: MockComponent
|
||||
// Default priority is 1
|
||||
})
|
||||
|
||||
// Manager progress should still be on top
|
||||
expect(store.dialogStack[0].key).toBe('manager-progress')
|
||||
|
||||
// Check full order
|
||||
expect(store.dialogStack.map((d) => d.key)).toEqual([
|
||||
'manager-progress', // priority 2
|
||||
'dialog-3', // priority 1 (default)
|
||||
'dialog-2' // priority 0
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('basic dialog operations', () => {
|
||||
it('should show and close dialogs', () => {
|
||||
const store = useDialogStore()
|
||||
|
||||
store.showDialog({
|
||||
key: 'test-dialog',
|
||||
component: MockComponent
|
||||
})
|
||||
|
||||
expect(store.dialogStack).toHaveLength(1)
|
||||
expect(store.isDialogOpen('test-dialog')).toBe(true)
|
||||
|
||||
store.closeDialog({ key: 'test-dialog' })
|
||||
|
||||
expect(store.dialogStack).toHaveLength(0)
|
||||
expect(store.isDialogOpen('test-dialog')).toBe(false)
|
||||
})
|
||||
|
||||
it('should reuse existing dialog when showing with same key', () => {
|
||||
const store = useDialogStore()
|
||||
|
||||
store.showDialog({
|
||||
key: 'reusable-dialog',
|
||||
component: MockComponent,
|
||||
title: 'Original Title'
|
||||
})
|
||||
|
||||
// First call should create the dialog
|
||||
expect(store.dialogStack).toHaveLength(1)
|
||||
expect(store.dialogStack[0].title).toBe('Original Title')
|
||||
|
||||
// Second call with same key should reuse the dialog
|
||||
store.showDialog({
|
||||
key: 'reusable-dialog',
|
||||
component: MockComponent,
|
||||
title: 'New Title' // This should be ignored
|
||||
})
|
||||
|
||||
// Should still have only one dialog with original title
|
||||
expect(store.dialogStack).toHaveLength(1)
|
||||
expect(store.dialogStack[0].key).toBe('reusable-dialog')
|
||||
expect(store.dialogStack[0].title).toBe('Original Title')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user