replace ElMessageBox(alert, confirm) with alert dialog

This commit is contained in:
pa
2026-01-13 22:40:13 +09:00
committed by Natsumi
parent 870c7a4938
commit fc5afe9e69
53 changed files with 1250 additions and 862 deletions

78
package-lock.json generated
View File

@@ -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": {

View File

@@ -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",

View File

@@ -14,6 +14,8 @@
<RouterView></RouterView>
<Toaster position="top-center"></Toaster>
<AlertDialogModal></AlertDialogModal>
<VRCXUpdateDialog></VRCXUpdateDialog>
</div>
</el-config-provider>
@@ -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';

View File

@@ -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'));

View File

@@ -246,8 +246,8 @@
<script setup>
import { computed, defineAsyncComponent, onMounted, ref, watch } from 'vue';
import { ElMessageBox, dayjs } from 'element-plus';
import { Button } from '@/components/ui/button';
import { dayjs } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@@ -255,6 +255,7 @@
import {
useAppearanceSettingsStore,
useAuthStore,
useModalStore,
useSearchStore,
useUiStore,
useVRCXUpdaterStore
@@ -269,6 +270,7 @@
const { t, locale } = useI18n();
const router = useRouter();
const modalStore = useModalStore();
const createDefaultNavLayout = () => [
{ type: 'item', key: 'feed' },
@@ -559,12 +561,15 @@
};
const handleCustomNavReset = () => {
ElMessageBox.confirm(t('nav_menu.custom_nav.restore_default_confirm'), {
type: 'warning',
confirmButtonText: t('nav_menu.custom_nav.restore_default'),
cancelButtonText: t('nav_menu.custom_nav.cancel')
})
.then(async () => {
modalStore
.confirm({
description: t('nav_menu.custom_nav.restore_default_confirm'),
title: t('confirm.title'),
confirmText: t('nav_menu.custom_nav.restore_default'),
cancelText: t('nav_menu.custom_nav.cancel')
})
.then(async ({ ok }) => {
if (!ok) return;
const defaults = sanitizeLayout(createDefaultNavLayout());
navLayout.value = defaults;
await saveNavLayout(defaults);

View File

@@ -559,6 +559,7 @@
useFavoriteStore,
useGalleryStore,
useGameStore,
useModalStore,
useUserStore
} from '../../../stores';
import {
@@ -591,6 +592,7 @@
const { deleteVRChatCache } = useGameStore();
const { showFullscreenImageDialog } = useGalleryStore();
const { isDarkMode } = storeToRefs(useAppearanceSettingsStore());
const modalStore = useModalStore();
const { t } = useI18n();
@@ -752,15 +754,13 @@
showFavoriteDialog('avatar', D.id);
break;
default:
ElMessageBox.confirm(`Continue? ${command}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action !== 'confirm') {
return;
}
modalStore
.confirm({
title: 'Confirm',
description: `Continue? ${command}`
})
.then(({ ok }) => {
if (!ok) return;
switch (command) {
case 'Delete Favorite':
favoriteRequest.deleteFavorite({
@@ -897,8 +897,7 @@
});
break;
}
})
.catch(() => {});
});
break;
}
}

View File

@@ -1146,11 +1146,10 @@
View,
Warning
} from '@element-plus/icons-vue';
import { Card } from '@/components/ui/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { Card } from '@/components/ui/card';
import { InputGroupField } from '@/components/ui/input-group';
import { RefreshCcw } from 'lucide-vue-next';
import { Spinner } from '@/components/ui/spinner';
@@ -1176,6 +1175,14 @@
userImage,
userStatusClass
} from '../../../shared/utils';
import {
useAppearanceSettingsStore,
useGalleryStore,
useGroupStore,
useLocationStore,
useModalStore,
useUserStore
} from '../../../stores';
import {
DropdownMenu,
DropdownMenuContent,
@@ -1183,13 +1190,6 @@
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../ui/dropdown-menu';
import {
useAppearanceSettingsStore,
useGalleryStore,
useGroupStore,
useLocationStore,
useUserStore
} from '../../../stores';
import { formatJsonVars, getNextDialogIndex } from '../../../shared/utils/base/ui';
import { groupDialogFilterOptions, groupDialogSortingOptions } from '../../../shared/constants';
import { Badge } from '../../ui/badge';
@@ -1203,6 +1203,8 @@
const { t } = useI18n();
const modalStore = useModalStore();
const { showUserDialog } = useUserStore();
const { currentUser } = storeToRefs(useUserStore());
const { groupDialog, inviteGroupDialog } = storeToRefs(useGroupStore());
@@ -1469,43 +1471,42 @@
});
}
function confirmDeleteGroupPost(post) {
ElMessageBox.confirm('Are you sure you want to delete this post?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
groupRequest
.deleteGroupPost({
groupId: post.groupId,
postId: post.id
})
.then((args) => {
const D = groupDialog.value;
if (D.id !== args.params.groupId) {
return;
}
modalStore
.confirm({
description: 'Are you sure you want to delete this post?',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
groupRequest
.deleteGroupPost({
groupId: post.groupId,
postId: post.id
})
.then((args) => {
const D = groupDialog.value;
if (D.id !== args.params.groupId) {
return;
}
const postId = args.params.postId;
// remove existing post
for (const item of D.posts) {
if (item.id === postId) {
removeFromArray(D.posts, item);
break;
}
const postId = args.params.postId;
// remove existing post
for (const item of D.posts) {
if (item.id === postId) {
removeFromArray(D.posts, item);
break;
}
// remove/update announcement
if (postId === D.announcement.id) {
if (D.posts.length > 0) {
D.announcement = D.posts[0];
} else {
D.announcement = {};
}
}
// remove/update announcement
if (postId === D.announcement.id) {
if (D.posts.length > 0) {
D.announcement = D.posts[0];
} else {
D.announcement = {};
}
updateGroupPostSearch();
});
}
}
updateGroupPostSearch();
});
})
.catch(() => {});
}
@@ -1571,46 +1572,44 @@
}
function blockGroup(groupId) {
ElMessageBox.confirm('Are you sure you want to block this group?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
groupRequest
.blockGroup({
groupId
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
}
modalStore
.confirm({
description: 'Are you sure you want to block this group?',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
groupRequest
.blockGroup({
groupId
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
})
.catch(() => {});
}
function unblockGroup(groupId) {
ElMessageBox.confirm('Are you sure you want to unblock this group?', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
groupRequest
.unblockGroup({
groupId,
userId: currentUser.value.id
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
}
modalStore
.confirm({
description: 'Are you sure you want to unblock this group?',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
groupRequest
.unblockGroup({
groupId,
userId: currentUser.value.id
})
.then((args) => {
if (groupDialog.value.visible && groupDialog.value.id === args.params.groupId) {
showGroupDialog(args.params.groupId);
}
});
})
.catch(() => {});
}

View File

@@ -87,12 +87,11 @@
import { computed, ref } 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 { useFriendStore, useGalleryStore, useInviteStore, useUserStore } from '../../../stores';
import { useFriendStore, useGalleryStore, useInviteStore, useModalStore, useUserStore } from '../../../stores';
import { parseLocation, userImage, userStatusClass } from '../../../shared/utils';
import { instanceRequest, notificationRequest } from '../../../api';
import { VirtualCombobox } from '../../ui/virtual-combobox';
@@ -104,6 +103,8 @@
const { currentUser } = storeToRefs(useUserStore());
const { clearInviteImageUpload } = useGalleryStore();
const modalStore = useModalStore();
const { t } = useI18n();
const props = defineProps({
inviteDialog: {
@@ -233,14 +234,15 @@
}
function sendInvite() {
ElMessageBox.confirm('Continue? Invite', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
modalStore
.confirm({
description: 'Continue? Invite',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
const D = props.inviteDialog;
if (action !== 'confirm' || D.loading === true) {
if (D.loading === true) {
return;
}
D.loading = true;
@@ -275,7 +277,8 @@
}
};
inviteLoop();
})
.catch(() => {});
});
}
</script>
}) .catch(() => {});

View File

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

View File

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

View File

@@ -29,7 +29,6 @@
<script setup>
import { computed, nextTick, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -41,7 +40,7 @@
removeFromArray,
timeToText
} from '../../../shared/utils';
import { useInstanceStore, useSearchStore, useUiStore, useVrcxStore } from '../../../stores';
import { useInstanceStore, useModalStore, useSearchStore, useUiStore, useVrcxStore } from '../../../stores';
import { DataTableLayout } from '../../ui/data-table';
import { createColumns } from './previousInstancesGroupColumns.jsx';
import { database } from '../../../service/database';
@@ -56,6 +55,8 @@
const previousInstancesGroupDialogIndex = ref(2000);
const loading = ref(false);
const modalStore = useModalStore();
const vrcxStore = useVrcxStore();
const rawRows = ref([]);
const search = ref('');
@@ -169,15 +170,14 @@
}
function deleteGameLogGroupInstancePrompt(row) {
ElMessageBox.confirm('Continue? Delete GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deleteGameLogGroupInstance(row);
}
modalStore
.confirm({
description: 'Continue? Delete GameLog Instance',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
deleteGameLogGroupInstance(row);
})
.catch(() => {});
}

View File

@@ -30,11 +30,18 @@
<script setup>
import { computed, nextTick, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import {
useInstanceStore,
useModalStore,
useSearchStore,
useUiStore,
useUserStore,
useVrcxStore
} from '../../../stores';
import {
compareByCreatedAt,
localeIncludes,
@@ -42,7 +49,6 @@
removeFromArray,
timeToText
} from '../../../shared/utils';
import { useInstanceStore, useSearchStore, useUiStore, useUserStore, useVrcxStore } from '../../../stores';
import { DataTableLayout } from '../../ui/data-table';
import { createColumns } from './previousInstancesWorldColumns.jsx';
import { database } from '../../../service/database';
@@ -51,6 +57,8 @@
const { t } = useI18n();
const modalStore = useModalStore();
const props = defineProps({
previousInstancesWorldDialog: {
type: Object,
@@ -164,15 +172,14 @@
}
function deleteGameLogWorldInstancePrompt(row) {
ElMessageBox.confirm('Continue? Delete GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deleteGameLogWorldInstance(row);
}
modalStore
.confirm({
description: 'Continue? Delete GameLog Instance',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
deleteGameLogWorldInstance(row);
})
.catch(() => {});
}

View File

@@ -30,11 +30,18 @@
<script setup>
import { computed, nextTick, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import {
useInstanceStore,
useLaunchStore,
useModalStore,
useSearchStore,
useUiStore,
useVrcxStore
} from '../../../stores';
import {
compareByCreatedAt,
localeIncludes,
@@ -42,7 +49,6 @@
removeFromArray,
timeToText
} from '../../../shared/utils';
import { useInstanceStore, useLaunchStore, useSearchStore, useUiStore, useVrcxStore } from '../../../stores';
import { DataTableLayout } from '../../ui/data-table';
import { createColumns } from './previousInstancesUserColumns.jsx';
import { database } from '../../../service/database';
@@ -68,6 +74,8 @@
});
const emit = defineEmits(['update:previous-instances-user-dialog']);
const modalStore = useModalStore();
const loading = ref(false);
const rawRows = ref([]);
const search = ref('');
@@ -189,13 +197,14 @@
}
function deleteGameLogUserInstancePrompt(row) {
ElMessageBox.confirm('Continue? Delete User From GameLog Instance', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') deleteGameLogUserInstance(row);
modalStore
.confirm({
description: 'Continue? Delete User From GameLog Instance',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
deleteGameLogUserInstance(row);
})
.catch(() => {});
}

View File

@@ -1318,7 +1318,6 @@
import { Download, LogOut, RefreshCcw } from 'lucide-vue-next';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { ElMessageBox } from 'element-plus';
import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
@@ -1358,6 +1357,7 @@
useGroupStore,
useInviteStore,
useLocationStore,
useModalStore,
useModerationStore,
useUiStore,
useUserStore,
@@ -1393,6 +1393,8 @@
const { t } = useI18n();
const modalStore = useModalStore();
const { hideUserNotes, hideUserMemos, isDarkMode } = storeToRefs(useAppearanceSettingsStore());
const { bioLanguage, avatarRemoteDatabase, translationApi, translationApiType } =
storeToRefs(useAdvancedSettingsStore());
@@ -1894,19 +1896,17 @@
? command
: t(`${i18nPreFix}${formattedCommand}`);
ElMessageBox.confirm(
t('confirm.message', {
command: displayCommandText
}),
t('confirm.title'),
{
confirmButtonText: t('confirm.confirm_button'),
cancelButtonText: t('confirm.cancel_button'),
type: 'info'
}
)
.then((action) => {
if (action === 'confirm') {
modalStore
.confirm({
description: t('confirm.message', {
command: displayCommandText
}),
title: t('confirm.title'),
confirmText: t('confirm.confirm_button'),
cancelText: t('confirm.cancel_button')
})
.then(({ ok }) => {
if (ok) {
performUserDialogCommand(command, D.id);
}
})
@@ -2422,22 +2422,21 @@
}
function resetHome() {
ElMessageBox.confirm('Continue? Reset Home', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
userRequest
.saveCurrentUser({
homeLocation: ''
})
.then((args) => {
toast.success('Home world has been reset');
return args;
});
}
modalStore
.confirm({
description: 'Continue? Reset Home',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
userRequest
.saveCurrentUser({
homeLocation: ''
})
.then((args) => {
toast.success('Home world has been reset');
return args;
});
})
.catch(() => {});
}

View File

@@ -795,6 +795,7 @@
useInstanceStore,
useInviteStore,
useLocationStore,
useModalStore,
useUserStore,
useWorldStore
} from '../../../stores';
@@ -810,6 +811,8 @@
import { Badge } from '../../ui/badge';
import { database } from '../../../service/database.js';
const modalStore = useModalStore();
const NewInstanceDialog = defineAsyncComponent(() => import('../NewInstanceDialog.vue'));
const PreviousInstancesWorldDialog = defineAsyncComponent(
() => import('../PreviousInstancesDialog/PreviousInstancesWorldDialog.vue')
@@ -988,15 +991,13 @@
case 'Unpublish':
case 'Delete Persistent Data':
case 'Delete':
ElMessageBox.confirm(`Continue? ${command}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action !== 'confirm') {
return;
}
modalStore
.confirm({
description: `Continue? ${command}`,
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) return;
switch (command) {
case 'Delete Favorite':
favoriteRequest.deleteFavorite({

View File

@@ -0,0 +1,17 @@
<script setup>
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui';
const props = defineProps({
open: { type: Boolean, required: false },
defaultOpen: { type: Boolean, required: false }
});
const emits = defineEmits(['update:open']);
const forwarded = useForwardPropsEmits(props, emits);
</script>
<template>
<AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
<slot v-bind="slotProps" />
</AlertDialogRoot>
</template>

View File

@@ -0,0 +1,20 @@
<script setup>
import { AlertDialogAction } from 'reka-ui';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
<slot />
</AlertDialogAction>
</template>

View File

@@ -0,0 +1,22 @@
<script setup>
import { AlertDialogCancel } from 'reka-ui';
import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<AlertDialogCancel
v-bind="delegatedProps"
:class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)">
<slot />
</AlertDialogCancel>
</template>

View File

@@ -0,0 +1,48 @@
<script setup>
import { AlertDialogContent, AlertDialogOverlay, AlertDialogPortal, useForwardPropsEmits } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
defineOptions({
inheritAttrs: false
});
const props = defineProps({
forceMount: { type: Boolean, required: false },
disableOutsidePointerEvents: { type: Boolean, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const emits = defineEmits([
'escapeKeyDown',
'pointerDownOutside',
'focusOutside',
'interactOutside',
'openAutoFocus',
'closeAutoFocus'
]);
const delegatedProps = reactiveOmit(props, 'class');
const forwarded = useForwardPropsEmits(delegatedProps, emits);
</script>
<template>
<AlertDialogPortal>
<AlertDialogOverlay
data-slot="alert-dialog-overlay"
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-10000 bg-black/80" />
<AlertDialogContent
data-slot="alert-dialog-content"
v-bind="{ ...$attrs, ...forwarded }"
:class="
cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-10000 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
props.class
)
">
<slot />
</AlertDialogContent>
</AlertDialogPortal>
</template>

View File

@@ -0,0 +1,22 @@
<script setup>
import { AlertDialogDescription } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<AlertDialogDescription
data-slot="alert-dialog-description"
v-bind="delegatedProps"
:class="cn('text-muted-foreground text-sm', props.class)">
<slot />
</AlertDialogDescription>
</template>

View File

@@ -0,0 +1,15 @@
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<div
data-slot="alert-dialog-footer"
:class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,13 @@
<script setup>
import { cn } from '@/lib/utils';
const props = defineProps({
class: { type: null, required: false }
});
</script>
<template>
<div data-slot="alert-dialog-header" :class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)">
<slot />
</div>
</template>

View File

@@ -0,0 +1,67 @@
<script setup>
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog';
import { storeToRefs } from 'pinia';
import { useModalStore } from '@/stores';
const modalStore = useModalStore();
const { alertOpen, alertMode, alertTitle, alertDescription, alertOkText, alertCancelText, alertDismissible } =
storeToRefs(modalStore);
function onEscapeKeyDown(event) {
if (!alertDismissible.value) {
event.preventDefault();
return;
}
modalStore.handleDismiss();
}
function onPointerDownOutside(event) {
if (!alertDismissible.value) {
event.preventDefault();
return;
}
modalStore.handleDismiss();
}
function onInteractOutside(event) {
if (!alertDismissible.value) {
event.preventDefault();
return;
}
modalStore.handleDismiss();
}
</script>
<template>
<AlertDialog :open="alertOpen" @update:open="modalStore.setAlertOpen">
<AlertDialogContent
@escapeKeyDown="onEscapeKeyDown"
@pointerDownOutside="onPointerDownOutside"
@interactOutside="onInteractOutside">
<AlertDialogHeader>
<AlertDialogTitle>{{ alertTitle }}</AlertDialogTitle>
<AlertDialogDescription>{{ alertDescription }}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel v-if="alertMode === 'confirm'" @click="modalStore.handleCancel">
{{ alertCancelText }}
</AlertDialogCancel>
<AlertDialogAction @click="modalStore.handleOk">
{{ alertOkText }}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</template>

View File

@@ -0,0 +1,22 @@
<script setup>
import { AlertDialogTitle } from 'reka-ui';
import { cn } from '@/lib/utils';
import { reactiveOmit } from '@vueuse/core';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false },
class: { type: null, required: false }
});
const delegatedProps = reactiveOmit(props, 'class');
</script>
<template>
<AlertDialogTitle
data-slot="alert-dialog-title"
v-bind="delegatedProps"
:class="cn('text-lg font-semibold', props.class)">
<slot />
</AlertDialogTitle>
</template>

View File

@@ -0,0 +1,14 @@
<script setup>
import { AlertDialogTrigger } from 'reka-ui';
const props = defineProps({
asChild: { type: Boolean, required: false },
as: { type: null, required: false }
});
</script>
<template>
<AlertDialogTrigger data-slot="alert-dialog-trigger" v-bind="props">
<slot />
</AlertDialogTrigger>
</template>

View File

@@ -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';

View File

@@ -911,6 +911,11 @@
"pending_offline": "Pending Offline"
},
"dialog": {
"alertdialog": {
"ok": "OK",
"cancel": "Cancel",
"confirm": "Confirm"
},
"user": {
"status": {
"active": "Active",

View File

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

View File

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

View File

@@ -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);
});
}
/**

View File

@@ -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(() => {});
}

View File

@@ -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(() => {});

View File

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

View File

@@ -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'
);

View File

@@ -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);

View File

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

View File

@@ -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(() => {});

View File

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

178
src/stores/modal.js Normal file
View File

@@ -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<ConfirmResult>}
*/
function confirm(options) {
return openBase('confirm', options);
}
/**
* alert: always resolve({ok:true, reason:'ok'}) when closed
* @param {AlertOptions} options
* @returns {Promise<ConfirmResult>}
*/
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
};
});

View File

@@ -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?</br></br>' +
'• Only collects crash and error information.</br>' +
'• No personal data or VRChat information is collected.</br>' +
'• Only enabled in nightly builds.</br>' +
'• 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?</br></br>' +
'• Only collects crash and error information.</br>' +
'• No personal data or VRChat information is collected.</br>' +
'• Only enabled in nightly builds.</br>' +
'• 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(() => {});

View File

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

View File

@@ -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() {

View File

@@ -521,18 +521,7 @@
</template>
<script setup>
import {
computed,
h,
markRaw,
nextTick,
onBeforeMount,
onBeforeUnmount,
onMounted,
reactive,
ref,
watch
} from 'vue';
import { computed, markRaw, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { Ellipsis, Loader, RefreshCcw } from 'lucide-vue-next';
import { MoreFilled, Plus, Refresh } from '@element-plus/icons-vue';
import { InputGroupField, InputGroupSearch } from '@/components/ui/input-group';
@@ -558,7 +547,13 @@
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../components/ui/dropdown-menu';
import { useAppearanceSettingsStore, useAvatarStore, useFavoriteStore, useUserStore } from '../../stores';
import {
useAppearanceSettingsStore,
useAvatarStore,
useFavoriteStore,
useModalStore,
useUserStore
} from '../../stores';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { avatarRequest, favoriteRequest } from '../../api';
@@ -597,6 +592,7 @@
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore();
const favoriteStore = useFavoriteStore();
const modalStore = useModalStore();
const {
favoriteAvatars,
favoriteAvatarGroups,
@@ -1183,26 +1179,22 @@
showAvatarImportDialog();
}
function showAvatarBulkUnfavoriteSelectionConfirm() {
async function showAvatarBulkUnfavoriteSelectionConfirm() {
if (!selectedFavoriteAvatars.value.length) {
return;
}
const total = selectedFavoriteAvatars.value.length;
ElMessageBox.confirm(
`Are you sure you want to unfavorite ${total} favorites?\n This action cannot be undone.`,
`Delete ${total} favorites?`,
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
}
)
.then((action) => {
if (action === 'confirm') {
bulkUnfavoriteSelectedAvatars([...selectedFavoriteAvatars.value]);
}
})
.catch(() => {});
const result = await modalStore.confirm({
description: `Are you sure you want to unfavorite ${total} favorites?\nThis action cannot be undone.`,
title: `Delete ${total} favorites?`
});
if (!result.ok) {
return;
}
bulkUnfavoriteSelectedAvatars([...selectedFavoriteAvatars.value]);
}
function bulkUnfavoriteSelectedAvatars(ids) {
@@ -1252,17 +1244,14 @@
async function handleCheckInvalidAvatars(groupName) {
handleGroupMenuVisible(localGroupMenuKey(groupName), false);
try {
await ElMessageBox.confirm(
t('view.favorite.avatars.check_description'),
t('view.favorite.avatars.check_invalid'),
{
confirmButtonText: t('confirm.confirm_button'),
cancelButtonText: t('confirm.cancel_button'),
type: 'info'
}
);
} catch {
const startCheckResult = await modalStore.confirm({
description: t('view.favorite.avatars.check_description'),
title: t('view.favorite.avatars.check_invalid'),
confirmText: t('confirm.confirm_button'),
cancelText: t('confirm.cancel_button')
});
if (!startCheckResult.ok) {
return;
}
@@ -1299,54 +1288,29 @@
return;
}
const confirmDelete = await ElMessageBox.confirm(
h('div', [
h(
'p',
{ style: 'margin-bottom: 12px;' },
t('view.favorite.avatars.confirm_delete_description', { count: result.invalid })
),
h(
'div',
{ style: 'margin-top: 12px; margin-bottom: 8px; font-weight: 600;' },
t('view.favorite.avatars.removed_list_header')
),
h(
'div',
{
style: 'max-height: 200px; overflow-y: auto; background: var(--el-fill-color-lighter); padding: 8px; border-radius: 4px;'
},
result.invalidIds.map((id) =>
h('div', { style: 'font-family: monospace; font-size: 12px; padding: 2px 0;' }, id)
)
)
]),
t('view.favorite.avatars.confirm_delete_invalid'),
{
confirmButtonText: t('confirm.confirm_button'),
cancelButtonText: t('view.favorite.avatars.copy_removed_ids'),
distinguishCancelAndClose: true,
type: 'warning',
beforeClose: (action, instance, done) => {
if (action === 'cancel') {
navigator.clipboard
.writeText(result.invalidIds.join('\n'))
.then(() => {
toast.success(t('view.favorite.avatars.copied_ids'));
})
.catch(() => {
toast.error(t('view.favorite.avatars.copy_failed'));
});
return;
}
done();
}
}
)
.then(() => true)
.catch(() => false);
const invalidIdsText = result.invalidIds.join('\n');
if (!confirmDelete) {
const confirmDeleteResult = await modalStore.confirm({
description:
`${t('view.favorite.avatars.confirm_delete_description', { count: result.invalid })}` +
`\n\n${t('view.favorite.avatars.removed_list_header')}\n` +
invalidIdsText,
title: t('view.favorite.avatars.confirm_delete_invalid'),
confirmText: t('confirm.confirm_button'),
cancelText: t('view.favorite.avatars.copy_removed_ids')
});
if (!confirmDeleteResult.ok) {
if (confirmDeleteResult.reason === 'cancel') {
navigator.clipboard
.writeText(invalidIdsText)
.then(() => {
toast.success(t('view.favorite.avatars.copied_ids'));
})
.catch(() => {
toast.error(t('view.favorite.avatars.copy_failed'));
});
}
toast.info(t('view.favorite.avatars.delete_cancelled'));
return;
}
@@ -1434,18 +1398,16 @@
}
function clearFavoriteGroup(ctx) {
ElMessageBox.confirm('Continue? Clear Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
}
modalStore
.confirm({
description: 'Continue? Clear Group',
title: 'Confirm'
})
.then(() => {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
})
.catch(() => {});
}
@@ -1477,16 +1439,12 @@
}
function promptLocalAvatarFavoriteGroupDelete(group) {
ElMessageBox.confirm(`Delete Group? ${group}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deleteLocalAvatarFavoriteGroup(group);
}
modalStore
.confirm({
description: `Delete Group? ${group}`,
title: 'Confirm'
})
.then(() => deleteLocalAvatarFavoriteGroup(group))
.catch(() => {});
}

View File

@@ -331,9 +331,9 @@
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../components/ui/dropdown-menu';
import { useAppearanceSettingsStore, useFavoriteStore, useModalStore, useUserStore } from '../../stores';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { useAppearanceSettingsStore, useFavoriteStore, useUserStore } from '../../stores';
import { Badge } from '../../components/ui/badge';
import { Slider } from '../../components/ui/slider';
import { Switch } from '../../components/ui/switch';
@@ -358,6 +358,7 @@
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore();
const favoriteStore = useFavoriteStore();
const modalStore = useModalStore();
const {
favoriteFriends,
favoriteFriendGroups,
@@ -751,20 +752,12 @@
return;
}
const total = selectedFavoriteFriends.value.length;
ElMessageBox.confirm(
`Are you sure you want to unfavorite ${total} favorites?\n This action cannot be undone.`,
`Delete ${total} favorites?`,
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
}
)
.then((action) => {
if (action === 'confirm') {
bulkUnfavoriteSelectedFriends([...selectedFavoriteFriends.value]);
}
modalStore
.confirm({
description: `Are you sure you want to unfavorite ${total} favorites?\n This action cannot be undone.`,
title: `Delete ${total} favorites?`
})
.then(({ ok }) => ok && bulkUnfavoriteSelectedFriends([...selectedFavoriteFriends.value]))
.catch(() => {});
}
@@ -779,18 +772,16 @@
}
function clearFavoriteGroup(ctx) {
ElMessageBox.confirm('Continue? Clear Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
}
modalStore
.confirm({
description: 'Continue? Clear Group',
title: 'Confirm'
})
.then(() => {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
})
.catch(() => {});
}

View File

@@ -461,9 +461,9 @@
DropdownMenuSeparator,
DropdownMenuTrigger
} from '../../components/ui/dropdown-menu';
import { useAppearanceSettingsStore, useFavoriteStore, useModalStore, useWorldStore } from '../../stores';
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { useAppearanceSettingsStore, useFavoriteStore, useWorldStore } from '../../stores';
import { favoriteRequest, worldRequest } from '../../api';
import { Badge } from '../../components/ui/badge';
import { Slider } from '../../components/ui/slider';
@@ -490,6 +490,7 @@
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore();
const favoriteStore = useFavoriteStore();
const modalStore = useModalStore();
const {
favoriteWorlds,
favoriteWorldGroups,
@@ -1054,21 +1055,13 @@
return;
}
const total = selectedFavoriteWorlds.value.length;
ElMessageBox.confirm(
`Are you sure you want to unfavorite ${total} favorites?
modalStore
.confirm({
description: `Are you sure you want to unfavorite ${total} favorites?
This action cannot be undone.`,
`Delete ${total} favorites?`,
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
}
)
.then((action) => {
if (action === 'confirm') {
bulkUnfavoriteSelectedWorlds([...selectedFavoriteWorlds.value]);
}
title: `Delete ${total} favorites?`
})
.then(() => bulkUnfavoriteSelectedWorlds([...selectedFavoriteWorlds.value]))
.catch(() => {});
}
@@ -1175,32 +1168,26 @@
}
function promptLocalWorldFavoriteGroupDelete(group) {
ElMessageBox.confirm(`Delete Group? ${group}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deleteLocalWorldFavoriteGroup(group);
}
modalStore
.confirm({
description: `Delete Group? ${group}`,
title: 'Confirm'
})
.then(() => deleteLocalWorldFavoriteGroup(group))
.catch(() => {});
}
function clearFavoriteGroup(ctx) {
ElMessageBox.confirm('Continue? Clear Group', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
}
modalStore
.confirm({
description: 'Continue? Clear Group',
title: 'Confirm'
})
.then(() => {
favoriteRequest.clearFavoriteGroup({
type: ctx.type,
group: ctx.name
});
})
.catch(() => {});
}

View File

@@ -108,7 +108,6 @@
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, nextTick, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group';
import { Progress } from '@/components/ui/progress';
import { storeToRefs } from 'pinia';
@@ -119,6 +118,7 @@
import {
useAppearanceSettingsStore,
useFriendStore,
useModalStore,
useSearchStore,
useUserStore,
useVrcxStore
@@ -138,6 +138,7 @@
const emit = defineEmits(['lookup-user']);
const { friends } = storeToRefs(useFriendStore());
const modalStore = useModalStore();
const { getAllUserStats, getAllUserMutualCount, confirmDeleteFriend, handleFriendDelete } = useFriendStore();
const { randomUserColours } = storeToRefs(useAppearanceSettingsStore());
const vrcxStore = useVrcxStore();
@@ -324,25 +325,18 @@
.filter((item) => selectedFriends.value.has(item.id))
.map((item) => item.displayName);
if (!pending.length) return;
ElMessageBox.confirm(
`Are you sure you want to delete ${pending.length} friends?
This can negatively affect your trust rank,
This action cannot be undone.`,
`Delete ${pending.length} friends?`,
{
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info',
showInput: true,
inputType: 'textarea',
inputValue: pending.join('\r\n')
}
)
.then(({ action }) => {
if (action === 'confirm') {
bulkUnfriendSelection();
}
const description =
`Are you sure you want to delete ${pending.length} friends?\n` +
'This can negatively affect your trust rank,\n' +
'This action cannot be undone.\n\n' +
pending.join('\n');
modalStore
.confirm({
description,
title: `Delete ${pending.length} friends?`
})
.then(({ ok }) => ok && bulkUnfriendSelection())
.catch(() => {});
}
@@ -355,9 +349,9 @@
selectedFriends.value.delete(item.id);
}
}
ElMessageBox.alert(`Unfriended ${selectedFriends.value.size} friends.`, 'Bulk Unfriend Complete', {
confirmButtonText: 'OK',
type: 'success'
modalStore.alert({
description: `Unfriended ${selectedFriends.value.size} friends.`,
title: 'Bulk Unfriend Complete'
});
selectedFriends.value.clear();
}

View File

@@ -47,7 +47,6 @@
<script setup>
import { computed, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
@@ -61,7 +60,7 @@
SelectTrigger,
SelectValue
} from '../../components/ui/select';
import { useAppearanceSettingsStore, useFriendStore, useVrcxStore } from '../../stores';
import { useAppearanceSettingsStore, useFriendStore, useModalStore, useVrcxStore } from '../../stores';
import { DataTableLayout } from '../../components/ui/data-table';
import { InputGroupField } from '../../components/ui/input-group';
import { createColumns } from './columns.jsx';
@@ -74,6 +73,7 @@
const appearanceSettingsStore = useAppearanceSettingsStore();
const vrcxStore = useVrcxStore();
const modalStore = useModalStore();
const { hideUnfriends } = storeToRefs(appearanceSettingsStore);
const { friendLogTable } = storeToRefs(useFriendStore());
@@ -149,16 +149,12 @@
saveTableFilters();
}
function deleteFriendLogPrompt(row) {
ElMessageBox.confirm('Continue? Delete Log', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deleteFriendLog(row);
}
modalStore
.confirm({
description: 'Continue? Delete Log',
title: 'Confirm'
})
.then(({ ok }) => ok && deleteFriendLog(row))
.catch(() => {});
}
function deleteFriendLog(row) {

View File

@@ -57,14 +57,13 @@
<script setup>
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { Switch } from '@/components/ui/switch';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import dayjs from 'dayjs';
import { useAppearanceSettingsStore, useGameLogStore, useVrcxStore } from '../../stores';
import { useAppearanceSettingsStore, useGameLogStore, useModalStore, useVrcxStore } from '../../stores';
import { DataTableLayout } from '../../components/ui/data-table';
import { InputGroupField } from '../../components/ui/input-group';
import { createColumns } from './columns.jsx';
@@ -77,6 +76,7 @@
const { gameLogTable } = storeToRefs(useGameLogStore());
const appearanceSettingsStore = useAppearanceSettingsStore();
const vrcxStore = useVrcxStore();
const modalStore = useModalStore();
function getGameLogCreatedAt(row) {
if (typeof row?.created_at === 'string' && row.created_at.length > 0) {
@@ -134,16 +134,12 @@
});
function deleteGameLogEntryPrompt(row) {
ElMessageBox.confirm('Continue? Delete Log', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deleteGameLogEntry(row);
}
modalStore
.confirm({
description: 'Continue? Delete Log',
title: 'Confirm'
})
.then(({ ok }) => ok && deleteGameLogEntry(row))
.catch(() => {});
}

View File

@@ -51,14 +51,13 @@
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group';
import { Refresh } from '@element-plus/icons-vue';
import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia';
import { useI18n } from 'vue-i18n';
import { useAppearanceSettingsStore, useModerationStore, useVrcxStore } from '../../stores';
import { useAppearanceSettingsStore, useModalStore, useModerationStore, useVrcxStore } from '../../stores';
import { DataTableLayout } from '../../components/ui/data-table';
import { createColumns } from './columns.jsx';
import { moderationTypes } from '../../shared/constants';
@@ -73,6 +72,7 @@
const { refreshPlayerModerations, handlePlayerModerationDelete } = useModerationStore();
const appearanceSettingsStore = useAppearanceSettingsStore();
const vrcxStore = useVrcxStore();
const modalStore = useModalStore();
const moderationRef = ref(null);
const { tableStyle: tableHeightStyle } = useDataTableScrollHeight(moderationRef, {
@@ -110,16 +110,12 @@
}
function deletePlayerModerationPrompt(row) {
ElMessageBox.confirm(`Continue? Delete Moderation ${row.type}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
deletePlayerModeration(row);
}
modalStore
.confirm({
description: `Continue? Delete Moderation ${row.type}`,
title: 'Confirm'
})
.then(({ ok }) => ok && deletePlayerModeration(row))
.catch(() => {});
}

View File

@@ -85,7 +85,6 @@
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group';
import { Refresh } from '@element-plus/icons-vue';
import { Spinner } from '@/components/ui/spinner';
@@ -102,6 +101,7 @@
useGroupStore,
useInviteStore,
useLocationStore,
useModalStore,
useNotificationStore,
useUserStore,
useVrcxStore
@@ -129,6 +129,7 @@
const { currentUser } = storeToRefs(useUserStore());
const appearanceSettingsStore = useAppearanceSettingsStore();
const vrcxStore = useVrcxStore();
const modalStore = useModalStore();
const { t } = useI18n();
@@ -321,17 +322,18 @@
function acceptFriendRequestNotification(row) {
// FIXME: 메시지 수정
ElMessageBox.confirm('Continue? Accept Friend Request', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
notificationRequest.acceptFriendRequestNotification({
notificationId: row.id
});
modalStore
.confirm({
description: 'Continue? Accept Friend Request',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) {
return;
}
notificationRequest.acceptFriendRequestNotification({
notificationId: row.id
});
})
.catch(() => {});
}
@@ -345,46 +347,47 @@
}
function acceptRequestInvite(row) {
ElMessageBox.confirm('Continue? Send Invite', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
let currentLocation = lastLocation.value.location;
if (lastLocation.value.location === 'traveling') {
currentLocation = lastLocationDestination.value;
}
if (!currentLocation) {
// game log disabled, use API location
currentLocation = currentUser.$locationTag;
}
const L = parseLocation(currentLocation);
worldRequest
.getCachedWorld({
worldId: L.worldId
})
.then((args) => {
notificationRequest
.sendInvite(
{
instanceId: L.tag,
worldId: L.tag,
worldName: args.ref.name,
rsvp: true
},
row.senderUserId
)
.then((_args) => {
toast('Invite sent');
notificationRequest.hideNotification({
notificationId: row.id
});
return _args;
});
});
modalStore
.confirm({
description: 'Continue? Send Invite',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) {
return;
}
let currentLocation = lastLocation.value.location;
if (lastLocation.value.location === 'traveling') {
currentLocation = lastLocationDestination.value;
}
if (!currentLocation) {
// game log disabled, use API location
currentLocation = currentUser.$locationTag;
}
const L = parseLocation(currentLocation);
worldRequest
.getCachedWorld({
worldId: L.worldId
})
.then((args) => {
notificationRequest
.sendInvite(
{
instanceId: L.tag,
worldId: L.tag,
worldName: args.ref.name,
rsvp: true
},
row.senderUserId
)
.then((_args) => {
toast('Invite sent');
notificationRequest.hideNotification({
notificationId: row.id
});
return _args;
});
});
})
.catch(() => {});
}
@@ -454,13 +457,13 @@
}
function hideNotificationPrompt(row) {
ElMessageBox.confirm(`Continue? Decline ${row.type}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
modalStore
.confirm({
description: `Continue? Decline ${row.type}`,
title: 'Confirm'
})
.then(({ ok }) => {
if (ok) {
hideNotification(row);
}
})
@@ -478,13 +481,13 @@
}
function deleteNotificationLogPrompt(row) {
ElMessageBox.confirm(`Continue? Delete ${row.type}`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
modalStore
.confirm({
description: `Continue? Delete ${row.type}`,
title: 'Confirm'
})
.then(({ ok }) => {
if (ok) {
deleteNotificationLog(row);
}
})

View File

@@ -55,8 +55,8 @@
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { useAdvancedSettingsStore, useModalStore, useVrcxStore } from '../../../stores';
import { downloadAndSaveJson, removeFromArray } from '../../../shared/utils';
import { useAdvancedSettingsStore, useVrcxStore } from '../../../stores';
import { Switch } from '../../../components/ui/switch';
import { createColumns } from './registryBackupColumns.jsx';
import { useVrcxVueTable } from '../../../lib/table/useVrcxVueTable';
@@ -67,6 +67,7 @@
const { isRegistryBackupDialogVisible } = storeToRefs(useVrcxStore());
const { vrcRegistryAutoBackup, vrcRegistryAskRestore } = storeToRefs(useAdvancedSettingsStore());
const { setVrcRegistryAutoBackup, setVrcRegistryAskRestore } = useAdvancedSettingsStore();
const modalStore = useModalStore();
const { t } = useI18n();
@@ -121,13 +122,13 @@
}
function restoreVrcRegistryBackup(row) {
ElMessageBox.confirm('Continue? Restore Backup', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning'
})
.then((action) => {
if (action !== 'confirm') {
modalStore
.confirm({
description: 'Continue? Restore Backup',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) {
return;
}
const data = JSON.stringify(row.data);
@@ -155,13 +156,13 @@
}
function deleteVrcRegistry() {
ElMessageBox.confirm('Continue? Delete VRC Registry Settings', 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'warning'
})
.then((action) => {
if (action !== 'confirm') {
modalStore
.confirm({
description: 'Continue? Delete VRC Registry Settings',
title: 'Confirm'
})
.then(({ ok }) => {
if (!ok) {
return;
}
AppApi.DeleteVRChatRegistryFolder().then(() => {
@@ -178,8 +179,6 @@
function promptVrcRegistryBackupName() {
ElMessageBox.prompt('Enter a name for the backup', 'Backup Name', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
inputPattern: /\S+/,
inputErrorMessage: 'Name is required',
inputValue: 'Backup'

View File

@@ -173,23 +173,23 @@
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { computed, ref, watch } from 'vue';
import { Button } from '@/components/ui/button';
import { InputGroupAction } from '@/components/ui/input-group';
import { Checkbox } from '@/components/ui/checkbox';
import { ElMessageBox } from 'element-plus';
import { InputGroupAction } from '@/components/ui/input-group';
import { Refresh } from '@element-plus/icons-vue';
import { Spinner } from '@/components/ui/spinner';
import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { useAdvancedSettingsStore, useGameStore, useModalStore } from '../../../stores';
import { VRChatCameraResolutions, VRChatScreenshotResolutions } from '../../../shared/constants';
import { getVRChatResolution, openExternalLink } from '../../../shared/utils';
import { useAdvancedSettingsStore, useGameStore } from '../../../stores';
const { VRChatUsedCacheSize, VRChatTotalCacheSize, VRChatCacheSizeLoading } = storeToRefs(useGameStore());
const { sweepVRChatCache, getVRChatCacheSize } = useGameStore();
const { folderSelectorDialog } = useAdvancedSettingsStore();
const { isVRChatConfigDialogVisible } = storeToRefs(useAdvancedSettingsStore());
const modalStore = useModalStore();
const { t } = useI18n();
@@ -322,13 +322,13 @@
});
function showDeleteAllVRChatCacheConfirm() {
ElMessageBox.confirm(`Continue? Delete all VRChat cache`, 'Confirm', {
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
type: 'info'
})
.then((action) => {
if (action === 'confirm') {
modalStore
.confirm({
description: 'Continue? Delete all VRChat cache',
title: 'Confirm'
})
.then(({ ok }) => {
if (ok) {
deleteAllVRChatCache();
}
})

View File

@@ -91,7 +91,7 @@
<div v-for="friendArr in friendsInSameInstance" :key="friendArr[0].ref.$location.tag">
<div class="mb-1 flex items-center">
<Location
class="extra text-neutral-300!"
class="extra text-muted-foreground!"
:location="getFriendsLocations(friendArr)"
style="display: inline" />
<span class="extra" style="margin-left: 5px">{{ `(${friendArr.length})` }}</span>