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 @@
+
+
+
+
+
+
+ {{ alertTitle }}
+ {{ alertDescription }}
+
+
+
+
+ {{ alertCancelText }}
+
+
+
+ {{ alertOkText }}
+
+
+
+
+
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 @@