diff --git a/package-lock.json b/package-lock.json index 7af00a78..46cdb7eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,12 +21,12 @@ "@fontsource-variable/noto-sans-tc": "^5.2.9", "@kamiya4047/eslint-plugin-pretty-import": "^0.1.6", "@sentry/vite-plugin": "^4.6.1", - "@sentry/vue": "^10.32.1", + "@sentry/vue": "^10.33.0", "@tailwindcss/vite": "^4.1.18", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.18", "@types/jest": "^30.0.0", - "@types/node": "^25.0.6", + "@types/node": "^25.0.7", "@vee-validate/zod": "^4.15.1", "@vitejs/plugin-vue": "^6.0.3", "@vitejs/plugin-vue-jsx": "^5.1.3", @@ -4372,54 +4372,54 @@ ] }, "node_modules/@sentry-internal/browser-utils": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.32.1.tgz", - "integrity": "sha512-sjLLep1es3rTkbtAdTtdpc/a6g7v7bK5YJiZJsUigoJ4NTiFeMI5uIDCxbH/tjJ1q23YE1LzVn7T96I+qBRjHA==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.33.0.tgz", + "integrity": "sha512-nDJFHAfiFifBfJB0OF6DV6BIsIV5uah4lDsV4UBAgPBf+YAHclO10y1gi2U/JMh58c+s4lXi9p+PI1TFXZ0c6w==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/core": "10.32.1" + "@sentry/core": "10.33.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/feedback": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.32.1.tgz", - "integrity": "sha512-O24G8jxbfBY1RE/v2qFikPJISVMOrd/zk8FKyl+oUVYdOxU2Ucjk2cR3EQruBFlc7irnL6rT3GPfRZ/kBgLkmQ==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.33.0.tgz", + "integrity": "sha512-sN/VLWtEf0BeV6w6wldIpTxUQxNVc9o9tjLRQa8je1ZV2FCgXA124Iff/zsowsz82dLqtg7qp6GA5zYXVq+JMA==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/core": "10.32.1" + "@sentry/core": "10.33.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.32.1.tgz", - "integrity": "sha512-KKmLUgIaLRM0VjrMA1ByQTawZyRDYSkG2evvEOVpEtR9F0sumidAQdi7UY71QEKE1RYe/Jcp/3WoaqsMh8tbnQ==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.33.0.tgz", + "integrity": "sha512-UOU9PYxuXnPop3HoQ3l4Q7SZUXJC3Vmfm0Adgad8U03UcrThWIHYc5CxECSrVzfDFNOT7w9o7HQgRAgWxBPMXg==", "dev": true, "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "10.32.1", - "@sentry/core": "10.32.1" + "@sentry-internal/browser-utils": "10.33.0", + "@sentry/core": "10.33.0" }, "engines": { "node": ">=18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.32.1.tgz", - "integrity": "sha512-/XGTzWNWVc+B691fIVekV2KeoHFEDA5KftrLFAhEAW7uWOwk/xy3aQX4TYM0LcPm2PBKvoumlAD+Sd/aXk63oA==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.33.0.tgz", + "integrity": "sha512-MTmP6uoAVzw4CCPeqCgCLsRSiOfGLxgyMFjGTCW3E7t62MJ9S0H5sLsQ34sHxXUa1gFU9UNAjEvRRpZ0JvWrPw==", "dev": true, "license": "MIT", "dependencies": { - "@sentry-internal/replay": "10.32.1", - "@sentry/core": "10.32.1" + "@sentry-internal/replay": "10.33.0", + "@sentry/core": "10.33.0" }, "engines": { "node": ">=18" @@ -4436,17 +4436,17 @@ } }, "node_modules/@sentry/browser": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.32.1.tgz", - "integrity": "sha512-NPNCXTZ05ZGTFyJdKNqjykpFm+urem0ebosILQiw3C4BxNVNGH4vfYZexyl6prRhmg91oB6GjVNiVDuJiap1gg==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.33.0.tgz", + "integrity": "sha512-iWiPjik9zetM84jKfk01UveW1J0+X7w8XmJ8+IrhTyNDBVUWCRJWD8FrksiN1dRSg5mFWgfMRzKMz27hAScRwg==", "dev": true, "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "10.32.1", - "@sentry-internal/feedback": "10.32.1", - "@sentry-internal/replay": "10.32.1", - "@sentry-internal/replay-canvas": "10.32.1", - "@sentry/core": "10.32.1" + "@sentry-internal/browser-utils": "10.33.0", + "@sentry-internal/feedback": "10.33.0", + "@sentry-internal/replay": "10.33.0", + "@sentry-internal/replay-canvas": "10.33.0", + "@sentry/core": "10.33.0" }, "engines": { "node": ">=18" @@ -4647,9 +4647,9 @@ } }, "node_modules/@sentry/core": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.32.1.tgz", - "integrity": "sha512-PH2ldpSJlhqsMj2vCTyU0BI2Fx1oIDhm7Izo5xFALvjVCS0gmlqHt1udu6YlKn8BtpGH6bGzssvv5APrk+OdPQ==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.33.0.tgz", + "integrity": "sha512-ehH1VSUclIHZKEZVdv+klofsFIh8FFzqA6AAV23RtLepptzA8wqQzUGraEuSN25sYcNmYJ0jti5U0Ys+WZv5Dw==", "dev": true, "license": "MIT", "engines": { @@ -4671,14 +4671,14 @@ } }, "node_modules/@sentry/vue": { - "version": "10.32.1", - "resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.32.1.tgz", - "integrity": "sha512-3KVvjkBw18FgbYar87CevQNPRATtBrzi+xIRZf6uJG2Wnd9w+WH3+CQsjKwDvQiYyChiW4CYuFL2DuQ/VqOxfQ==", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.33.0.tgz", + "integrity": "sha512-CUtoBl62DG8mkoYfgpkw2WdB187XA2CfPj7OJdIzt3lavhpSAPmsY4jUarK2RUJvcowr5zYbEfv50Y0tsQxuGA==", "dev": true, "license": "MIT", "dependencies": { - "@sentry/browser": "10.32.1", - "@sentry/core": "10.32.1" + "@sentry/browser": "10.33.0", + "@sentry/core": "10.33.0" }, "engines": { "node": ">=18" @@ -5313,9 +5313,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz", - "integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==", + "version": "25.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.7.tgz", + "integrity": "sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 220f89d7..ba92f322 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,12 @@ "@fontsource-variable/noto-sans-tc": "^5.2.9", "@kamiya4047/eslint-plugin-pretty-import": "^0.1.6", "@sentry/vite-plugin": "^4.6.1", - "@sentry/vue": "^10.32.1", + "@sentry/vue": "^10.33.0", "@tailwindcss/vite": "^4.1.18", "@tanstack/vue-table": "^8.21.3", "@tanstack/vue-virtual": "^3.13.18", "@types/jest": "^30.0.0", - "@types/node": "^25.0.6", + "@types/node": "^25.0.7", "@vee-validate/zod": "^4.15.1", "@vitejs/plugin-vue": "^6.0.3", "@vitejs/plugin-vue-jsx": "^5.1.3", diff --git a/src/App.vue b/src/App.vue index b1cae1cd..1a7459fd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,6 +14,8 @@ + + @@ -29,6 +31,7 @@ import { createGlobalStores } from './stores'; import { initNoty } from './plugin/noty'; + import AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue'; import MacOSTitleBar from './components/MacOSTitleBar.vue'; import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue'; diff --git a/src/components/InstanceInfo.vue b/src/components/InstanceInfo.vue index eb118083..f27f94c8 100644 --- a/src/components/InstanceInfo.vue +++ b/src/components/InstanceInfo.vue @@ -67,11 +67,10 @@ import { reactive, watch } from 'vue'; import { Button } from '@/components/ui/button'; import { CaretBottom } from '@element-plus/icons-vue'; - import { ElMessageBox } from 'element-plus'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; - import { useGroupStore, useInstanceStore, useLocationStore, useUserStore } from '../stores'; + import { useGroupStore, useInstanceStore, useLocationStore, useModalStore, useUserStore } from '../stores'; import { formatDateFilter, hasGroupPermission } from '../shared/utils'; import { miscRequest } from '../api'; @@ -81,6 +80,7 @@ const userStore = useUserStore(); const groupStore = useGroupStore(); const instanceStore = useInstanceStore(); + const modalStore = useModalStore(); const props = defineProps({ location: String, @@ -126,13 +126,13 @@ } function closeInstance(location) { - ElMessageBox.confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'warning' - }) - .then(async (action) => { - if (action !== 'confirm') return; + modalStore + .confirm({ + description: 'Continue? Close Instance, nobody will be able to join', + title: 'Confirm' + }) + .then(async ({ ok }) => { + if (!ok) return; const args = await miscRequest.closeInstance({ location, hardClose: false }); if (args.json) { toast.success(t('message.instance.closed')); diff --git a/src/components/NavMenu.vue b/src/components/NavMenu.vue index 6885fb27..c6f946d2 100644 --- a/src/components/NavMenu.vue +++ b/src/components/NavMenu.vue @@ -246,8 +246,8 @@ + +}) .catch(() => {}); diff --git a/src/components/dialogs/InviteGroupDialog.vue b/src/components/dialogs/InviteGroupDialog.vue index 75d68707..723300ff 100644 --- a/src/components/dialogs/InviteGroupDialog.vue +++ b/src/components/dialogs/InviteGroupDialog.vue @@ -79,14 +79,13 @@ import { computed, nextTick, ref, watch } from 'vue'; import { Button } from '@/components/ui/button'; import { Check as CheckIcon } from 'lucide-vue-next'; - import { ElMessageBox } from 'element-plus'; import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; import { hasGroupPermission, userImage, userStatusClass } from '../../shared/utils'; + import { useFriendStore, useGroupStore, useModalStore } from '../../stores'; import { groupRequest, userRequest } from '../../api'; - import { useFriendStore, useGroupStore } from '../../stores'; import { VirtualCombobox } from '../ui/virtual-combobox'; import { getNextDialogIndex } from '../../shared/utils/base/ui'; @@ -96,6 +95,7 @@ const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore()); const { applyGroup } = useGroupStore(); const { t } = useI18n(); + const modalStore = useModalStore(); watch( () => inviteGroupDialog.value.visible, @@ -272,14 +272,15 @@ }); } function sendGroupInvite() { - ElMessageBox.confirm('Continue? Invite User(s) To Group', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - }) - .then((action) => { + modalStore + .confirm({ + description: 'Continue? Invite User(s) To Group', + title: 'Confirm' + }) + .then(({ ok }) => { + if (!ok) return; const D = inviteGroupDialog.value; - if (action !== 'confirm' || D.loading === true) { + if (D.loading === true) { return; } D.loading = true; diff --git a/src/components/dialogs/LaunchDialog.vue b/src/components/dialogs/LaunchDialog.vue index 8e7e10a3..8cdd9fa0 100644 --- a/src/components/dialogs/LaunchDialog.vue +++ b/src/components/dialogs/LaunchDialog.vue @@ -135,7 +135,6 @@ import { Button } from '@/components/ui/button'; import { ButtonGroup } from '@/components/ui/button-group'; import { Copy } from 'lucide-vue-next'; - import { ElMessageBox } from 'element-plus'; import { InputGroupField } from '@/components/ui/input-group'; import { MoreHorizontal } from 'lucide-vue-next'; import { Warning } from '@element-plus/icons-vue'; @@ -143,7 +142,14 @@ import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; - import { useFriendStore, useGameStore, useInviteStore, useLaunchStore, useLocationStore } from '../../stores'; + import { + useFriendStore, + useGameStore, + useInviteStore, + useLaunchStore, + useLocationStore, + useModalStore + } from '../../stores'; import { checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils'; import { instanceRequest, worldRequest } from '../../api'; import { getNextDialogIndex } from '../../shared/utils/base/ui'; @@ -153,6 +159,8 @@ const { t } = useI18n(); + const modalStore = useModalStore(); + const { friends } = storeToRefs(useFriendStore()); const { lastLocation } = storeToRefs(useLocationStore()); const { launchGame, tryOpenInstanceInVrc } = useLaunchStore(); @@ -245,16 +253,17 @@ } function handleLaunchGame(location, shortName, desktop) { if (isGameRunning.value) { - ElMessageBox.confirm(t('dialog.launch.game_running_warning'), t('dialog.launch.header'), { - confirmButtonText: t('dialog.launch.confirm_yes'), - cancelButtonText: t('dialog.launch.confirm_no'), - type: 'warning' - }) - .then((action) => { - if (action === 'confirm') { - launchGame(location, shortName, desktop); - isVisible.value = false; - } + modalStore + .confirm({ + description: t('dialog.launch.game_running_warning'), + title: t('dialog.launch.header'), + confirmText: t('dialog.launch.confirm_yes'), + cancelText: t('dialog.launch.confirm_no') + }) + .then(({ ok }) => { + if (!ok) return; + launchGame(location, shortName, desktop); + isVisible.value = false; }) .catch(() => {}); return; diff --git a/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue b/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue index b1c20a77..95147695 100644 --- a/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue +++ b/src/components/dialogs/PreviousInstancesDialog/PreviousInstancesGroupDialog.vue @@ -29,7 +29,6 @@ + + diff --git a/src/components/ui/alert-dialog/AlertDialogAction.vue b/src/components/ui/alert-dialog/AlertDialogAction.vue new file mode 100644 index 00000000..48697a55 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogAction.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogCancel.vue b/src/components/ui/alert-dialog/AlertDialogCancel.vue new file mode 100644 index 00000000..952476e0 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogCancel.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogContent.vue b/src/components/ui/alert-dialog/AlertDialogContent.vue new file mode 100644 index 00000000..1d1ed6b9 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogContent.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogDescription.vue b/src/components/ui/alert-dialog/AlertDialogDescription.vue new file mode 100644 index 00000000..b5d0eaa3 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogDescription.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogFooter.vue b/src/components/ui/alert-dialog/AlertDialogFooter.vue new file mode 100644 index 00000000..7a28957e --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogFooter.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogHeader.vue b/src/components/ui/alert-dialog/AlertDialogHeader.vue new file mode 100644 index 00000000..c1cb4ab1 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogHeader.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogModal.vue b/src/components/ui/alert-dialog/AlertDialogModal.vue new file mode 100644 index 00000000..c6a46c8b --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogModal.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogTitle.vue b/src/components/ui/alert-dialog/AlertDialogTitle.vue new file mode 100644 index 00000000..1d0f7c61 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogTitle.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/ui/alert-dialog/AlertDialogTrigger.vue b/src/components/ui/alert-dialog/AlertDialogTrigger.vue new file mode 100644 index 00000000..e98927a6 --- /dev/null +++ b/src/components/ui/alert-dialog/AlertDialogTrigger.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/ui/alert-dialog/index.js b/src/components/ui/alert-dialog/index.js new file mode 100644 index 00000000..88770f1e --- /dev/null +++ b/src/components/ui/alert-dialog/index.js @@ -0,0 +1,9 @@ +export { default as AlertDialog } from './AlertDialog.vue'; +export { default as AlertDialogAction } from './AlertDialogAction.vue'; +export { default as AlertDialogCancel } from './AlertDialogCancel.vue'; +export { default as AlertDialogContent } from './AlertDialogContent.vue'; +export { default as AlertDialogDescription } from './AlertDialogDescription.vue'; +export { default as AlertDialogFooter } from './AlertDialogFooter.vue'; +export { default as AlertDialogHeader } from './AlertDialogHeader.vue'; +export { default as AlertDialogTitle } from './AlertDialogTitle.vue'; +export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'; diff --git a/src/localization/en.json b/src/localization/en.json index 35acef57..1de84874 100644 --- a/src/localization/en.json +++ b/src/localization/en.json @@ -911,6 +911,11 @@ "pending_offline": "Pending Offline" }, "dialog": { + "alertdialog": { + "ok": "OK", + "cancel": "Cancel", + "confirm": "Confirm" + }, "user": { "status": { "active": "Active", diff --git a/src/service/request.js b/src/service/request.js index dc0c9d4b..ceae1c58 100644 --- a/src/service/request.js +++ b/src/service/request.js @@ -1,4 +1,3 @@ -import { ElMessageBox } from 'element-plus'; import { toast } from 'vue-sonner'; import Noty from 'noty'; @@ -6,6 +5,7 @@ import Noty from 'noty'; import { useAuthStore, useAvatarStore, + useModalStore, useNotificationStore, useUpdateLoopStore, useUserStore @@ -33,6 +33,7 @@ export function request(endpoint, options) { const userStore = useUserStore(); const avatarStore = useAvatarStore(); const authStore = useAuthStore(); + const modalStore = useModalStore(); const notificationStore = useNotificationStore(); const updateLoopStore = useUpdateLoopStore(); if ( @@ -187,10 +188,10 @@ export function request(endpoint, options) { } } if (status === 403 && endpoint === 'config') { - ElMessageBox.alert( - t('api.error.message.vpn_in_use'), - `403 ${t('api.error.message.login_error')}` - ).catch(() => {}); + modalStore.alert({ + description: t('api.error.message.vpn_in_use'), + title: `403 ${t('api.error.message.login_error')}` + }); authStore.handleLogoutEvent(); $throw(403, endpoint); } diff --git a/src/service/sqlite.js b/src/service/sqlite.js index 8359154e..ab84258d 100644 --- a/src/service/sqlite.js +++ b/src/service/sqlite.js @@ -1,22 +1,20 @@ -import { ElMessageBox } from 'element-plus'; - import { openExternalLink } from '../shared/utils'; +import { useModalStore } from '../stores'; // requires binding of SQLite class SQLiteService { handleSQLiteError(e) { if (typeof e.message === 'string') { + const modalStore = useModalStore(); if (e.message.includes('database disk image is malformed')) { - ElMessageBox.confirm( - 'Please repair or delete your database file by following these instructions.', - 'Your database is corrupted', - { - confirmButtonText: 'Confirm', - type: 'warning' - } - ) - .then(async (action) => { - if (action !== 'confirm') return; + modalStore + .confirm({ + description: + 'Please repair or delete your database file by following these instructions.', + title: 'Your database is corrupted' + }) + .then(({ ok }) => { + if (!ok) return; openExternalLink( 'https://github.com/vrcx-team/VRCX/wiki#how-to-repair-vrcx-database' ); @@ -24,37 +22,26 @@ class SQLiteService { .catch(() => {}); } if (e.message.includes('database or disk is full')) { - ElMessageBox.alert( - 'Please free up some disk space.', - 'Disk containing database is full', - { - confirmButtonText: 'OK', - type: 'warning' - } - ).catch(() => {}); + modalStore.alert({ + description: 'Please free up some disk space.', + title: 'Disk containing database is full' + }); } if ( e.message.includes('database is locked') || e.message.includes('attempt to write a readonly database') ) { - ElMessageBox.alert( - 'Please close other applications that might be using the database file.', - 'Database is locked', - { - confirmButtonText: 'OK', - type: 'warning' - } - ).catch(() => {}); + modalStore.alert({ + description: + 'Please close other applications that might be using the database file.', + title: 'Database is locked' + }); } if (e.message.includes('disk I/O error')) { - ElMessageBox.alert( - 'Please check your disk for errors.', - 'Disk I/O error', - { - confirmButtonText: 'OK', - type: 'warning' - } - ).catch(() => {}); + modalStore.alert({ + description: 'Please check your disk for errors.', + title: 'Disk I/O error' + }); } } throw e; diff --git a/src/shared/utils/common.js b/src/shared/utils/common.js index c111c3d8..f0f1121e 100644 --- a/src/shared/utils/common.js +++ b/src/shared/utils/common.js @@ -1,4 +1,3 @@ -import { ElMessageBox } from 'element-plus'; import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; @@ -7,6 +6,7 @@ import Noty from 'noty'; import { useAvatarStore, useInstanceStore, + useModalStore, useSearchStore, useWorldStore } from '../../stores'; @@ -395,24 +395,22 @@ function openExternalLink(link) { return; } - ElMessageBox.confirm(`${link}`, 'Open External Link', { - distinguishCancelAndClose: true, - confirmButtonText: 'Open', - cancelButtonText: 'Copy', - type: 'info', - beforeClose: (action, instance, done) => { - if (action === 'cancel') { - copyToClipboard(link); - } - done(); - } - }) - .then((action) => { - if (action === 'confirm') { - AppApi.OpenLink(link); - } + const modalStore = useModalStore(); + modalStore + .confirm({ + description: `${link}`, + title: 'Open External Link', + confirmText: 'Open', + cancelText: 'Copy' }) - .catch(() => {}); + // TODO: beforeClose alert dialog + .then(({ ok }) => { + if (!ok) { + copyToClipboard(link, 'Link copied to clipboard!'); + return; + } + AppApi.OpenLink(link); + }); } /** diff --git a/src/stores/auth.js b/src/stores/auth.js index 54f3d4e1..9aa30ddd 100644 --- a/src/stores/auth.js +++ b/src/stores/auth.js @@ -13,6 +13,7 @@ import { database } from '../service/database'; import { escapeTag } from '../shared/utils'; import { request } from '../service/request'; import { useAdvancedSettingsStore } from './settings/advanced'; +import { useModalStore } from './modal'; import { useNotificationStore } from './notification'; import { useUpdateLoopStore } from './updateLoop'; import { useUserStore } from './user'; @@ -27,6 +28,7 @@ export const useAuthStore = defineStore('Auth', () => { const notificationStore = useNotificationStore(); const userStore = useUserStore(); const updateLoopStore = useUpdateLoopStore(); + const modalStore = useModalStore(); const { t } = useI18n(); const state = reactive({ @@ -405,21 +407,20 @@ export const useAuthStore = defineStore('Auth', () => { } function logout() { - ElMessageBox.confirm('Continue? Logout', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - }) - .then((action) => { - if (action === 'confirm') { - const existingStyle = document.getElementById( - 'login-container-style' - ); - if (existingStyle) { - existingStyle.parentNode.removeChild(existingStyle); - } - handleLogoutEvent(); + modalStore + .confirm({ + description: 'Continue? Logout', + title: 'Confirm' + }) + .then(({ ok }) => { + if (!ok) return; + const existingStyle = document.getElementById( + 'login-container-style' + ); + if (existingStyle) { + existingStyle.parentNode.removeChild(existingStyle); } + handleLogoutEvent(); }) .catch(() => {}); } diff --git a/src/stores/avatar.js b/src/stores/avatar.js index d60489c2..cceb1a0f 100644 --- a/src/stores/avatar.js +++ b/src/stores/avatar.js @@ -1,5 +1,4 @@ import { nextTick, ref, watch } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; @@ -18,6 +17,7 @@ import { database } from '../service/database'; import { useAdvancedSettingsStore } from './settings/advanced'; import { useAvatarProviderStore } from './avatarProvider'; import { useFavoriteStore } from './favorite'; +import { useModalStore } from './modal'; import { useUserStore } from './user'; import { useVRCXUpdaterStore } from './vrcxUpdater'; import { watchState } from '../service/watchState'; @@ -30,6 +30,7 @@ export const useAvatarStore = defineStore('Avatar', () => { const vrcxUpdaterStore = useVRCXUpdaterStore(); const advancedSettingsStore = useAdvancedSettingsStore(); const userStore = useUserStore(); + const modalStore = useModalStore(); let cachedAvatarModerations = new Map(); let cachedAvatars = new Map(); @@ -389,12 +390,13 @@ export const useAvatarStore = defineStore('Avatar', () => { } function promptClearAvatarHistory() { - ElMessageBox.confirm('Continue? Clear Avatar History', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - }) - .then(() => { + modalStore + .confirm({ + description: 'Continue? Clear Avatar History', + title: 'Confirm' + }) + .then(({ ok }) => { + if (!ok) return; clearAvatarHistory(); }) .catch(() => {}); @@ -552,12 +554,13 @@ export const useAvatarStore = defineStore('Avatar', () => { } function selectAvatarWithConfirmation(id) { - ElMessageBox.confirm(`Continue? Select Avatar`, 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - }) - .then(() => { + modalStore + .confirm({ + description: 'Continue? Select Avatar', + title: 'Confirm' + }) + .then(({ ok }) => { + if (!ok) return; selectAvatarWithoutConfirmation(id); }) .catch(() => {}); diff --git a/src/stores/friend.js b/src/stores/friend.js index 66087a44..71101cda 100644 --- a/src/stores/friend.js +++ b/src/stores/friend.js @@ -1,5 +1,4 @@ import { computed, reactive, ref, watch } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; @@ -27,6 +26,7 @@ import { useFeedStore } from './feed'; import { useGeneralSettingsStore } from './settings/general'; import { useGroupStore } from './group'; import { useLocationStore } from './location'; +import { useModalStore } from './modal'; import { useNotificationStore } from './notification'; import { useSharedFeedStore } from './sharedFeed'; import { useUiStore } from './ui'; @@ -51,6 +51,7 @@ export const useFriendStore = defineStore('Friend', () => { const authStore = useAuthStore(); const locationStore = useLocationStore(); const favoriteStore = useFavoriteStore(); + const modalStore = useModalStore(); const { t } = useI18n(); const state = reactive({ @@ -1578,12 +1579,13 @@ export const useFriendStore = defineStore('Friend', () => { } function confirmDeleteFriend(id) { - ElMessageBox.confirm('Continue? Unfriend', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - }) - .then(async () => { + modalStore + .confirm({ + description: 'Continue? Unfriend', + title: 'Confirm' + }) + .then(async ({ ok }) => { + if (!ok) return; const args = await friendRequest.deleteFriend({ userId: id }); diff --git a/src/stores/gallery.js b/src/stores/gallery.js index 0a1d3187..e8195ac7 100644 --- a/src/stores/gallery.js +++ b/src/stores/gallery.js @@ -1,5 +1,4 @@ import { reactive, ref, shallowReactive, watch } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { useI18n } from 'vue-i18n'; @@ -19,9 +18,10 @@ import { } from '../api'; import { AppDebug } from '../service/appConfig'; import { handleImageUploadInput } from '../shared/utils/imageUpload'; -import { useAdvancedSettingsStore } from './settings/advanced'; -import { watchState } from '../service/watchState'; import { router } from '../plugin/router'; +import { useAdvancedSettingsStore } from './settings/advanced'; +import { useModalStore } from './modal'; +import { watchState } from '../service/watchState'; import miscReq from '../api/misc'; @@ -30,6 +30,7 @@ import * as workerTimers from 'worker-timers'; export const useGalleryStore = defineStore('Gallery', () => { const advancedSettingsStore = useAdvancedSettingsStore(); const { t } = useI18n(); + const modalStore = useModalStore(); const state = reactive({ printCache: [], @@ -515,17 +516,15 @@ export const useGalleryStore = defineStore('Gallery', () => { } } catch (e) { if (e.message.includes('Could not find file')) { - ElMessageBox.confirm( - 'Windows has blocked VRCX from creating files on your system. Please allow VRCX to create files to save emojis, would you like to see instructions on how to fix this?', - 'Failed to create emoji folder', - { - confirmButtonText: 'Confirm', - cancelButtonText: 'Ignore', - type: 'warning' - } - ) - .then(async (action) => { - if (action !== 'confirm') return; + modalStore + .confirm({ + description: + 'Windows has blocked VRCX from creating files on your system. Please allow VRCX to create files to save emojis, would you like to see instructions on how to fix this?', + title: 'Failed to create emoji folder', + cancelText: 'Ignore' + }) + .then(({ ok }) => { + if (!ok) return; openExternalLink( 'https://www.youtube.com/watch?v=1mwmmCdA4D8&t=213s' ); diff --git a/src/stores/game.js b/src/stores/game.js index dce2f88f..d9b8718d 100644 --- a/src/stores/game.js +++ b/src/stores/game.js @@ -1,5 +1,4 @@ import { reactive, ref } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; @@ -14,6 +13,7 @@ import { useGameLogStore } from './gameLog'; import { useInstanceStore } from './instance'; import { useLaunchStore } from './launch'; import { useLocationStore } from './location'; +import { useModalStore } from './modal'; import { useNotificationStore } from './notification'; import { useUpdateLoopStore } from './updateLoop'; import { useUserStore } from './user'; @@ -36,6 +36,7 @@ export const useGameStore = defineStore('Game', () => { const vrStore = useVrStore(); const userStore = useUserStore(); const updateLoopStore = useUpdateLoopStore(); + const modalStore = useModalStore(); const state = reactive({ lastCrashedTime: null @@ -218,17 +219,19 @@ export const useGameStore = defineStore('Game', () => { ); if (!result) { // failed to set key - ElMessageBox.alert( - 'VRCX has noticed VRChat debug logging is disabled. VRCX requires debug logging in order to function correctly. Please enable debug logging in VRChat quick menu settings > debug > enable debug logging, then rejoin the instance or restart VRChat.', - 'Enable debug logging' - ).catch(() => {}); + modalStore.alert({ + description: + 'VRCX has noticed VRChat debug logging is disabled. VRCX requires debug logging in order to function correctly. Please enable debug logging in VRChat quick menu settings > debug > enable debug logging, then rejoin the instance or restart VRChat.', + title: 'Enable debug logging' + }); console.error('Failed to enable debug logging', result); return; } - ElMessageBox.alert( - 'VRCX has noticed VRChat debug logging is disabled and automatically re-enabled it. VRCX requires debug logging in order to function correctly.', - 'Enabled debug logging' - ).catch(() => {}); + modalStore.alert({ + description: + 'VRCX has noticed VRChat debug logging is disabled and automatically re-enabled it. VRCX requires debug logging in order to function correctly.', + title: 'Enabled debug logging' + }); console.log('Enabled debug logging'); } catch (e) { console.error(e); diff --git a/src/stores/gameLog.js b/src/stores/gameLog.js index b680a6db..77ff84a1 100644 --- a/src/stores/gameLog.js +++ b/src/stores/gameLog.js @@ -1,5 +1,4 @@ import { reactive, ref, shallowReactive, watch } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; @@ -23,6 +22,7 @@ import { useGameStore } from './game'; import { useGeneralSettingsStore } from './settings/general'; import { useInstanceStore } from './instance'; import { useLocationStore } from './location'; +import { useModalStore } from './modal'; import { useNotificationStore } from './notification'; import { usePhotonStore } from './photon'; import { useSharedFeedStore } from './sharedFeed'; @@ -54,6 +54,7 @@ export const useGameLogStore = defineStore('GameLog', () => { const galleryStore = useGalleryStore(); const photonStore = usePhotonStore(); const sharedFeedStore = useSharedFeedStore(); + const modalStore = useModalStore(); const state = reactive({ lastLocationAvatarList: new Map() @@ -1414,15 +1415,14 @@ export const useGameLogStore = defineStore('GameLog', () => { return; } if (!advancedSettingsStore.gameLogDisabled) { - ElMessageBox.confirm('Continue? Disable GameLog', 'Confirm', { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - }) - .then(({ action }) => { - if (action === 'confirm') { - advancedSettingsStore.setGameLogDisabled(); - } + modalStore + .confirm({ + description: 'Continue? Disable GameLog', + title: 'Confirm' + }) + .then(({ ok }) => { + if (!ok) return; + advancedSettingsStore.setGameLogDisabled(); }) .catch(() => {}); } else { diff --git a/src/stores/group.js b/src/stores/group.js index cae28494..55f6ba01 100644 --- a/src/stores/group.js +++ b/src/stores/group.js @@ -1,5 +1,4 @@ import { nextTick, reactive, ref, watch } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; @@ -18,6 +17,7 @@ import { database } from '../service/database.js'; import { groupDialogFilterOptions } from '../shared/constants/'; import { useGameStore } from './game'; import { useInstanceStore } from './instance'; +import { useModalStore } from './modal'; import { useNotificationStore } from './notification'; import { useUserStore } from './user'; import { watchState } from '../service/watchState'; @@ -31,6 +31,7 @@ export const useGroupStore = defineStore('Group', () => { const gameStore = useGameStore(); const userStore = useUserStore(); const notificationStore = useNotificationStore(); + const modalStore = useModalStore(); let cachedGroups = new Map(); @@ -555,16 +556,13 @@ export const useGroupStore = defineStore('Group', () => { } function leaveGroupPrompt(groupId) { - ElMessageBox.confirm( - 'Are you sure you want to leave this group?', - 'Confirm', - { - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - type: 'info' - } - ) - .then(() => { + modalStore + .confirm({ + description: 'Are you sure you want to leave this group?', + title: 'Confirm' + }) + .then(({ ok }) => { + if (!ok) return; leaveGroup(groupId); }) .catch(() => {}); diff --git a/src/stores/index.js b/src/stores/index.js index d5bd5a3b..ab416f0f 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -21,6 +21,7 @@ import { useInstanceStore } from './instance'; import { useInviteStore } from './invite'; import { useLaunchStore } from './launch'; import { useLocationStore } from './location'; +import { useModalStore } from './modal'; import { useModerationStore } from './moderation'; import { useNotificationStore } from './notification'; import { useNotificationsSettingsStore } from './settings/notifications'; @@ -162,7 +163,8 @@ export function createGlobalStores() { updateLoop: useUpdateLoopStore(), auth: useAuthStore(), vrcStatus: useVrcStatusStore(), - charts: useChartsStore() + charts: useChartsStore(), + modal: useModalStore() }; } @@ -200,5 +202,6 @@ export { useWorldStore, useSharedFeedStore, useUpdateLoopStore, - useVrcStatusStore + useVrcStatusStore, + useModalStore }; diff --git a/src/stores/modal.js b/src/stores/modal.js new file mode 100644 index 00000000..40bf698b --- /dev/null +++ b/src/stores/modal.js @@ -0,0 +1,178 @@ +import { defineStore } from 'pinia'; +import { i18n } from '@/plugin'; +import { ref } from 'vue'; + +function translate(key, fallback) { + try { + return i18n.global.t(key); + } catch { + return fallback; + } +} + +/** + * @typedef {Object} ConfirmResult + * @property {boolean} ok + * @property {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason + */ + +/** + * @typedef {Object} ConfirmOptions + * @property {string} title + * @property {string} description + * @property {string=} confirmText + * @property {string=} cancelText + * @property {boolean=} dismissible // true: allow esc/outside, false: block + */ + +/** + * @typedef {Object} AlertOptions + * @property {string} title + * @property {string} description + * @property {string=} confirmText + * @property {boolean=} dismissible + */ + +// TODO: Method chains for confirm + +export const useModalStore = defineStore('Modal', () => { + const alertOpen = ref(false); + const alertMode = ref('confirm'); // 'confirm' | 'alert' + const alertTitle = ref(''); + const alertDescription = ref(''); + const alertOkText = ref(''); + const alertCancelText = ref(''); + const alertDismissible = ref(true); + + /** @type {{ resolve: ((result: ConfirmResult) => void) | null } | null} */ + let pending = null; + + function closeDialog() { + alertOpen.value = false; + } + + /** + * @param {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason + */ + function finish(reason) { + const resolve = pending?.resolve; + pending = null; + closeDialog(); + if (resolve) resolve({ ok: reason === 'ok', reason }); + } + + /** + * @param {'ok' | 'cancel' | 'dismiss' | 'replaced'} reason + */ + function finishWithoutClosing(reason) { + const resolve = pending?.resolve; + pending = null; + if (resolve) resolve({ ok: reason === 'ok', reason }); + } + + /** + * @param {'confirm' | 'alert'} mode + * @param {any} options + */ + function openBase(mode, options) { + if (pending) { + // old dialog is force-finished + // do not call closeDialog here because we are about to open a new one + finishWithoutClosing('replaced'); + } + + alertMode.value = mode; + alertTitle.value = options.title; + alertDescription.value = options.description; + alertDismissible.value = options.dismissible !== false; + + if (mode === 'alert') { + alertOkText.value = + options.confirmText || translate('dialog.alertdialog.ok', 'OK'); + alertCancelText.value = ''; + } else { + alertOkText.value = + options.confirmText || + translate('dialog.alertdialog.confirm', 'Confirm'); + alertCancelText.value = + options.cancelText || + translate('dialog.alertdialog.cancel', 'Cancel'); + } + + alertOpen.value = true; + + return new Promise((resolve) => { + pending = { resolve }; + }); + } + + /** + * confirm: always resolve({ok, reason}) + * @param {ConfirmOptions} options + * @returns {Promise} + */ + function confirm(options) { + return openBase('confirm', options); + } + + /** + * alert: always resolve({ok:true, reason:'ok'}) when closed + * @param {AlertOptions} options + * @returns {Promise} + */ + function alert(options) { + return openBase('alert', options); + } + + function handleOk() { + if (!pending) return; + finish('ok'); + } + + function handleCancel() { + if (!pending) return; + + // alert has no cancel semantics + if (alertMode.value === 'alert') { + finish('ok'); + return; + } + + finish('cancel'); + } + + function handleDismiss() { + if (!pending) return; + if (!alertDismissible.value) return; + + // alert: dismiss also means done + if (alertMode.value === 'alert') { + finish('ok'); + return; + } + + finish('dismiss'); + } + + function setAlertOpen(open) { + alertOpen.value = !!open; + } + + return { + alertOpen, + alertMode, + alertTitle, + alertDescription, + alertOkText, + alertCancelText, + alertDismissible, + + confirm, + alert, + + handleOk, + handleCancel, + handleDismiss, + setAlertOpen + }; +}); diff --git a/src/stores/settings/advanced.js b/src/stores/settings/advanced.js index b9cceda7..1839050b 100644 --- a/src/stores/settings/advanced.js +++ b/src/stores/settings/advanced.js @@ -8,6 +8,7 @@ import { AppDebug } from '../../service/appConfig'; import { database } from '../../service/database'; import { languageCodes } from '../../localization'; import { useGameStore } from '../game'; +import { useModalStore } from '../modal'; import { useVRCXUpdaterStore } from '../vrcxUpdater'; import { useVrcxStore } from '../vrcx'; import { watchState } from '../../service/watchState'; @@ -19,6 +20,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => { const gameStore = useGameStore(); const vrcxStore = useVrcxStore(); const VRCXUpdaterStore = useVRCXUpdaterStore(); + const modalStore = useModalStore(); const { t } = useI18n(); @@ -466,68 +468,58 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => { } async function checkSentryConsent() { - const { action: consentAction } = await ElMessageBox.confirm( - 'Help improve VRCX by allowing anonymous error reporting?

' + - '• Only collects crash and error information.
' + - '• No personal data or VRChat information is collected.
' + - '• Only enabled in nightly builds.
' + - '• Can be disabled at anytime in Advanced Settings.', - 'Anonymous Error Reporting', - { - type: 'warning', - center: true, - dangerouslyUseHTMLString: true, - closeOnClickModal: false, - closeOnPressEscape: false, - distinguishCancelAndClose: true - } - ).catch(() => ({ action: 'cancel' })); + modalStore + .confirm({ + description: + 'Help improve VRCX by allowing anonymous error reporting?

' + + '• Only collects crash and error information.
' + + '• No personal data or VRChat information is collected.
' + + '• Only enabled in nightly builds.
' + + '• Can be disabled at anytime in Advanced Settings.', + title: 'Anonymous Error Reporting' + }) + .then(async ({ ok }) => { + if (!ok) return; + modalStore + .confirm({ + description: + 'Error reporting setting has been enabled. Would you like to restart VRCX now for the change to take effect?', + title: 'Restart Required', + confirmText: 'Restart Now', + cancelText: 'Later' + }) + .then(async ({ ok }) => { + if (!ok) return; - if (consentAction === 'cancel') return; + sentryErrorReporting.value = true; + configRepository.setBool('VRCX_SentryEnabled', true); - const { action: restartAction } = await ElMessageBox.confirm( - 'Error reporting setting has been enabled. Would you like to restart VRCX now for the change to take effect?', - 'Restart Required', - { - confirmButtonText: 'Restart Now', - cancelButtonText: 'Later', - type: 'warning', - center: true, - closeOnClickModal: false, - closeOnPressEscape: false - } - ).catch(() => ({ action: 'cancel' })); - - if (restartAction === 'cancel') return; - - sentryErrorReporting.value = true; - configRepository.setBool('VRCX_SentryEnabled', true); - - VRCXUpdaterStore.restartVRCX(false); + VRCXUpdaterStore.restartVRCX(false); + }); + }); } async function setSentryErrorReporting() { if (VRCXUpdaterStore.branch !== 'Nightly') return; - const { action: restartAction } = await ElMessageBox.confirm( - 'Error reporting setting has been disabled. Would you like to restart VRCX now for the change to take effect?', - 'Restart Required', - { - confirmButtonText: 'Restart Now', - cancelButtonText: 'Later', - type: 'info', - center: true - } - ).catch(() => ({ action: 'cancel' })); + modalStore + .confirm({ + description: + 'Error reporting setting has been disabled. Would you like to restart VRCX now for the change to take effect?', + title: 'Restart Required', + confirmText: 'Restart Now', + cancelText: 'Later' + }) + .then(async ({ ok }) => { + if (!ok) return; - if (restartAction === 'cancel') return; - - sentryErrorReporting.value = !sentryErrorReporting.value; - await configRepository.setBool( - 'VRCX_SentryEnabled', - sentryErrorReporting.value - ); - VRCXUpdaterStore.restartVRCX(false); + sentryErrorReporting.value = !sentryErrorReporting.value; + await configRepository.setBool( + 'VRCX_SentryEnabled', + sentryErrorReporting.value + ); + VRCXUpdaterStore.restartVRCX(false); + }); } async function getSqliteTableSizes() { @@ -735,96 +727,87 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => { function cropPrintsChanged() { if (!cropInstancePrints.value) return; - ElMessageBox.confirm( - t( - 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old' - ), - { - confirmButtonText: t( + modalStore + .confirm({ + description: t( + 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old' + ), + title: '', + confirmText: t( 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_confirm' ), - cancelButtonText: t( + cancelText: t( 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_cancel' - ), - type: 'info', - showInput: false - } - ) - .then(async ({ action }) => { - if (action === 'confirm') { - const msgBox = toast.warning( - 'Batch print cropping in progress...', - { duration: Infinity, position: 'bottom-right' } - ); - try { - await AppApi.CropAllPrints(ugcFolderPath.value); - toast.success('Batch print cropping complete'); - } catch (err) { - console.error(err); - toast.error(`Batch print cropping failed: ${err}`); - } finally { - toast.dismiss(msgBox); - } + ) + }) + .then(async ({ ok }) => { + if (!ok) return; + const msgBox = toast.warning( + 'Batch print cropping in progress...', + { duration: Infinity, position: 'bottom-right' } + ); + try { + await AppApi.CropAllPrints(ugcFolderPath.value); + toast.success('Batch print cropping complete'); + } catch (err) { + console.error(err); + toast.error(`Batch print cropping failed: ${err}`); + } finally { + toast.dismiss(msgBox); } }) .catch(() => {}); } function askDeleteAllScreenshotMetadata() { - ElMessageBox.confirm( - t( - 'view.settings.advanced.advanced.delete_all_screenshot_metadata.ask' - ), - { - confirmButtonText: t( + modalStore + .confirm({ + description: t( + 'view.settings.advanced.advanced.delete_all_screenshot_metadata.ask' + ), + title: '', + confirmText: t( 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm_yes' ), - cancelButtonText: t( + cancelText: t( 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm_no' - ), - type: 'warning', - showInput: false - } - ) - .then(({ action }) => { - if (action === 'confirm') { - deleteAllScreenshotMetadata(); - } + ) + }) + .then(({ ok }) => { + if (!ok) return; + deleteAllScreenshotMetadata(); }) .catch(() => {}); } function deleteAllScreenshotMetadata() { - ElMessageBox.confirm( - t( - 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm' - ), - { - confirmButtonText: t( + modalStore + .confirm({ + description: t( + 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm' + ), + title: '', + confirmText: t( 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_confirm' ), - cancelButtonText: t( + cancelText: t( 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_cancel' - ), - type: 'warning', - showInput: false - } - ) - .then(async ({ action }) => { - if (action === 'confirm') { - const msgBox = toast.warning( - 'Batch metadata removal in progress...', - { duration: Infinity, position: 'bottom-right' } - ); - try { - await AppApi.DeleteAllScreenshotMetadata(); - toast.success('Batch metadata removal complete'); - } catch (err) { - console.error(err); - toast.error(`Batch metadata removal failed: ${err}`); - } finally { - toast.dismiss(msgBox); - } + ) + }) + .then(async ({ ok }) => { + if (!ok) return; + const msgBox = toast.warning( + 'Batch metadata removal in progress...', + { duration: Infinity, position: 'bottom-right' } + ); + try { + await AppApi.DeleteAllScreenshotMetadata(); + toast.success('Batch metadata removal complete'); + } catch (err) { + console.error(err); + toast.error(`Batch metadata removal failed: ${err}`); + } finally { + toast.dismiss(msgBox); } }) .catch(() => {}); diff --git a/src/stores/vrcx.js b/src/stores/vrcx.js index ba377cf2..4320e865 100644 --- a/src/stores/vrcx.js +++ b/src/stores/vrcx.js @@ -1,5 +1,4 @@ import { reactive, ref, watch } from 'vue'; -import { ElMessageBox } from 'element-plus'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; @@ -26,6 +25,7 @@ import { useGameStore } from './game'; import { useGroupStore } from './group'; import { useInstanceStore } from './instance'; import { useLocationStore } from './location'; +import { useModalStore } from './modal'; import { useNotificationStore } from './notification'; import { usePhotonStore } from './photon'; import { useSearchStore } from './search'; @@ -58,6 +58,7 @@ export const useVrcxStore = defineStore('Vrcx', () => { const vrcStatusStore = useVrcStatusStore(); const galleryStore = useGalleryStore(); const { t } = useI18n(); + const modalStore = useModalStore(); const state = reactive({ databaseVersion: 0, @@ -711,10 +712,10 @@ export const useVrcxStore = defineStore('Vrcx', () => { return; } // popup message about auto restore - ElMessageBox.alert( - t('dialog.registry_backup.restore_prompt'), - t('dialog.registry_backup.header') - ).catch(() => {}); + modalStore.alert({ + description: t('dialog.registry_backup.restore_prompt'), + title: t('dialog.registry_backup.header') + }); showRegistryBackupDialog(); await AppApi.FocusWindow(); await configRepository.setString( diff --git a/src/views/Charts/components/MutualFriends.vue b/src/views/Charts/components/MutualFriends.vue index 9312dc73..e2d5eed4 100644 --- a/src/views/Charts/components/MutualFriends.vue +++ b/src/views/Charts/components/MutualFriends.vue @@ -142,16 +142,21 @@ import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from '@/components/ui/field'; import { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field'; import { Button } from '@/components/ui/button'; - import { ElMessageBox } from 'element-plus'; - import { Settings } from 'lucide-vue-next'; import { Progress } from '@/components/ui/progress'; + import { Settings } from 'lucide-vue-next'; import { Spinner } from '@/components/ui/spinner'; import { onBeforeRouteLeave } from 'vue-router'; import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; - import { useAppearanceSettingsStore, useChartsStore, useFriendStore, useUserStore } from '../../../stores'; + import { + useAppearanceSettingsStore, + useChartsStore, + useFriendStore, + useModalStore, + useUserStore + } from '../../../stores'; import { applyForceOverrides, computeForceOptions, useMutualGraphChart } from '../composables/useMutualGraphChart'; import { createRateLimiter, executeWithBackoff } from '../../../shared/utils'; import { database } from '../../../service/database'; @@ -164,6 +169,7 @@ const { t } = useI18n(); const friendStore = useFriendStore(); const userStore = useUserStore(); + const modalStore = useModalStore(); const chartsStore = useChartsStore(); const appearanceStore = useAppearanceSettingsStore(); const { friends } = storeToRefs(friendStore); @@ -396,39 +402,35 @@ if (isFetching.value || hasFetched.value || !totalFriends.value) { return; } - try { - await ElMessageBox.confirm( - t('view.charts.mutual_friend.prompt.message'), - t('view.charts.mutual_friend.prompt.title'), - { - confirmButtonText: t('view.charts.mutual_friend.prompt.confirm'), - cancelButtonText: t('view.charts.mutual_friend.prompt.cancel'), - type: 'warning' - } - ); - await startFetch(); - } catch { - // cancelled - } + + modalStore + .confirm({ + description: t('view.charts.mutual_friend.prompt.message'), + title: t('view.charts.mutual_friend.prompt.title'), + confirmText: t('view.charts.mutual_friend.prompt.confirm'), + cancelText: t('view.charts.mutual_friend.prompt.cancel') + }) + .then(async ({ ok }) => { + if (!ok) return; + + await startFetch(); + }); } function promptEnableMutualFriendsSharing() { - ElMessageBox.confirm( - t('view.charts.mutual_friend.enable_sharing_prompt.message'), - t('view.charts.mutual_friend.enable_sharing_prompt.title'), - { - confirmButtonText: t('view.charts.mutual_friend.enable_sharing_prompt.confirm'), - cancelButtonText: t('view.charts.mutual_friend.enable_sharing_prompt.cancel'), - type: 'info' - } - ) - .then(() => { + modalStore + .confirm({ + description: t('view.charts.mutual_friend.enable_sharing_prompt.message'), + title: t('view.charts.mutual_friend.enable_sharing_prompt.title'), + confirmText: t('view.charts.mutual_friend.enable_sharing_prompt.confirm'), + cancelText: t('view.charts.mutual_friend.enable_sharing_prompt.cancel') + }) + .then(({ ok }) => { + if (!ok) return; userStore.toggleSharedConnectionsOptOut(); promptInitialFetch(); }) - .catch(() => { - // cancelled - }); + .catch(() => {}); } function cancelFetch() { diff --git a/src/views/Favorites/FavoritesAvatar.vue b/src/views/Favorites/FavoritesAvatar.vue index 3a9585ef..3b4cd132 100644 --- a/src/views/Favorites/FavoritesAvatar.vue +++ b/src/views/Favorites/FavoritesAvatar.vue @@ -521,18 +521,7 @@