Skip to content

Commit 2ef760c

Browse files
[Manager] Keep progress dialog on top using priority system (#4225)
1 parent 429ab6c commit 2ef760c

File tree

3 files changed

+207
-4
lines changed

3 files changed

+207
-4
lines changed

src/services/dialogService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export const useDialogService = () => {
154154
headerComponent: ManagerProgressHeader,
155155
footerComponent: ManagerProgressFooter,
156156
props: options?.props,
157+
priority: 2,
157158
dialogComponentProps: {
158159
closable: false,
159160
modal: false,

src/stores/dialogStore.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface DialogInstance {
4040
contentProps: Record<string, any>
4141
footerComponent?: Component
4242
dialogComponentProps: DialogComponentProps
43+
priority: number
4344
}
4445

4546
export interface ShowDialogOptions {
@@ -50,20 +51,42 @@ export interface ShowDialogOptions {
5051
component: Component
5152
props?: Record<string, any>
5253
dialogComponentProps?: DialogComponentProps
54+
/**
55+
* Optional priority for dialog stacking.
56+
* A dialog will never be shown above a dialog with a higher priority.
57+
* @default 1
58+
*/
59+
priority?: number
5360
}
5461

5562
export const useDialogStore = defineStore('dialog', () => {
5663
const dialogStack = ref<DialogInstance[]>([])
5764

5865
const genDialogKey = () => `dialog-${Math.random().toString(36).slice(2, 9)}`
5966

67+
/**
68+
* Inserts a dialog into the stack at the correct position based on priority.
69+
* Higher priority dialogs are placed before lower priority ones.
70+
*/
71+
function insertDialogByPriority(dialog: DialogInstance) {
72+
const insertIndex = dialogStack.value.findIndex(
73+
(d) => d.priority <= dialog.priority
74+
)
75+
76+
dialogStack.value.splice(
77+
insertIndex === -1 ? dialogStack.value.length : insertIndex,
78+
0,
79+
dialog
80+
)
81+
}
82+
6083
function riseDialog(options: { key: string }) {
6184
const dialogKey = options.key
6285

6386
const index = dialogStack.value.findIndex((d) => d.key === dialogKey)
6487
if (index !== -1) {
65-
const dialogs = dialogStack.value.splice(index, 1)
66-
dialogStack.value.push(...dialogs)
88+
const [dialog] = dialogStack.value.splice(index, 1)
89+
insertDialogByPriority(dialog)
6790
}
6891
}
6992

@@ -85,12 +108,13 @@ export const useDialogStore = defineStore('dialog', () => {
85108
component: Component
86109
props?: Record<string, any>
87110
dialogComponentProps?: DialogComponentProps
111+
priority?: number
88112
}) {
89113
if (dialogStack.value.length >= 10) {
90114
dialogStack.value.shift()
91115
}
92116

93-
const dialog = {
117+
const dialog: DialogInstance = {
94118
key: options.key,
95119
visible: true,
96120
title: options.title,
@@ -102,6 +126,7 @@ export const useDialogStore = defineStore('dialog', () => {
102126
: undefined,
103127
component: markRaw(options.component),
104128
contentProps: { ...options.props },
129+
priority: options.priority ?? 1,
105130
dialogComponentProps: {
106131
maximizable: false,
107132
modal: true,
@@ -110,6 +135,7 @@ export const useDialogStore = defineStore('dialog', () => {
110135
dismissableMask: true,
111136
...options.dialogComponentProps,
112137
maximized: false,
138+
// @ts-expect-error TODO: fix this
113139
onMaximize: () => {
114140
dialog.dialogComponentProps.maximized = true
115141
},
@@ -128,7 +154,8 @@ export const useDialogStore = defineStore('dialog', () => {
128154
})
129155
}
130156
}
131-
dialogStack.value.push(dialog)
157+
158+
insertDialogByPriority(dialog)
132159

133160
return dialog
134161
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { createPinia, setActivePinia } from 'pinia'
2+
import { beforeEach, describe, expect, it } from 'vitest'
3+
import { defineComponent } from 'vue'
4+
5+
import { useDialogStore } from '@/stores/dialogStore'
6+
7+
const MockComponent = defineComponent({
8+
name: 'MockComponent',
9+
template: '<div>Mock</div>'
10+
})
11+
12+
describe('dialogStore', () => {
13+
beforeEach(() => {
14+
setActivePinia(createPinia())
15+
})
16+
17+
describe('priority system', () => {
18+
it('should create dialogs in correct priority order', () => {
19+
const store = useDialogStore()
20+
21+
// Create dialogs with different priorities
22+
store.showDialog({
23+
key: 'low-priority',
24+
component: MockComponent,
25+
priority: 0
26+
})
27+
28+
store.showDialog({
29+
key: 'high-priority',
30+
component: MockComponent,
31+
priority: 10
32+
})
33+
34+
store.showDialog({
35+
key: 'medium-priority',
36+
component: MockComponent,
37+
priority: 5
38+
})
39+
40+
store.showDialog({
41+
key: 'no-priority',
42+
component: MockComponent
43+
})
44+
45+
// Check order: high (2) -> medium (1) -> low (0)
46+
expect(store.dialogStack.map((d) => d.key)).toEqual([
47+
'high-priority',
48+
'medium-priority',
49+
'no-priority',
50+
'low-priority'
51+
])
52+
})
53+
54+
it('should maintain priority order when rising dialogs', () => {
55+
const store = useDialogStore()
56+
57+
// Create dialogs with different priorities
58+
store.showDialog({
59+
key: 'priority-2',
60+
component: MockComponent,
61+
priority: 2
62+
})
63+
64+
store.showDialog({
65+
key: 'priority-1',
66+
component: MockComponent,
67+
priority: 1
68+
})
69+
70+
store.showDialog({
71+
key: 'priority-0',
72+
component: MockComponent,
73+
priority: 0
74+
})
75+
76+
// Try to rise the lowest priority dialog
77+
store.riseDialog({ key: 'priority-0' })
78+
79+
// Should still be at the bottom because of its priority
80+
expect(store.dialogStack.map((d) => d.key)).toEqual([
81+
'priority-2',
82+
'priority-1',
83+
'priority-0'
84+
])
85+
86+
// Rise the medium priority dialog
87+
store.riseDialog({ key: 'priority-1' })
88+
89+
// Should be above priority-0 but below priority-2
90+
expect(store.dialogStack.map((d) => d.key)).toEqual([
91+
'priority-2',
92+
'priority-1',
93+
'priority-0'
94+
])
95+
})
96+
97+
it('should keep high priority dialogs on top when creating new lower priority dialogs', () => {
98+
const store = useDialogStore()
99+
100+
// Create a high priority dialog (like manager progress)
101+
store.showDialog({
102+
key: 'manager-progress',
103+
component: MockComponent,
104+
priority: 10
105+
})
106+
107+
store.showDialog({
108+
key: 'dialog-2',
109+
component: MockComponent,
110+
priority: 0
111+
})
112+
113+
store.showDialog({
114+
key: 'dialog-3',
115+
component: MockComponent
116+
// Default priority is 1
117+
})
118+
119+
// Manager progress should still be on top
120+
expect(store.dialogStack[0].key).toBe('manager-progress')
121+
122+
// Check full order
123+
expect(store.dialogStack.map((d) => d.key)).toEqual([
124+
'manager-progress', // priority 2
125+
'dialog-3', // priority 1 (default)
126+
'dialog-2' // priority 0
127+
])
128+
})
129+
})
130+
131+
describe('basic dialog operations', () => {
132+
it('should show and close dialogs', () => {
133+
const store = useDialogStore()
134+
135+
store.showDialog({
136+
key: 'test-dialog',
137+
component: MockComponent
138+
})
139+
140+
expect(store.dialogStack).toHaveLength(1)
141+
expect(store.isDialogOpen('test-dialog')).toBe(true)
142+
143+
store.closeDialog({ key: 'test-dialog' })
144+
145+
expect(store.dialogStack).toHaveLength(0)
146+
expect(store.isDialogOpen('test-dialog')).toBe(false)
147+
})
148+
149+
it('should reuse existing dialog when showing with same key', () => {
150+
const store = useDialogStore()
151+
152+
store.showDialog({
153+
key: 'reusable-dialog',
154+
component: MockComponent,
155+
title: 'Original Title'
156+
})
157+
158+
// First call should create the dialog
159+
expect(store.dialogStack).toHaveLength(1)
160+
expect(store.dialogStack[0].title).toBe('Original Title')
161+
162+
// Second call with same key should reuse the dialog
163+
store.showDialog({
164+
key: 'reusable-dialog',
165+
component: MockComponent,
166+
title: 'New Title' // This should be ignored
167+
})
168+
169+
// Should still have only one dialog with original title
170+
expect(store.dialogStack).toHaveLength(1)
171+
expect(store.dialogStack[0].key).toBe('reusable-dialog')
172+
expect(store.dialogStack[0].title).toBe('Original Title')
173+
})
174+
})
175+
})

0 commit comments

Comments
 (0)