[Manager] Keep progress dialog on top using priority system (#4225)

This commit is contained in:
Christian Byrne
2025-06-20 15:22:42 -07:00
committed by GitHub
parent 429ab6c365
commit 2ef760c599
3 changed files with 207 additions and 4 deletions

View File

@@ -154,6 +154,7 @@ export const useDialogService = () => {
headerComponent: ManagerProgressHeader,
footerComponent: ManagerProgressFooter,
props: options?.props,
priority: 2,
dialogComponentProps: {
closable: false,
modal: false,

View File

@@ -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
}

View 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')
})
})
})