Skip to content

Commit 4e38429

Browse files
committed
feat(modal): implement stacked modals with ESC handling
1 parent 269f335 commit 4e38429

File tree

11 files changed

+71
-11
lines changed

11 files changed

+71
-11
lines changed

frontend/src/App.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,19 @@ EventsOn('onBeforeExitApp', async () => {
5353
5454
EventsOn('exitApp', () => exitApp())
5555
56-
const showError = (err: string) => {
57-
hasError.value = true
58-
message.error(err)
59-
}
56+
window.addEventListener('keydown', (e) => {
57+
if (e.key === 'Escape') {
58+
const closeFn = appStore.modalStack.at(-1)
59+
closeFn?.()
60+
}
61+
})
6062
6163
envStore.setupEnv().then(async () => {
64+
const showError = (err: string) => {
65+
hasError.value = true
66+
message.error(err)
67+
}
68+
6269
await Promise.all([
6370
appSettings.setupAppSettings(),
6471
profilesStore.setupProfiles(),

frontend/src/components/Card/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const hasTitle = computed(() => {
2121

2222
<template>
2323
<div class="gui-card rounded-8 relative flex flex-col">
24-
<div v-if="hasTitle" class="card-header flex items-center p-8">
24+
<div v-if="hasTitle" class="card-header flex items-center break-all p-8">
2525
<slot name="title-prefix"></slot>
2626
<div v-if="title" v-tips="title" class="card-header_title line-clamp-1 text-16 font-bold">
2727
{{ title }}

frontend/src/components/CodeViewer/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ const initEditor = () => {
173173
</script>
174174

175175
<template>
176-
<div ref="domRef">
176+
<div ref="domRef" @keydown.esc.stop @keydown.esc.prevent>
177177
<div class="flex justify-center">
178178
<Button loading type="link" />
179179
</div>

frontend/src/components/Input/index.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ defineExpose({
142142
@input="($event) => onInput($event)"
143143
@blur="onSubmit"
144144
@keydown.enter="inputRef?.blur"
145+
@keydown.esc.stop
146+
@keydown.esc.prevent
145147
autocomplete="off"
146148
ref="inputRef"
147149
class="flex-1 inline-block py-6 outline-none border-0 bg-transparent"

frontend/src/components/Modal/index.vue

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<script setup lang="ts">
2-
import { computed, provide, ref } from 'vue'
2+
import { computed, provide, ref, watch } from 'vue'
33
44
import { useBool } from '@/hooks'
55
import useI18n from '@/lang'
6+
import { useAppStore } from '@/stores'
7+
import { message } from '@/utils'
68
79
export interface Props {
810
title?: string
@@ -66,6 +68,8 @@ const open = defineModel('open', { default: false })
6668
const cancelLoading = ref(false)
6769
const submitLoading = ref(false)
6870
71+
const modalZindex = ref()
72+
const appStore = useAppStore()
6973
const [isMaximize, toggleMaximize] = useBool(false)
7074
// const [isMinimize, toggleMinimize] = useBool(false)
7175
@@ -102,6 +106,39 @@ const contentStyle = computed(() => ({
102106
height: props.height + '%',
103107
}))
104108
109+
let lastEscTime = 0
110+
let closeMessage: () => void
111+
112+
const closeFn = () => {
113+
if (props.maskClosable) {
114+
handleCancel()
115+
return
116+
}
117+
const now = performance.now()
118+
if (now - lastEscTime < 1000) {
119+
handleCancel()
120+
lastEscTime = 0
121+
closeMessage?.()
122+
} else {
123+
const { destroy } = message.info('common.pressAgainToClose', 1_000)
124+
closeMessage = destroy
125+
lastEscTime = now
126+
}
127+
}
128+
129+
watch(open, (v) => {
130+
if (v) {
131+
modalZindex.value = ++appStore.modalZIndexCounter
132+
appStore.modalStack.push(closeFn)
133+
} else {
134+
closeMessage?.()
135+
const idx = appStore.modalStack.findIndex((fn) => fn === closeFn)
136+
if (idx !== -1) {
137+
appStore.modalStack.splice(idx, 1)
138+
}
139+
}
140+
})
141+
105142
provide('cancel', handleCancel)
106143
provide('submit', handleSubmit)
107144
</script>
@@ -112,7 +149,8 @@ provide('submit', handleSubmit)
112149
<div
113150
v-if="open"
114151
@click.self="onMaskClick"
115-
class="gui-modal-mask fixed inset-0 z-999 flex items-center justify-center backdrop-blur-sm"
152+
:style="{ zIndex: modalZindex }"
153+
class="gui-modal-mask fixed inset-0 flex items-center justify-center backdrop-blur-sm"
116154
style="--wails-draggable: drag"
117155
>
118156
<div

frontend/src/lang/locale/en.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default {
4747
canceled: 'Canceled',
4848
downloading: 'Downloading...',
4949
empty: 'Data is empty',
50+
pressAgainToClose: 'Press again to close the modal',
5051
},
5152
kernel: {
5253
rule: 'Rule',

frontend/src/lang/locale/fa.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default {
4747
canceled: 'لغو شد',
4848
downloading: 'در حال دانلود...',
4949
empty: 'داده‌ای وجود ندارد',
50+
pressAgainToClose: 'برای بستن پنجره دوباره فشار دهید',
5051
},
5152
kernel: {
5253
rule: 'قانون',

frontend/src/lang/locale/ru.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default {
4747
canceled: 'Отменено',
4848
downloading: 'Скачивание...',
4949
empty: 'Данные отсутствуют',
50+
pressAgainToClose: 'Нажмите ещё раз, чтобы закрыть окно',
5051
},
5152
kernel: {
5253
rule: 'По правилам',

frontend/src/lang/locale/zh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export default {
4747
canceled: '已取消',
4848
downloading: '下载中...',
4949
empty: '数据为空',
50+
pressAgainToClose: '再按一次关闭弹窗',
5051
},
5152
kernel: {
5253
rule: '规则',

frontend/src/stores/app.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ export const useAppStore = defineStore('app', () => {
4747
y: 0,
4848
})
4949

50+
/* Modal Stack */
51+
const modalStack: (() => void)[] = []
52+
const modalZIndexCounter = 999
53+
5054
/* Actions */
5155
const customActions = ref<Recordable<(CustomAction | CustomActionFn)[]>>({
5256
core_state: [],
@@ -172,6 +176,8 @@ export const useAppStore = defineStore('app', () => {
172176
tipsShow,
173177
tipsMessage,
174178
tipsPosition,
179+
modalStack,
180+
modalZIndexCounter,
175181
showAbout,
176182
checkForUpdatesLoading,
177183
restartable,

0 commit comments

Comments
 (0)