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", "@fontsource-variable/noto-sans-tc": "^5.2.9",
"@kamiya4047/eslint-plugin-pretty-import": "^0.1.6", "@kamiya4047/eslint-plugin-pretty-import": "^0.1.6",
"@sentry/vite-plugin": "^4.6.1", "@sentry/vite-plugin": "^4.6.1",
"@sentry/vue": "^10.32.1", "@sentry/vue": "^10.33.0",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"@tanstack/vue-table": "^8.21.3", "@tanstack/vue-table": "^8.21.3",
"@tanstack/vue-virtual": "^3.13.18", "@tanstack/vue-virtual": "^3.13.18",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^25.0.6", "@types/node": "^25.0.7",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vitejs/plugin-vue": "^6.0.3", "@vitejs/plugin-vue": "^6.0.3",
"@vitejs/plugin-vue-jsx": "^5.1.3", "@vitejs/plugin-vue-jsx": "^5.1.3",
@@ -4372,54 +4372,54 @@
] ]
}, },
"node_modules/@sentry-internal/browser-utils": { "node_modules/@sentry-internal/browser-utils": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.33.0.tgz",
"integrity": "sha512-sjLLep1es3rTkbtAdTtdpc/a6g7v7bK5YJiZJsUigoJ4NTiFeMI5uIDCxbH/tjJ1q23YE1LzVn7T96I+qBRjHA==", "integrity": "sha512-nDJFHAfiFifBfJB0OF6DV6BIsIV5uah4lDsV4UBAgPBf+YAHclO10y1gi2U/JMh58c+s4lXi9p+PI1TFXZ0c6w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.32.1" "@sentry/core": "10.33.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/feedback": { "node_modules/@sentry-internal/feedback": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.33.0.tgz",
"integrity": "sha512-O24G8jxbfBY1RE/v2qFikPJISVMOrd/zk8FKyl+oUVYdOxU2Ucjk2cR3EQruBFlc7irnL6rT3GPfRZ/kBgLkmQ==", "integrity": "sha512-sN/VLWtEf0BeV6w6wldIpTxUQxNVc9o9tjLRQa8je1ZV2FCgXA124Iff/zsowsz82dLqtg7qp6GA5zYXVq+JMA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.32.1" "@sentry/core": "10.33.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay": { "node_modules/@sentry-internal/replay": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.33.0.tgz",
"integrity": "sha512-KKmLUgIaLRM0VjrMA1ByQTawZyRDYSkG2evvEOVpEtR9F0sumidAQdi7UY71QEKE1RYe/Jcp/3WoaqsMh8tbnQ==", "integrity": "sha512-UOU9PYxuXnPop3HoQ3l4Q7SZUXJC3Vmfm0Adgad8U03UcrThWIHYc5CxECSrVzfDFNOT7w9o7HQgRAgWxBPMXg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "10.32.1", "@sentry-internal/browser-utils": "10.33.0",
"@sentry/core": "10.32.1" "@sentry/core": "10.33.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay-canvas": { "node_modules/@sentry-internal/replay-canvas": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.33.0.tgz",
"integrity": "sha512-/XGTzWNWVc+B691fIVekV2KeoHFEDA5KftrLFAhEAW7uWOwk/xy3aQX4TYM0LcPm2PBKvoumlAD+Sd/aXk63oA==", "integrity": "sha512-MTmP6uoAVzw4CCPeqCgCLsRSiOfGLxgyMFjGTCW3E7t62MJ9S0H5sLsQ34sHxXUa1gFU9UNAjEvRRpZ0JvWrPw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/replay": "10.32.1", "@sentry-internal/replay": "10.33.0",
"@sentry/core": "10.32.1" "@sentry/core": "10.33.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -4436,17 +4436,17 @@
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.33.0.tgz",
"integrity": "sha512-NPNCXTZ05ZGTFyJdKNqjykpFm+urem0ebosILQiw3C4BxNVNGH4vfYZexyl6prRhmg91oB6GjVNiVDuJiap1gg==", "integrity": "sha512-iWiPjik9zetM84jKfk01UveW1J0+X7w8XmJ8+IrhTyNDBVUWCRJWD8FrksiN1dRSg5mFWgfMRzKMz27hAScRwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "10.32.1", "@sentry-internal/browser-utils": "10.33.0",
"@sentry-internal/feedback": "10.32.1", "@sentry-internal/feedback": "10.33.0",
"@sentry-internal/replay": "10.32.1", "@sentry-internal/replay": "10.33.0",
"@sentry-internal/replay-canvas": "10.32.1", "@sentry-internal/replay-canvas": "10.33.0",
"@sentry/core": "10.32.1" "@sentry/core": "10.33.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -4647,9 +4647,9 @@
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.33.0.tgz",
"integrity": "sha512-PH2ldpSJlhqsMj2vCTyU0BI2Fx1oIDhm7Izo5xFALvjVCS0gmlqHt1udu6YlKn8BtpGH6bGzssvv5APrk+OdPQ==", "integrity": "sha512-ehH1VSUclIHZKEZVdv+klofsFIh8FFzqA6AAV23RtLepptzA8wqQzUGraEuSN25sYcNmYJ0jti5U0Ys+WZv5Dw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4671,14 +4671,14 @@
} }
}, },
"node_modules/@sentry/vue": { "node_modules/@sentry/vue": {
"version": "10.32.1", "version": "10.33.0",
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.32.1.tgz", "resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.33.0.tgz",
"integrity": "sha512-3KVvjkBw18FgbYar87CevQNPRATtBrzi+xIRZf6uJG2Wnd9w+WH3+CQsjKwDvQiYyChiW4CYuFL2DuQ/VqOxfQ==", "integrity": "sha512-CUtoBl62DG8mkoYfgpkw2WdB187XA2CfPj7OJdIzt3lavhpSAPmsY4jUarK2RUJvcowr5zYbEfv50Y0tsQxuGA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/browser": "10.32.1", "@sentry/browser": "10.33.0",
"@sentry/core": "10.32.1" "@sentry/core": "10.33.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -5313,9 +5313,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "25.0.6", "version": "25.0.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.7.tgz",
"integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==", "integrity": "sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -42,12 +42,12 @@
"@fontsource-variable/noto-sans-tc": "^5.2.9", "@fontsource-variable/noto-sans-tc": "^5.2.9",
"@kamiya4047/eslint-plugin-pretty-import": "^0.1.6", "@kamiya4047/eslint-plugin-pretty-import": "^0.1.6",
"@sentry/vite-plugin": "^4.6.1", "@sentry/vite-plugin": "^4.6.1",
"@sentry/vue": "^10.32.1", "@sentry/vue": "^10.33.0",
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
"@tanstack/vue-table": "^8.21.3", "@tanstack/vue-table": "^8.21.3",
"@tanstack/vue-virtual": "^3.13.18", "@tanstack/vue-virtual": "^3.13.18",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^25.0.6", "@types/node": "^25.0.7",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vitejs/plugin-vue": "^6.0.3", "@vitejs/plugin-vue": "^6.0.3",
"@vitejs/plugin-vue-jsx": "^5.1.3", "@vitejs/plugin-vue-jsx": "^5.1.3",

View File

@@ -14,6 +14,8 @@
<RouterView></RouterView> <RouterView></RouterView>
<Toaster position="top-center"></Toaster> <Toaster position="top-center"></Toaster>
<AlertDialogModal></AlertDialogModal>
<VRCXUpdateDialog></VRCXUpdateDialog> <VRCXUpdateDialog></VRCXUpdateDialog>
</div> </div>
</el-config-provider> </el-config-provider>
@@ -29,6 +31,7 @@
import { createGlobalStores } from './stores'; import { createGlobalStores } from './stores';
import { initNoty } from './plugin/noty'; import { initNoty } from './plugin/noty';
import AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue';
import MacOSTitleBar from './components/MacOSTitleBar.vue'; import MacOSTitleBar from './components/MacOSTitleBar.vue';
import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue'; import VRCXUpdateDialog from './components/dialogs/VRCXUpdateDialog.vue';

View File

@@ -67,11 +67,10 @@
import { reactive, watch } from 'vue'; import { reactive, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { CaretBottom } from '@element-plus/icons-vue'; import { CaretBottom } from '@element-plus/icons-vue';
import { ElMessageBox } from 'element-plus';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; 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 { formatDateFilter, hasGroupPermission } from '../shared/utils';
import { miscRequest } from '../api'; import { miscRequest } from '../api';
@@ -81,6 +80,7 @@
const userStore = useUserStore(); const userStore = useUserStore();
const groupStore = useGroupStore(); const groupStore = useGroupStore();
const instanceStore = useInstanceStore(); const instanceStore = useInstanceStore();
const modalStore = useModalStore();
const props = defineProps({ const props = defineProps({
location: String, location: String,
@@ -126,13 +126,13 @@
} }
function closeInstance(location) { function closeInstance(location) {
ElMessageBox.confirm('Continue? Close Instance, nobody will be able to join', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Close Instance, nobody will be able to join',
type: 'warning' title: 'Confirm'
}) })
.then(async (action) => { .then(async ({ ok }) => {
if (action !== 'confirm') return; if (!ok) return;
const args = await miscRequest.closeInstance({ location, hardClose: false }); const args = await miscRequest.closeInstance({ location, hardClose: false });
if (args.json) { if (args.json) {
toast.success(t('message.instance.closed')); toast.success(t('message.instance.closed'));

View File

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

View File

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

View File

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

View File

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

View File

@@ -79,14 +79,13 @@
import { computed, nextTick, ref, watch } from 'vue'; import { computed, nextTick, ref, watch } from 'vue';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Check as CheckIcon } from 'lucide-vue-next'; import { Check as CheckIcon } from 'lucide-vue-next';
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { hasGroupPermission, userImage, userStatusClass } from '../../shared/utils'; import { hasGroupPermission, userImage, userStatusClass } from '../../shared/utils';
import { useFriendStore, useGroupStore, useModalStore } from '../../stores';
import { groupRequest, userRequest } from '../../api'; import { groupRequest, userRequest } from '../../api';
import { useFriendStore, useGroupStore } from '../../stores';
import { VirtualCombobox } from '../ui/virtual-combobox'; import { VirtualCombobox } from '../ui/virtual-combobox';
import { getNextDialogIndex } from '../../shared/utils/base/ui'; import { getNextDialogIndex } from '../../shared/utils/base/ui';
@@ -96,6 +95,7 @@
const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore()); const { currentUserGroups, inviteGroupDialog } = storeToRefs(useGroupStore());
const { applyGroup } = useGroupStore(); const { applyGroup } = useGroupStore();
const { t } = useI18n(); const { t } = useI18n();
const modalStore = useModalStore();
watch( watch(
() => inviteGroupDialog.value.visible, () => inviteGroupDialog.value.visible,
@@ -272,14 +272,15 @@
}); });
} }
function sendGroupInvite() { function sendGroupInvite() {
ElMessageBox.confirm('Continue? Invite User(s) To Group', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Invite User(s) To Group',
type: 'info' title: 'Confirm'
}) })
.then((action) => { .then(({ ok }) => {
if (!ok) return;
const D = inviteGroupDialog.value; const D = inviteGroupDialog.value;
if (action !== 'confirm' || D.loading === true) { if (D.loading === true) {
return; return;
} }
D.loading = true; D.loading = true;

View File

@@ -135,7 +135,6 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ButtonGroup } from '@/components/ui/button-group'; import { ButtonGroup } from '@/components/ui/button-group';
import { Copy } from 'lucide-vue-next'; import { Copy } from 'lucide-vue-next';
import { ElMessageBox } from 'element-plus';
import { InputGroupField } from '@/components/ui/input-group'; import { InputGroupField } from '@/components/ui/input-group';
import { MoreHorizontal } from 'lucide-vue-next'; import { MoreHorizontal } from 'lucide-vue-next';
import { Warning } from '@element-plus/icons-vue'; import { Warning } from '@element-plus/icons-vue';
@@ -143,7 +142,14 @@
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; 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 { checkCanInvite, getLaunchURL, isRealInstance, parseLocation } from '../../shared/utils';
import { instanceRequest, worldRequest } from '../../api'; import { instanceRequest, worldRequest } from '../../api';
import { getNextDialogIndex } from '../../shared/utils/base/ui'; import { getNextDialogIndex } from '../../shared/utils/base/ui';
@@ -153,6 +159,8 @@
const { t } = useI18n(); const { t } = useI18n();
const modalStore = useModalStore();
const { friends } = storeToRefs(useFriendStore()); const { friends } = storeToRefs(useFriendStore());
const { lastLocation } = storeToRefs(useLocationStore()); const { lastLocation } = storeToRefs(useLocationStore());
const { launchGame, tryOpenInstanceInVrc } = useLaunchStore(); const { launchGame, tryOpenInstanceInVrc } = useLaunchStore();
@@ -245,16 +253,17 @@
} }
function handleLaunchGame(location, shortName, desktop) { function handleLaunchGame(location, shortName, desktop) {
if (isGameRunning.value) { if (isGameRunning.value) {
ElMessageBox.confirm(t('dialog.launch.game_running_warning'), t('dialog.launch.header'), { modalStore
confirmButtonText: t('dialog.launch.confirm_yes'), .confirm({
cancelButtonText: t('dialog.launch.confirm_no'), description: t('dialog.launch.game_running_warning'),
type: 'warning' title: t('dialog.launch.header'),
}) confirmText: t('dialog.launch.confirm_yes'),
.then((action) => { cancelText: t('dialog.launch.confirm_no')
if (action === 'confirm') { })
launchGame(location, shortName, desktop); .then(({ ok }) => {
isVisible.value = false; if (!ok) return;
} launchGame(location, shortName, desktop);
isVisible.value = false;
}) })
.catch(() => {}); .catch(() => {});
return; return;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import { ElMessageBox } from 'element-plus';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import Noty from 'noty'; import Noty from 'noty';
@@ -6,6 +5,7 @@ import Noty from 'noty';
import { import {
useAuthStore, useAuthStore,
useAvatarStore, useAvatarStore,
useModalStore,
useNotificationStore, useNotificationStore,
useUpdateLoopStore, useUpdateLoopStore,
useUserStore useUserStore
@@ -33,6 +33,7 @@ export function request(endpoint, options) {
const userStore = useUserStore(); const userStore = useUserStore();
const avatarStore = useAvatarStore(); const avatarStore = useAvatarStore();
const authStore = useAuthStore(); const authStore = useAuthStore();
const modalStore = useModalStore();
const notificationStore = useNotificationStore(); const notificationStore = useNotificationStore();
const updateLoopStore = useUpdateLoopStore(); const updateLoopStore = useUpdateLoopStore();
if ( if (
@@ -187,10 +188,10 @@ export function request(endpoint, options) {
} }
} }
if (status === 403 && endpoint === 'config') { if (status === 403 && endpoint === 'config') {
ElMessageBox.alert( modalStore.alert({
t('api.error.message.vpn_in_use'), description: t('api.error.message.vpn_in_use'),
`403 ${t('api.error.message.login_error')}` title: `403 ${t('api.error.message.login_error')}`
).catch(() => {}); });
authStore.handleLogoutEvent(); authStore.handleLogoutEvent();
$throw(403, endpoint); $throw(403, endpoint);
} }

View File

@@ -1,22 +1,20 @@
import { ElMessageBox } from 'element-plus';
import { openExternalLink } from '../shared/utils'; import { openExternalLink } from '../shared/utils';
import { useModalStore } from '../stores';
// requires binding of SQLite // requires binding of SQLite
class SQLiteService { class SQLiteService {
handleSQLiteError(e) { handleSQLiteError(e) {
if (typeof e.message === 'string') { if (typeof e.message === 'string') {
const modalStore = useModalStore();
if (e.message.includes('database disk image is malformed')) { if (e.message.includes('database disk image is malformed')) {
ElMessageBox.confirm( modalStore
'Please repair or delete your database file by following these instructions.', .confirm({
'Your database is corrupted', description:
{ 'Please repair or delete your database file by following these instructions.',
confirmButtonText: 'Confirm', title: 'Your database is corrupted'
type: 'warning' })
} .then(({ ok }) => {
) if (!ok) return;
.then(async (action) => {
if (action !== 'confirm') return;
openExternalLink( openExternalLink(
'https://github.com/vrcx-team/VRCX/wiki#how-to-repair-vrcx-database' 'https://github.com/vrcx-team/VRCX/wiki#how-to-repair-vrcx-database'
); );
@@ -24,37 +22,26 @@ class SQLiteService {
.catch(() => {}); .catch(() => {});
} }
if (e.message.includes('database or disk is full')) { if (e.message.includes('database or disk is full')) {
ElMessageBox.alert( modalStore.alert({
'Please free up some disk space.', description: 'Please free up some disk space.',
'Disk containing database is full', title: 'Disk containing database is full'
{ });
confirmButtonText: 'OK',
type: 'warning'
}
).catch(() => {});
} }
if ( if (
e.message.includes('database is locked') || e.message.includes('database is locked') ||
e.message.includes('attempt to write a readonly database') e.message.includes('attempt to write a readonly database')
) { ) {
ElMessageBox.alert( modalStore.alert({
'Please close other applications that might be using the database file.', description:
'Database is locked', 'Please close other applications that might be using the database file.',
{ title: 'Database is locked'
confirmButtonText: 'OK', });
type: 'warning'
}
).catch(() => {});
} }
if (e.message.includes('disk I/O error')) { if (e.message.includes('disk I/O error')) {
ElMessageBox.alert( modalStore.alert({
'Please check your disk for errors.', description: 'Please check your disk for errors.',
'Disk I/O error', title: 'Disk I/O error'
{ });
confirmButtonText: 'OK',
type: 'warning'
}
).catch(() => {});
} }
} }
throw e; throw e;

View File

@@ -1,4 +1,3 @@
import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -7,6 +6,7 @@ import Noty from 'noty';
import { import {
useAvatarStore, useAvatarStore,
useInstanceStore, useInstanceStore,
useModalStore,
useSearchStore, useSearchStore,
useWorldStore useWorldStore
} from '../../stores'; } from '../../stores';
@@ -395,24 +395,22 @@ function openExternalLink(link) {
return; return;
} }
ElMessageBox.confirm(`${link}`, 'Open External Link', { const modalStore = useModalStore();
distinguishCancelAndClose: true, modalStore
confirmButtonText: 'Open', .confirm({
cancelButtonText: 'Copy', description: `${link}`,
type: 'info', title: 'Open External Link',
beforeClose: (action, instance, done) => { confirmText: 'Open',
if (action === 'cancel') { cancelText: 'Copy'
copyToClipboard(link);
}
done();
}
})
.then((action) => {
if (action === 'confirm') {
AppApi.OpenLink(link);
}
}) })
.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 { escapeTag } from '../shared/utils';
import { request } from '../service/request'; import { request } from '../service/request';
import { useAdvancedSettingsStore } from './settings/advanced'; import { useAdvancedSettingsStore } from './settings/advanced';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { useUpdateLoopStore } from './updateLoop'; import { useUpdateLoopStore } from './updateLoop';
import { useUserStore } from './user'; import { useUserStore } from './user';
@@ -27,6 +28,7 @@ export const useAuthStore = defineStore('Auth', () => {
const notificationStore = useNotificationStore(); const notificationStore = useNotificationStore();
const userStore = useUserStore(); const userStore = useUserStore();
const updateLoopStore = useUpdateLoopStore(); const updateLoopStore = useUpdateLoopStore();
const modalStore = useModalStore();
const { t } = useI18n(); const { t } = useI18n();
const state = reactive({ const state = reactive({
@@ -405,21 +407,20 @@ export const useAuthStore = defineStore('Auth', () => {
} }
function logout() { function logout() {
ElMessageBox.confirm('Continue? Logout', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Logout',
type: 'info' title: 'Confirm'
}) })
.then((action) => { .then(({ ok }) => {
if (action === 'confirm') { if (!ok) return;
const existingStyle = document.getElementById( const existingStyle = document.getElementById(
'login-container-style' 'login-container-style'
); );
if (existingStyle) { if (existingStyle) {
existingStyle.parentNode.removeChild(existingStyle); existingStyle.parentNode.removeChild(existingStyle);
}
handleLogoutEvent();
} }
handleLogoutEvent();
}) })
.catch(() => {}); .catch(() => {});
} }

View File

@@ -1,5 +1,4 @@
import { nextTick, ref, watch } from 'vue'; import { nextTick, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -18,6 +17,7 @@ import { database } from '../service/database';
import { useAdvancedSettingsStore } from './settings/advanced'; import { useAdvancedSettingsStore } from './settings/advanced';
import { useAvatarProviderStore } from './avatarProvider'; import { useAvatarProviderStore } from './avatarProvider';
import { useFavoriteStore } from './favorite'; import { useFavoriteStore } from './favorite';
import { useModalStore } from './modal';
import { useUserStore } from './user'; import { useUserStore } from './user';
import { useVRCXUpdaterStore } from './vrcxUpdater'; import { useVRCXUpdaterStore } from './vrcxUpdater';
import { watchState } from '../service/watchState'; import { watchState } from '../service/watchState';
@@ -30,6 +30,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
const vrcxUpdaterStore = useVRCXUpdaterStore(); const vrcxUpdaterStore = useVRCXUpdaterStore();
const advancedSettingsStore = useAdvancedSettingsStore(); const advancedSettingsStore = useAdvancedSettingsStore();
const userStore = useUserStore(); const userStore = useUserStore();
const modalStore = useModalStore();
let cachedAvatarModerations = new Map(); let cachedAvatarModerations = new Map();
let cachedAvatars = new Map(); let cachedAvatars = new Map();
@@ -389,12 +390,13 @@ export const useAvatarStore = defineStore('Avatar', () => {
} }
function promptClearAvatarHistory() { function promptClearAvatarHistory() {
ElMessageBox.confirm('Continue? Clear Avatar History', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Clear Avatar History',
type: 'info' title: 'Confirm'
}) })
.then(() => { .then(({ ok }) => {
if (!ok) return;
clearAvatarHistory(); clearAvatarHistory();
}) })
.catch(() => {}); .catch(() => {});
@@ -552,12 +554,13 @@ export const useAvatarStore = defineStore('Avatar', () => {
} }
function selectAvatarWithConfirmation(id) { function selectAvatarWithConfirmation(id) {
ElMessageBox.confirm(`Continue? Select Avatar`, 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Select Avatar',
type: 'info' title: 'Confirm'
}) })
.then(() => { .then(({ ok }) => {
if (!ok) return;
selectAvatarWithoutConfirmation(id); selectAvatarWithoutConfirmation(id);
}) })
.catch(() => {}); .catch(() => {});

View File

@@ -1,5 +1,4 @@
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -27,6 +26,7 @@ import { useFeedStore } from './feed';
import { useGeneralSettingsStore } from './settings/general'; import { useGeneralSettingsStore } from './settings/general';
import { useGroupStore } from './group'; import { useGroupStore } from './group';
import { useLocationStore } from './location'; import { useLocationStore } from './location';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { useSharedFeedStore } from './sharedFeed'; import { useSharedFeedStore } from './sharedFeed';
import { useUiStore } from './ui'; import { useUiStore } from './ui';
@@ -51,6 +51,7 @@ export const useFriendStore = defineStore('Friend', () => {
const authStore = useAuthStore(); const authStore = useAuthStore();
const locationStore = useLocationStore(); const locationStore = useLocationStore();
const favoriteStore = useFavoriteStore(); const favoriteStore = useFavoriteStore();
const modalStore = useModalStore();
const { t } = useI18n(); const { t } = useI18n();
const state = reactive({ const state = reactive({
@@ -1578,12 +1579,13 @@ export const useFriendStore = defineStore('Friend', () => {
} }
function confirmDeleteFriend(id) { function confirmDeleteFriend(id) {
ElMessageBox.confirm('Continue? Unfriend', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Unfriend',
type: 'info' title: 'Confirm'
}) })
.then(async () => { .then(async ({ ok }) => {
if (!ok) return;
const args = await friendRequest.deleteFriend({ const args = await friendRequest.deleteFriend({
userId: id userId: id
}); });

View File

@@ -1,5 +1,4 @@
import { reactive, ref, shallowReactive, watch } from 'vue'; import { reactive, ref, shallowReactive, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -19,9 +18,10 @@ import {
} from '../api'; } from '../api';
import { AppDebug } from '../service/appConfig'; import { AppDebug } from '../service/appConfig';
import { handleImageUploadInput } from '../shared/utils/imageUpload'; import { handleImageUploadInput } from '../shared/utils/imageUpload';
import { useAdvancedSettingsStore } from './settings/advanced';
import { watchState } from '../service/watchState';
import { router } from '../plugin/router'; import { router } from '../plugin/router';
import { useAdvancedSettingsStore } from './settings/advanced';
import { useModalStore } from './modal';
import { watchState } from '../service/watchState';
import miscReq from '../api/misc'; import miscReq from '../api/misc';
@@ -30,6 +30,7 @@ import * as workerTimers from 'worker-timers';
export const useGalleryStore = defineStore('Gallery', () => { export const useGalleryStore = defineStore('Gallery', () => {
const advancedSettingsStore = useAdvancedSettingsStore(); const advancedSettingsStore = useAdvancedSettingsStore();
const { t } = useI18n(); const { t } = useI18n();
const modalStore = useModalStore();
const state = reactive({ const state = reactive({
printCache: [], printCache: [],
@@ -515,17 +516,15 @@ export const useGalleryStore = defineStore('Gallery', () => {
} }
} catch (e) { } catch (e) {
if (e.message.includes('Could not find file')) { if (e.message.includes('Could not find file')) {
ElMessageBox.confirm( modalStore
'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?', .confirm({
'Failed to create emoji folder', 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?',
confirmButtonText: 'Confirm', title: 'Failed to create emoji folder',
cancelButtonText: 'Ignore', cancelText: 'Ignore'
type: 'warning' })
} .then(({ ok }) => {
) if (!ok) return;
.then(async (action) => {
if (action !== 'confirm') return;
openExternalLink( openExternalLink(
'https://www.youtube.com/watch?v=1mwmmCdA4D8&t=213s' 'https://www.youtube.com/watch?v=1mwmmCdA4D8&t=213s'
); );

View File

@@ -1,5 +1,4 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -14,6 +13,7 @@ import { useGameLogStore } from './gameLog';
import { useInstanceStore } from './instance'; import { useInstanceStore } from './instance';
import { useLaunchStore } from './launch'; import { useLaunchStore } from './launch';
import { useLocationStore } from './location'; import { useLocationStore } from './location';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { useUpdateLoopStore } from './updateLoop'; import { useUpdateLoopStore } from './updateLoop';
import { useUserStore } from './user'; import { useUserStore } from './user';
@@ -36,6 +36,7 @@ export const useGameStore = defineStore('Game', () => {
const vrStore = useVrStore(); const vrStore = useVrStore();
const userStore = useUserStore(); const userStore = useUserStore();
const updateLoopStore = useUpdateLoopStore(); const updateLoopStore = useUpdateLoopStore();
const modalStore = useModalStore();
const state = reactive({ const state = reactive({
lastCrashedTime: null lastCrashedTime: null
@@ -218,17 +219,19 @@ export const useGameStore = defineStore('Game', () => {
); );
if (!result) { if (!result) {
// failed to set key // failed to set key
ElMessageBox.alert( modalStore.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.', description:
'Enable debug logging' '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.',
).catch(() => {}); title: 'Enable debug logging'
});
console.error('Failed to enable debug logging', result); console.error('Failed to enable debug logging', result);
return; return;
} }
ElMessageBox.alert( modalStore.alert({
'VRCX has noticed VRChat debug logging is disabled and automatically re-enabled it. VRCX requires debug logging in order to function correctly.', description:
'Enabled debug logging' 'VRCX has noticed VRChat debug logging is disabled and automatically re-enabled it. VRCX requires debug logging in order to function correctly.',
).catch(() => {}); title: 'Enabled debug logging'
});
console.log('Enabled debug logging'); console.log('Enabled debug logging');
} catch (e) { } catch (e) {
console.error(e); console.error(e);

View File

@@ -1,5 +1,4 @@
import { reactive, ref, shallowReactive, watch } from 'vue'; import { reactive, ref, shallowReactive, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -23,6 +22,7 @@ import { useGameStore } from './game';
import { useGeneralSettingsStore } from './settings/general'; import { useGeneralSettingsStore } from './settings/general';
import { useInstanceStore } from './instance'; import { useInstanceStore } from './instance';
import { useLocationStore } from './location'; import { useLocationStore } from './location';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { usePhotonStore } from './photon'; import { usePhotonStore } from './photon';
import { useSharedFeedStore } from './sharedFeed'; import { useSharedFeedStore } from './sharedFeed';
@@ -54,6 +54,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
const galleryStore = useGalleryStore(); const galleryStore = useGalleryStore();
const photonStore = usePhotonStore(); const photonStore = usePhotonStore();
const sharedFeedStore = useSharedFeedStore(); const sharedFeedStore = useSharedFeedStore();
const modalStore = useModalStore();
const state = reactive({ const state = reactive({
lastLocationAvatarList: new Map() lastLocationAvatarList: new Map()
@@ -1414,15 +1415,14 @@ export const useGameLogStore = defineStore('GameLog', () => {
return; return;
} }
if (!advancedSettingsStore.gameLogDisabled) { if (!advancedSettingsStore.gameLogDisabled) {
ElMessageBox.confirm('Continue? Disable GameLog', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Disable GameLog',
type: 'info' title: 'Confirm'
}) })
.then(({ action }) => { .then(({ ok }) => {
if (action === 'confirm') { if (!ok) return;
advancedSettingsStore.setGameLogDisabled(); advancedSettingsStore.setGameLogDisabled();
}
}) })
.catch(() => {}); .catch(() => {});
} else { } else {

View File

@@ -1,5 +1,4 @@
import { nextTick, reactive, ref, watch } from 'vue'; import { nextTick, reactive, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
@@ -18,6 +17,7 @@ import { database } from '../service/database.js';
import { groupDialogFilterOptions } from '../shared/constants/'; import { groupDialogFilterOptions } from '../shared/constants/';
import { useGameStore } from './game'; import { useGameStore } from './game';
import { useInstanceStore } from './instance'; import { useInstanceStore } from './instance';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { useUserStore } from './user'; import { useUserStore } from './user';
import { watchState } from '../service/watchState'; import { watchState } from '../service/watchState';
@@ -31,6 +31,7 @@ export const useGroupStore = defineStore('Group', () => {
const gameStore = useGameStore(); const gameStore = useGameStore();
const userStore = useUserStore(); const userStore = useUserStore();
const notificationStore = useNotificationStore(); const notificationStore = useNotificationStore();
const modalStore = useModalStore();
let cachedGroups = new Map(); let cachedGroups = new Map();
@@ -555,16 +556,13 @@ export const useGroupStore = defineStore('Group', () => {
} }
function leaveGroupPrompt(groupId) { function leaveGroupPrompt(groupId) {
ElMessageBox.confirm( modalStore
'Are you sure you want to leave this group?', .confirm({
'Confirm', description: 'Are you sure you want to leave this group?',
{ title: 'Confirm'
confirmButtonText: 'Confirm', })
cancelButtonText: 'Cancel', .then(({ ok }) => {
type: 'info' if (!ok) return;
}
)
.then(() => {
leaveGroup(groupId); leaveGroup(groupId);
}) })
.catch(() => {}); .catch(() => {});

View File

@@ -21,6 +21,7 @@ import { useInstanceStore } from './instance';
import { useInviteStore } from './invite'; import { useInviteStore } from './invite';
import { useLaunchStore } from './launch'; import { useLaunchStore } from './launch';
import { useLocationStore } from './location'; import { useLocationStore } from './location';
import { useModalStore } from './modal';
import { useModerationStore } from './moderation'; import { useModerationStore } from './moderation';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { useNotificationsSettingsStore } from './settings/notifications'; import { useNotificationsSettingsStore } from './settings/notifications';
@@ -162,7 +163,8 @@ export function createGlobalStores() {
updateLoop: useUpdateLoopStore(), updateLoop: useUpdateLoopStore(),
auth: useAuthStore(), auth: useAuthStore(),
vrcStatus: useVrcStatusStore(), vrcStatus: useVrcStatusStore(),
charts: useChartsStore() charts: useChartsStore(),
modal: useModalStore()
}; };
} }
@@ -200,5 +202,6 @@ export {
useWorldStore, useWorldStore,
useSharedFeedStore, useSharedFeedStore,
useUpdateLoopStore, 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 { database } from '../../service/database';
import { languageCodes } from '../../localization'; import { languageCodes } from '../../localization';
import { useGameStore } from '../game'; import { useGameStore } from '../game';
import { useModalStore } from '../modal';
import { useVRCXUpdaterStore } from '../vrcxUpdater'; import { useVRCXUpdaterStore } from '../vrcxUpdater';
import { useVrcxStore } from '../vrcx'; import { useVrcxStore } from '../vrcx';
import { watchState } from '../../service/watchState'; import { watchState } from '../../service/watchState';
@@ -19,6 +20,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
const gameStore = useGameStore(); const gameStore = useGameStore();
const vrcxStore = useVrcxStore(); const vrcxStore = useVrcxStore();
const VRCXUpdaterStore = useVRCXUpdaterStore(); const VRCXUpdaterStore = useVRCXUpdaterStore();
const modalStore = useModalStore();
const { t } = useI18n(); const { t } = useI18n();
@@ -466,68 +468,58 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
} }
async function checkSentryConsent() { async function checkSentryConsent() {
const { action: consentAction } = await ElMessageBox.confirm( modalStore
'Help improve VRCX by allowing anonymous error reporting?</br></br>' + .confirm({
'• Only collects crash and error information.</br>' + description:
'• No personal data or VRChat information is collected.</br>' + 'Help improve VRCX by allowing anonymous error reporting?</br></br>' +
'• Only enabled in nightly builds.</br>' + '• Only collects crash and error information.</br>' +
'• Can be disabled at anytime in Advanced Settings.', '• No personal data or VRChat information is collected.</br>' +
'Anonymous Error Reporting', '• Only enabled in nightly builds.</br>' +
{ '• Can be disabled at anytime in Advanced Settings.',
type: 'warning', title: 'Anonymous Error Reporting'
center: true, })
dangerouslyUseHTMLString: true, .then(async ({ ok }) => {
closeOnClickModal: false, if (!ok) return;
closeOnPressEscape: false, modalStore
distinguishCancelAndClose: true .confirm({
} description:
).catch(() => ({ action: 'cancel' })); '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( VRCXUpdaterStore.restartVRCX(false);
'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);
} }
async function setSentryErrorReporting() { async function setSentryErrorReporting() {
if (VRCXUpdaterStore.branch !== 'Nightly') return; if (VRCXUpdaterStore.branch !== 'Nightly') return;
const { action: restartAction } = await ElMessageBox.confirm( modalStore
'Error reporting setting has been disabled. Would you like to restart VRCX now for the change to take effect?', .confirm({
'Restart Required', description:
{ 'Error reporting setting has been disabled. Would you like to restart VRCX now for the change to take effect?',
confirmButtonText: 'Restart Now', title: 'Restart Required',
cancelButtonText: 'Later', confirmText: 'Restart Now',
type: 'info', cancelText: 'Later'
center: true })
} .then(async ({ ok }) => {
).catch(() => ({ action: 'cancel' })); if (!ok) return;
if (restartAction === 'cancel') return; sentryErrorReporting.value = !sentryErrorReporting.value;
await configRepository.setBool(
sentryErrorReporting.value = !sentryErrorReporting.value; 'VRCX_SentryEnabled',
await configRepository.setBool( sentryErrorReporting.value
'VRCX_SentryEnabled', );
sentryErrorReporting.value VRCXUpdaterStore.restartVRCX(false);
); });
VRCXUpdaterStore.restartVRCX(false);
} }
async function getSqliteTableSizes() { async function getSqliteTableSizes() {
@@ -735,96 +727,87 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
function cropPrintsChanged() { function cropPrintsChanged() {
if (!cropInstancePrints.value) return; if (!cropInstancePrints.value) return;
ElMessageBox.confirm( modalStore
t( .confirm({
'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old' description: t(
), 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old'
{ ),
confirmButtonText: t( title: '',
confirmText: t(
'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_confirm' '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' 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_cancel'
), )
type: 'info', })
showInput: false .then(async ({ ok }) => {
} if (!ok) return;
) const msgBox = toast.warning(
.then(async ({ action }) => { 'Batch print cropping in progress...',
if (action === 'confirm') { { duration: Infinity, position: 'bottom-right' }
const msgBox = toast.warning( );
'Batch print cropping in progress...', try {
{ duration: Infinity, position: 'bottom-right' } await AppApi.CropAllPrints(ugcFolderPath.value);
); toast.success('Batch print cropping complete');
try { } catch (err) {
await AppApi.CropAllPrints(ugcFolderPath.value); console.error(err);
toast.success('Batch print cropping complete'); toast.error(`Batch print cropping failed: ${err}`);
} catch (err) { } finally {
console.error(err); toast.dismiss(msgBox);
toast.error(`Batch print cropping failed: ${err}`);
} finally {
toast.dismiss(msgBox);
}
} }
}) })
.catch(() => {}); .catch(() => {});
} }
function askDeleteAllScreenshotMetadata() { function askDeleteAllScreenshotMetadata() {
ElMessageBox.confirm( modalStore
t( .confirm({
'view.settings.advanced.advanced.delete_all_screenshot_metadata.ask' description: t(
), 'view.settings.advanced.advanced.delete_all_screenshot_metadata.ask'
{ ),
confirmButtonText: t( title: '',
confirmText: t(
'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm_yes' 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm_yes'
), ),
cancelButtonText: t( cancelText: t(
'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm_no' 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm_no'
), )
type: 'warning', })
showInput: false .then(({ ok }) => {
} if (!ok) return;
) deleteAllScreenshotMetadata();
.then(({ action }) => {
if (action === 'confirm') {
deleteAllScreenshotMetadata();
}
}) })
.catch(() => {}); .catch(() => {});
} }
function deleteAllScreenshotMetadata() { function deleteAllScreenshotMetadata() {
ElMessageBox.confirm( modalStore
t( .confirm({
'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm' description: t(
), 'view.settings.advanced.advanced.delete_all_screenshot_metadata.confirm'
{ ),
confirmButtonText: t( title: '',
confirmText: t(
'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_confirm' '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' 'view.settings.advanced.advanced.save_instance_prints_to_file.crop_convert_old_cancel'
), )
type: 'warning', })
showInput: false .then(async ({ ok }) => {
} if (!ok) return;
) const msgBox = toast.warning(
.then(async ({ action }) => { 'Batch metadata removal in progress...',
if (action === 'confirm') { { duration: Infinity, position: 'bottom-right' }
const msgBox = toast.warning( );
'Batch metadata removal in progress...', try {
{ duration: Infinity, position: 'bottom-right' } await AppApi.DeleteAllScreenshotMetadata();
); toast.success('Batch metadata removal complete');
try { } catch (err) {
await AppApi.DeleteAllScreenshotMetadata(); console.error(err);
toast.success('Batch metadata removal complete'); toast.error(`Batch metadata removal failed: ${err}`);
} catch (err) { } finally {
console.error(err); toast.dismiss(msgBox);
toast.error(`Batch metadata removal failed: ${err}`);
} finally {
toast.dismiss(msgBox);
}
} }
}) })
.catch(() => {}); .catch(() => {});

View File

@@ -1,5 +1,4 @@
import { reactive, ref, watch } from 'vue'; import { reactive, ref, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@@ -26,6 +25,7 @@ import { useGameStore } from './game';
import { useGroupStore } from './group'; import { useGroupStore } from './group';
import { useInstanceStore } from './instance'; import { useInstanceStore } from './instance';
import { useLocationStore } from './location'; import { useLocationStore } from './location';
import { useModalStore } from './modal';
import { useNotificationStore } from './notification'; import { useNotificationStore } from './notification';
import { usePhotonStore } from './photon'; import { usePhotonStore } from './photon';
import { useSearchStore } from './search'; import { useSearchStore } from './search';
@@ -58,6 +58,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
const vrcStatusStore = useVrcStatusStore(); const vrcStatusStore = useVrcStatusStore();
const galleryStore = useGalleryStore(); const galleryStore = useGalleryStore();
const { t } = useI18n(); const { t } = useI18n();
const modalStore = useModalStore();
const state = reactive({ const state = reactive({
databaseVersion: 0, databaseVersion: 0,
@@ -711,10 +712,10 @@ export const useVrcxStore = defineStore('Vrcx', () => {
return; return;
} }
// popup message about auto restore // popup message about auto restore
ElMessageBox.alert( modalStore.alert({
t('dialog.registry_backup.restore_prompt'), description: t('dialog.registry_backup.restore_prompt'),
t('dialog.registry_backup.header') title: t('dialog.registry_backup.header')
).catch(() => {}); });
showRegistryBackupDialog(); showRegistryBackupDialog();
await AppApi.FocusWindow(); await AppApi.FocusWindow();
await configRepository.setString( await configRepository.setString(

View File

@@ -142,16 +142,21 @@
import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from '@/components/ui/field'; import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel } from '@/components/ui/field';
import { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field'; import { NumberField, NumberFieldContent, NumberFieldInput } from '@/components/ui/number-field';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { ElMessageBox } from 'element-plus';
import { Settings } from 'lucide-vue-next';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Settings } from 'lucide-vue-next';
import { Spinner } from '@/components/ui/spinner'; import { Spinner } from '@/components/ui/spinner';
import { onBeforeRouteLeave } from 'vue-router'; import { onBeforeRouteLeave } from 'vue-router';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { toast } from 'vue-sonner'; import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n'; 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 { applyForceOverrides, computeForceOptions, useMutualGraphChart } from '../composables/useMutualGraphChart';
import { createRateLimiter, executeWithBackoff } from '../../../shared/utils'; import { createRateLimiter, executeWithBackoff } from '../../../shared/utils';
import { database } from '../../../service/database'; import { database } from '../../../service/database';
@@ -164,6 +169,7 @@
const { t } = useI18n(); const { t } = useI18n();
const friendStore = useFriendStore(); const friendStore = useFriendStore();
const userStore = useUserStore(); const userStore = useUserStore();
const modalStore = useModalStore();
const chartsStore = useChartsStore(); const chartsStore = useChartsStore();
const appearanceStore = useAppearanceSettingsStore(); const appearanceStore = useAppearanceSettingsStore();
const { friends } = storeToRefs(friendStore); const { friends } = storeToRefs(friendStore);
@@ -396,39 +402,35 @@
if (isFetching.value || hasFetched.value || !totalFriends.value) { if (isFetching.value || hasFetched.value || !totalFriends.value) {
return; return;
} }
try {
await ElMessageBox.confirm( modalStore
t('view.charts.mutual_friend.prompt.message'), .confirm({
t('view.charts.mutual_friend.prompt.title'), description: t('view.charts.mutual_friend.prompt.message'),
{ title: t('view.charts.mutual_friend.prompt.title'),
confirmButtonText: t('view.charts.mutual_friend.prompt.confirm'), confirmText: t('view.charts.mutual_friend.prompt.confirm'),
cancelButtonText: t('view.charts.mutual_friend.prompt.cancel'), cancelText: t('view.charts.mutual_friend.prompt.cancel')
type: 'warning' })
} .then(async ({ ok }) => {
); if (!ok) return;
await startFetch();
} catch { await startFetch();
// cancelled });
}
} }
function promptEnableMutualFriendsSharing() { function promptEnableMutualFriendsSharing() {
ElMessageBox.confirm( modalStore
t('view.charts.mutual_friend.enable_sharing_prompt.message'), .confirm({
t('view.charts.mutual_friend.enable_sharing_prompt.title'), description: t('view.charts.mutual_friend.enable_sharing_prompt.message'),
{ title: t('view.charts.mutual_friend.enable_sharing_prompt.title'),
confirmButtonText: t('view.charts.mutual_friend.enable_sharing_prompt.confirm'), confirmText: t('view.charts.mutual_friend.enable_sharing_prompt.confirm'),
cancelButtonText: t('view.charts.mutual_friend.enable_sharing_prompt.cancel'), cancelText: t('view.charts.mutual_friend.enable_sharing_prompt.cancel')
type: 'info' })
} .then(({ ok }) => {
) if (!ok) return;
.then(() => {
userStore.toggleSharedConnectionsOptOut(); userStore.toggleSharedConnectionsOptOut();
promptInitialFetch(); promptInitialFetch();
}) })
.catch(() => { .catch(() => {});
// cancelled
});
} }
function cancelFetch() { function cancelFetch() {

View File

@@ -521,18 +521,7 @@
</template> </template>
<script setup> <script setup>
import { import { computed, markRaw, nextTick, onBeforeMount, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
computed,
h,
markRaw,
nextTick,
onBeforeMount,
onBeforeUnmount,
onMounted,
reactive,
ref,
watch
} from 'vue';
import { Ellipsis, Loader, RefreshCcw } from 'lucide-vue-next'; import { Ellipsis, Loader, RefreshCcw } from 'lucide-vue-next';
import { MoreFilled, Plus, Refresh } from '@element-plus/icons-vue'; import { MoreFilled, Plus, Refresh } from '@element-plus/icons-vue';
import { InputGroupField, InputGroupSearch } from '@/components/ui/input-group'; import { InputGroupField, InputGroupSearch } from '@/components/ui/input-group';
@@ -558,7 +547,13 @@
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger DropdownMenuTrigger
} from '../../components/ui/dropdown-menu'; } 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 { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../../components/ui/resizable';
import { avatarRequest, favoriteRequest } from '../../api'; import { avatarRequest, favoriteRequest } from '../../api';
@@ -597,6 +592,7 @@
const { sortFavorites } = storeToRefs(useAppearanceSettingsStore()); const { sortFavorites } = storeToRefs(useAppearanceSettingsStore());
const { setSortFavorites } = useAppearanceSettingsStore(); const { setSortFavorites } = useAppearanceSettingsStore();
const favoriteStore = useFavoriteStore(); const favoriteStore = useFavoriteStore();
const modalStore = useModalStore();
const { const {
favoriteAvatars, favoriteAvatars,
favoriteAvatarGroups, favoriteAvatarGroups,
@@ -1183,26 +1179,22 @@
showAvatarImportDialog(); showAvatarImportDialog();
} }
function showAvatarBulkUnfavoriteSelectionConfirm() { async function showAvatarBulkUnfavoriteSelectionConfirm() {
if (!selectedFavoriteAvatars.value.length) { if (!selectedFavoriteAvatars.value.length) {
return; return;
} }
const total = selectedFavoriteAvatars.value.length; const total = selectedFavoriteAvatars.value.length;
ElMessageBox.confirm(
`Are you sure you want to unfavorite ${total} favorites?\n This action cannot be undone.`, const result = await modalStore.confirm({
`Delete ${total} favorites?`, description: `Are you sure you want to unfavorite ${total} favorites?\nThis action cannot be undone.`,
{ title: `Delete ${total} favorites?`
confirmButtonText: 'Confirm', });
cancelButtonText: 'Cancel',
type: 'info' if (!result.ok) {
} return;
) }
.then((action) => {
if (action === 'confirm') { bulkUnfavoriteSelectedAvatars([...selectedFavoriteAvatars.value]);
bulkUnfavoriteSelectedAvatars([...selectedFavoriteAvatars.value]);
}
})
.catch(() => {});
} }
function bulkUnfavoriteSelectedAvatars(ids) { function bulkUnfavoriteSelectedAvatars(ids) {
@@ -1252,17 +1244,14 @@
async function handleCheckInvalidAvatars(groupName) { async function handleCheckInvalidAvatars(groupName) {
handleGroupMenuVisible(localGroupMenuKey(groupName), false); handleGroupMenuVisible(localGroupMenuKey(groupName), false);
try { const startCheckResult = await modalStore.confirm({
await ElMessageBox.confirm( description: t('view.favorite.avatars.check_description'),
t('view.favorite.avatars.check_description'), title: t('view.favorite.avatars.check_invalid'),
t('view.favorite.avatars.check_invalid'), confirmText: t('confirm.confirm_button'),
{ cancelText: t('confirm.cancel_button')
confirmButtonText: t('confirm.confirm_button'), });
cancelButtonText: t('confirm.cancel_button'),
type: 'info' if (!startCheckResult.ok) {
}
);
} catch {
return; return;
} }
@@ -1299,54 +1288,29 @@
return; return;
} }
const confirmDelete = await ElMessageBox.confirm( const invalidIdsText = result.invalidIds.join('\n');
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);
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')); toast.info(t('view.favorite.avatars.delete_cancelled'));
return; return;
} }
@@ -1434,18 +1398,16 @@
} }
function clearFavoriteGroup(ctx) { function clearFavoriteGroup(ctx) {
ElMessageBox.confirm('Continue? Clear Group', 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: 'Continue? Clear Group',
type: 'info' title: 'Confirm'
}) })
.then((action) => { .then(() => {
if (action === 'confirm') { favoriteRequest.clearFavoriteGroup({
favoriteRequest.clearFavoriteGroup({ type: ctx.type,
type: ctx.type, group: ctx.name
group: ctx.name });
});
}
}) })
.catch(() => {}); .catch(() => {});
} }
@@ -1477,16 +1439,12 @@
} }
function promptLocalAvatarFavoriteGroupDelete(group) { function promptLocalAvatarFavoriteGroupDelete(group) {
ElMessageBox.confirm(`Delete Group? ${group}`, 'Confirm', { modalStore
confirmButtonText: 'Confirm', .confirm({
cancelButtonText: 'Cancel', description: `Delete Group? ${group}`,
type: 'info' title: 'Confirm'
})
.then((action) => {
if (action === 'confirm') {
deleteLocalAvatarFavoriteGroup(group);
}
}) })
.then(() => deleteLocalAvatarFavoriteGroup(group))
.catch(() => {}); .catch(() => {});
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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