diff --git a/package-lock.json b/package-lock.json
index 19121454..8fe8f8fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,8 @@
"license": "MIT",
"dependencies": {
"hazardous": "^0.3.0",
- "node-api-dotnet": "^0.9.18"
+ "node-api-dotnet": "^0.9.18",
+ "vue-sonner": "^2.0.9"
},
"devDependencies": {
"@electron/rebuild": "^4.0.2",
@@ -19082,6 +19083,28 @@
"node": ">=16.19.0"
}
},
+ "node_modules/vue-sonner": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-2.0.9.tgz",
+ "integrity": "sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@nuxt/kit": "^4.0.3",
+ "@nuxt/schema": "^4.0.3",
+ "nuxt": "^4.0.3"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@nuxt/schema": {
+ "optional": true
+ },
+ "nuxt": {
+ "optional": true
+ }
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
diff --git a/package.json b/package.json
index 2839d3c4..6a419ba8 100644
--- a/package.json
+++ b/package.json
@@ -50,10 +50,10 @@
"@vitejs/plugin-vue": "^6.0.3",
"@vitejs/plugin-vue-jsx": "^5.1.3",
"@vueuse/core": "^14.1.0",
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
"animate.css": "^4.1.1",
"babel-runtime": "^6.26.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
"concurrently": "^9.2.1",
"cross-env": "^10.1.0",
"dayjs": "^1.11.19",
@@ -75,8 +75,8 @@
"prettier": "^3.7.4",
"reka-ui": "^2.7.0",
"remixicon": "^4.8.0",
- "tailwindcss": "^4.1.18",
"tailwind-merge": "^3.4.0",
+ "tailwindcss": "^4.1.18",
"tw-animate-css": "^1.4.0",
"vite": "^7.3.0",
"vue": "^3.5.26",
@@ -172,6 +172,7 @@
},
"dependencies": {
"hazardous": "^0.3.0",
- "node-api-dotnet": "^0.9.18"
+ "node-api-dotnet": "^0.9.18",
+ "vue-sonner": "^2.0.9"
}
}
diff --git a/src/App.vue b/src/App.vue
index d2d825e4..b1cae1cd 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -12,6 +12,7 @@
ondragover="event.preventDefault()"
ondrop="event.preventDefault()">
+
@@ -23,6 +24,7 @@
import { computed, onBeforeMount, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
+ import { Toaster } from './components/ui/sonner';
import { TooltipProvider } from './components/ui/tooltip';
import { createGlobalStores } from './stores';
import { initNoty } from './plugin/noty';
diff --git a/src/api/instance.js b/src/api/instance.js
index ce5a9f15..4e996e1d 100644
--- a/src/api/instance.js
+++ b/src/api/instance.js
@@ -1,4 +1,4 @@
-import { ElMessage } from 'element-plus';
+import { toast } from 'vue-sonner';
import { i18n } from '../plugin/i18n';
import { request } from '../service/request';
@@ -140,16 +140,10 @@ const instanceReq = {
})
.catch((err) => {
if (err?.error?.message) {
- ElMessage({
- message: err.error.message,
- type: 'error'
- });
+ toast.error(err.error.message);
throw err;
}
- ElMessage({
- message: i18n.global.t('message.instance.not_allowed'),
- type: 'error'
- });
+ toast.error(i18n.global.t('message.instance.not_allowed'));
throw err;
});
}
diff --git a/src/app.css b/src/app.css
index ac922757..90c0f21f 100644
--- a/src/app.css
+++ b/src/app.css
@@ -12,6 +12,7 @@
@import 'animate.css/animate.min.css';
@import 'noty/lib/noty.css';
@import 'remixicon/fonts/remixicon.css';
+@import 'vue-sonner/style.css';
@import './styles/flags.css';
@import './styles/animated-emoji.css';
diff --git a/src/components/FullscreenImagePreview.vue b/src/components/FullscreenImagePreview.vue
index 134ede8b..43ab730e 100644
--- a/src/components/FullscreenImagePreview.vue
+++ b/src/components/FullscreenImagePreview.vue
@@ -38,8 +38,8 @@
diff --git a/src/components/InstanceInfo.vue b/src/components/InstanceInfo.vue
index 0844976a..8955e4b1 100644
--- a/src/components/InstanceInfo.vue
+++ b/src/components/InstanceInfo.vue
@@ -63,9 +63,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/ui/sonner/index.js b/src/components/ui/sonner/index.js
new file mode 100644
index 00000000..93765a16
--- /dev/null
+++ b/src/components/ui/sonner/index.js
@@ -0,0 +1 @@
+export { default as Toaster } from './Sonner.vue';
diff --git a/src/service/request.js b/src/service/request.js
index 6389e1fb..dc0c9d4b 100644
--- a/src/service/request.js
+++ b/src/service/request.js
@@ -1,4 +1,5 @@
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
+import { toast } from 'vue-sonner';
import Noty from 'noty';
@@ -198,10 +199,7 @@ export function request(endpoint, options) {
status === 404 &&
endpoint?.startsWith('avatars/')
) {
- ElMessage({
- message: t('message.api_handler.avatar_private_or_deleted'),
- type: 'error'
- });
+ toast.error(t('message.api_handler.avatar_private_or_deleted'));
avatarStore.avatarDialog.visible = false;
$throw(404, data.error?.message || '', endpoint);
}
diff --git a/src/shared/utils/base/ui.js b/src/shared/utils/base/ui.js
index c5b2ab4f..cd1cb97e 100644
--- a/src/shared/utils/base/ui.js
+++ b/src/shared/utils/base/ui.js
@@ -1,5 +1,5 @@
-import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
+import { toast } from 'vue-sonner';
import { THEME_CONFIG } from '../../constants';
import { i18n } from '../../../plugin/i18n';
@@ -327,11 +327,7 @@ async function getThemeMode(configRepository) {
function redirectToToolsTab() {
router.push({ name: 'tools' });
- ElMessage({
- message: i18n.global.t('view.tools.redirect_message'),
- type: 'primary',
- duration: 3000
- });
+ toast(i18n.global.t('view.tools.redirect_message'), { duration: 3000 });
}
export {
diff --git a/src/shared/utils/common.js b/src/shared/utils/common.js
index 862c590b..23d9139d 100644
--- a/src/shared/utils/common.js
+++ b/src/shared/utils/common.js
@@ -1,5 +1,6 @@
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { storeToRefs } from 'pinia';
+import { toast } from 'vue-sonner';
import Noty from 'noty';
@@ -159,17 +160,11 @@ function copyToClipboard(text, message = 'Copied successfully!') {
navigator.clipboard
.writeText(text)
.then(() => {
- ElMessage({
- message: message,
- type: 'success'
- });
+ toast.success(message);
})
.catch((err) => {
console.error('Copy failed:', err);
- ElMessage({
- message: 'Copy failed!',
- type: 'error'
- });
+ toast.error('Copy failed!');
});
}
diff --git a/src/shared/utils/imageUpload.js b/src/shared/utils/imageUpload.js
index 594ac81a..3caeb904 100644
--- a/src/shared/utils/imageUpload.js
+++ b/src/shared/utils/imageUpload.js
@@ -1,5 +1,4 @@
-import { ElMessage } from 'element-plus';
-
+import { toast } from 'vue-sonner';
function resolveMessage(message) {
if (typeof message === 'function') {
return message();
@@ -47,10 +46,7 @@ export function handleImageUploadInput(event, options = {}) {
const file = files[0];
if (file.size >= maxSize) {
if (tooLargeMessage) {
- ElMessage({
- message: resolveMessage(tooLargeMessage),
- type: 'error'
- });
+ toast.error(resolveMessage(tooLargeMessage));
}
clearInput();
return { file: null, clearInput };
@@ -66,10 +62,7 @@ export function handleImageUploadInput(event, options = {}) {
if (acceptRegex && !acceptRegex.test(file.type)) {
if (invalidTypeMessage) {
- ElMessage({
- message: resolveMessage(invalidTypeMessage),
- type: 'error'
- });
+ toast.error(resolveMessage(invalidTypeMessage));
}
clearInput();
return { file: null, clearInput };
diff --git a/src/stores/auth.js b/src/stores/auth.js
index 5a2f83a3..6a55e5fa 100644
--- a/src/stores/auth.js
+++ b/src/stores/auth.js
@@ -1,6 +1,7 @@
import { reactive, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import Noty from 'noty';
@@ -477,10 +478,7 @@ export const useAuthStore = defineStore('Auth', () => {
});
})
.catch((_) => {
- ElMessage({
- message: 'Incorrect primary password',
- type: 'error'
- });
+ toast.error('Incorrect primary password');
reject(_);
});
} else {
diff --git a/src/stores/avatar.js b/src/stores/avatar.js
index 32767887..69012cb6 100644
--- a/src/stores/avatar.js
+++ b/src/stores/avatar.js
@@ -1,6 +1,7 @@
import { nextTick, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import {
checkVRChatCache,
@@ -468,10 +469,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
} catch (err) {
const msg = `Avatar search failed for ${search} with ${avatarProviderStore.avatarRemoteDatabaseProvider}\n${err}`;
console.error(msg);
- ElMessage({
- message: msg,
- type: 'error'
- });
+ toast.error(msg);
}
} else if (type === 'authorId') {
const length =
@@ -546,10 +544,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
} catch (err) {
const msg = `Avatar lookup failed for ${authorId} with ${url}\n${err}`;
console.error(msg);
- ElMessage({
- message: msg,
- type: 'error'
- });
+ toast.error(msg);
}
return avatars;
}
@@ -568,10 +563,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
async function selectAvatarWithoutConfirmation(id) {
if (userStore.currentUser.currentAvatar === id) {
- ElMessage({
- message: 'Avatar already selected',
- type: 'info'
- });
+ toast.info('Avatar already selected');
return;
}
return avatarRequest
@@ -579,10 +571,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
avatarId: id
})
.then(() => {
- ElMessage({
- message: 'Avatar changed',
- type: 'success'
- });
+ toast.success('Avatar changed');
});
}
@@ -614,10 +603,7 @@ export const useAvatarStore = defineStore('Avatar', () => {
) {
const fileId = extractFileId(currentAvatarImageUrl);
if (!fileId) {
- ElMessage({
- message: 'Sorry, the author is unknown',
- type: 'error'
- });
+ toast.error('Sorry, the author is unknown');
} else if (refUserId === userStore.currentUser.id) {
showAvatarDialog(userStore.currentUser.currentAvatar);
} else {
@@ -637,16 +623,11 @@ export const useAvatarStore = defineStore('Avatar', () => {
}
if (!avatarId) {
if (avatarInfo.ownerId === refUserId) {
- ElMessage({
- message:
- "It's personal (own) avatar or not found in avatar database",
- type: 'warning'
- });
+ toast.warning(
+ "It's personal (own) avatar or not found in avatar database"
+ );
} else {
- ElMessage({
- message: 'Avatar not found in avatar database',
- type: 'warning'
- });
+ toast.warning('Avatar not found in avatar database');
userStore.showUserDialog(avatarInfo.ownerId);
}
}
diff --git a/src/stores/charts.js b/src/stores/charts.js
index 741b0776..933a93d2 100644
--- a/src/stores/charts.js
+++ b/src/stores/charts.js
@@ -1,6 +1,7 @@
import { computed, reactive, ref, watch } from 'vue';
-import { ElMessage, ElNotification } from 'element-plus';
+import { ElNotification } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { useFriendStore } from './friend';
@@ -38,12 +39,8 @@ export const useChartsStore = defineStore('Charts', () => {
const friendCount = computed(() => friendStore.friends.size || 0);
function showInfoMessage(message, type) {
- ElMessage({
- message,
- type,
- duration: 4000,
- grouping: true
- });
+ const toastFn = toast[type] ?? toast;
+ toastFn(message, { duration: 4000 });
}
watch(
diff --git a/src/stores/favorite.js b/src/stores/favorite.js
index 590299db..b3173647 100644
--- a/src/stores/favorite.js
+++ b/src/stores/favorite.js
@@ -1,6 +1,6 @@
import { computed, reactive, ref, shallowReactive, watch } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import {
@@ -1071,12 +1071,11 @@ export const useFavoriteStore = defineStore('Favorite', () => {
*/
function renameLocalAvatarFavoriteGroup(newName, group) {
if (localAvatarFavoriteGroups.value.includes(newName)) {
- ElMessage({
- message: t('prompt.local_favorite_group_rename.message.error', {
+ toast.error(
+ t('prompt.local_favorite_group_rename.message.error', {
name: newName
- }),
- type: 'error'
- });
+ })
+ );
return;
}
localAvatarFavorites[newName] = localAvatarFavorites[group];
@@ -1092,12 +1091,11 @@ export const useFavoriteStore = defineStore('Favorite', () => {
*/
function newLocalAvatarFavoriteGroup(group) {
if (localAvatarFavoriteGroups.value.includes(group)) {
- ElMessage({
- message: t('prompt.new_local_favorite_group.message.error', {
+ toast.error(
+ t('prompt.new_local_favorite_group.message.error', {
name: group
- }),
- type: 'error'
- });
+ })
+ );
return;
}
if (!localAvatarFavorites[group]) {
@@ -1355,12 +1353,11 @@ export const useFavoriteStore = defineStore('Favorite', () => {
*/
function renameLocalWorldFavoriteGroup(newName, group) {
if (localWorldFavoriteGroups.value.includes(newName)) {
- ElMessage({
- message: t('prompt.local_favorite_group_rename.message.error', {
+ toast.error(
+ t('prompt.local_favorite_group_rename.message.error', {
name: newName
- }),
- type: 'error'
- });
+ })
+ );
return;
}
localWorldFavorites[newName] = localWorldFavorites[group];
@@ -1474,12 +1471,11 @@ export const useFavoriteStore = defineStore('Favorite', () => {
*/
function newLocalWorldFavoriteGroup(group) {
if (localWorldFavoriteGroups.value.includes(group)) {
- ElMessage({
- message: t('prompt.new_local_favorite_group.message.error', {
+ toast.error(
+ t('prompt.new_local_favorite_group.message.error', {
name: group
- }),
- type: 'error'
- });
+ })
+ );
return;
}
if (!localWorldFavorites[group]) {
diff --git a/src/stores/friend.js b/src/stores/friend.js
index 33d63d89..b8045f13 100644
--- a/src/stores/friend.js
+++ b/src/stores/friend.js
@@ -1,6 +1,7 @@
import { computed, reactive, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import {
@@ -1606,10 +1607,7 @@ export const useFriendStore = defineStore('Friend', () => {
}
} catch (err) {
if (!AppDebug.dontLogMeOut) {
- ElMessage({
- message: t('message.friend.load_failed'),
- type: 'error'
- });
+ toast.error(t('message.friend.load_failed'));
authStore.handleLogoutEvent();
throw err;
}
diff --git a/src/stores/game.js b/src/stores/game.js
index 6d69a6dd..cb8dd1f7 100644
--- a/src/stores/game.js
+++ b/src/stores/game.js
@@ -1,6 +1,7 @@
-import { ElMessage, ElMessageBox } from 'element-plus';
import { reactive, ref } from 'vue';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import {
deleteVRChatCache as _deleteVRChatCache,
@@ -123,10 +124,7 @@ export const useGameStore = defineStore('Game', () => {
}
AppApi.FocusWindow();
const message = 'VRChat crashed, attempting to rejoin last instance';
- ElMessage({
- message,
- type: 'info'
- });
+ toast(message);
const entry = {
created_at: new Date().toJSON(),
type: 'Event',
diff --git a/src/stores/gameLog.js b/src/stores/gameLog.js
index 69ce9b7a..5a78ef6a 100644
--- a/src/stores/gameLog.js
+++ b/src/stores/gameLog.js
@@ -1,6 +1,7 @@
import { reactive, ref, shallowReactive, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import dayjs from 'dayjs';
@@ -1400,11 +1401,9 @@ export const useGameLogStore = defineStore('GameLog', () => {
async function disableGameLogDialog() {
if (gameStore.isGameRunning) {
- ElMessage({
- message:
- 'VRChat needs to be closed before this option can be changed',
- type: 'error'
- });
+ toast.error(
+ 'VRChat needs to be closed before this option can be changed'
+ );
return;
}
if (!advancedSettingsStore.gameLogDisabled) {
diff --git a/src/stores/group.js b/src/stores/group.js
index c51de98f..48c67994 100644
--- a/src/stores/group.js
+++ b/src/stores/group.js
@@ -1,6 +1,7 @@
import { nextTick, reactive, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import {
groupRequest,
@@ -150,10 +151,7 @@ export const useGroupStore = defineStore('Group', () => {
.catch((err) => {
D.loading = false;
D.visible = false;
- ElMessage({
- message: 'Failed to load group',
- type: 'error'
- });
+ toast.error('Failed to load group');
throw err;
})
.then((args) => {
@@ -596,10 +594,7 @@ export const useGroupStore = defineStore('Group', () => {
})
.then((args) => {
handleGroupMemberProps(args);
- ElMessage({
- message: 'Group visibility updated',
- type: 'success'
- });
+ toast.success('Group visibility updated');
return args;
});
}
@@ -611,10 +606,7 @@ export const useGroupStore = defineStore('Group', () => {
})
.then((args) => {
handleGroupMemberProps(args);
- ElMessage({
- message: 'Group subscription updated',
- type: 'success'
- });
+ toast.success('Group subscription updated');
return args;
});
}
diff --git a/src/stores/instance.js b/src/stores/instance.js
index fbe759d8..1cf9f641 100644
--- a/src/stores/instance.js
+++ b/src/stores/instance.js
@@ -1,6 +1,6 @@
-import { computed, reactive, ref, watch } from 'vue';
-import { ElMessage } from 'element-plus';
+import { reactive, ref, watch } from 'vue';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import {
@@ -16,8 +16,7 @@ import {
hasGroupPermission,
isRealInstance,
parseLocation,
- replaceBioSymbols,
- replaceReactiveObject
+ replaceBioSymbols
} from '../shared/utils';
import { instanceRequest, userRequest, worldRequest } from '../api';
import { database } from '../service/database';
@@ -72,6 +71,7 @@ export const useInstanceStore = defineStore('Instance', () => {
lastUpdated: ''
});
+ /** @type {import('vue').Ref} */
const currentInstanceLocation = ref({});
const queuedInstances = reactive(new Map());
@@ -877,11 +877,8 @@ export const useInstanceStore = defineStore('Instance', () => {
function removeAllQueuedInstances() {
queuedInstances.forEach((ref) => {
- ElMessage({
- message: `Removed instance ${ref.$worldName} from queue`,
- type: 'info'
- });
- ref.$msgBox?.close();
+ toast.info(`Removed instance ${ref.$worldName} from queue`);
+ toast.dismiss(ref.$msgBox);
});
queuedInstances.clear();
}
@@ -893,7 +890,7 @@ export const useInstanceStore = defineStore('Instance', () => {
function removeQueuedInstance(instanceId) {
const ref = queuedInstances.get(instanceId);
if (typeof ref !== 'undefined') {
- ref.$msgBox.close();
+ toast.dismiss(ref.$msgBox);
queuedInstances.delete(instanceId);
}
}
@@ -905,13 +902,12 @@ export const useInstanceStore = defineStore('Instance', () => {
function applyQueuedInstance(instanceId) {
queuedInstances.forEach((ref) => {
if (ref.location !== instanceId) {
- ElMessage({
- message: t('message.instance.removed_form_queue', {
+ toast.info(
+ t('message.instance.removed_form_queue', {
worldName: ref.$worldName
- }),
- type: 'info'
- });
- ref.$msgBox?.close();
+ })
+ );
+ toast.dismiss(ref.$msgBox);
queuedInstances.delete(ref.location);
}
});
@@ -953,7 +949,7 @@ export const useInstanceStore = defineStore('Instance', () => {
function instanceQueueReady(instanceId) {
const ref = queuedInstances.get(instanceId);
if (typeof ref !== 'undefined') {
- ref.$msgBox.close();
+ toast.dismiss(ref.$msgBox);
queuedInstances.delete(instanceId);
}
const L = parseLocation(instanceId);
@@ -961,10 +957,7 @@ export const useInstanceStore = defineStore('Instance', () => {
const groupName = group?.name ?? '';
const worldName = ref?.$worldName ?? '';
const location = displayLocation(instanceId, worldName, groupName);
- ElMessage({
- message: `Instance ready to join ${location}`,
- type: 'success'
- });
+ toast.success(`Instance ready to join ${location}`);
const noty = {
created_at: new Date().toJSON(),
type: 'group.queueReady',
@@ -1021,14 +1014,16 @@ export const useInstanceStore = defineStore('Instance', () => {
ref.$worldName,
ref.$groupName
);
- ref.$msgBox?.close();
- ref.$msgBox = ElMessage({
- message: `You are in position ${ref.position} of ${ref.queueSize} in the queue for ${location} `,
- type: 'info',
- duration: 0,
- showClose: true,
- customClass: 'vrc-instance-queue-message'
- });
+ toast.dismiss(ref.$msgBox ?? undefined);
+ ref.$msgBox = toast.info(
+ `You are in position ${ref.position} of ${ref.queueSize} in the queue for ${location} `,
+ {
+ duration: Infinity,
+ position: 'bottom-right',
+ closeButton: true,
+ class: 'vrc-instance-queue-message'
+ }
+ );
queuedInstances.set(instanceId, ref);
// workerTimers.setTimeout(this.instanceQueueTimeout, 3600000);
}
@@ -1196,7 +1191,7 @@ export const useInstanceStore = defineStore('Instance', () => {
// $app.methods.instanceQueueClear = function () {
// // remove all instances from queue
// queuedInstances.forEach((ref) => {
- // ref.$msgBox.close();
+ // toast.dismiss(ref.$msgBox);
// queuedInstances.delete(ref.location);
// });
// };
diff --git a/src/stores/invite.js b/src/stores/invite.js
index 6220f556..239a51cd 100644
--- a/src/stores/invite.js
+++ b/src/stores/invite.js
@@ -1,6 +1,6 @@
import { computed, ref, watch } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { instanceRequest, inviteMessagesRequest } from '../api';
import { parseLocation } from '../shared/utils';
@@ -111,10 +111,7 @@ export const useInviteStore = defineStore('Invite', () => {
instanceStore.createNewInstance(worldId).then((args) => {
const location = args?.json?.location;
if (!location) {
- ElMessage({
- message: 'Failed to create instance',
- type: 'error'
- });
+ toast.error('Failed to create instance');
return;
}
// self invite
@@ -134,10 +131,7 @@ export const useInviteStore = defineStore('Invite', () => {
worldId: L.worldId
})
.then((args) => {
- ElMessage({
- message: 'Self invite sent',
- type: 'success'
- });
+ toast.success('Self invite sent');
return args;
});
});
diff --git a/src/stores/launch.js b/src/stores/launch.js
index d7d29c02..f4de4063 100644
--- a/src/stores/launch.js
+++ b/src/stores/launch.js
@@ -1,6 +1,6 @@
import { nextTick, ref, watch } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { instanceRequest } from '../api';
import { parseLocation } from '../shared/utils';
@@ -103,11 +103,9 @@ export const useLaunchStore = defineStore('Launch', () => {
}
console.log('Attach Game', launchUrl, result);
if (!result) {
- ElMessage({
- message:
- 'Failed open instance in VRChat, falling back to self invite',
- type: 'warning'
- });
+ toast.warning(
+ 'Failed open instance in VRChat, falling back to self invite'
+ );
// self invite fallback
try {
const L = parseLocation(location);
@@ -116,10 +114,7 @@ export const useLaunchStore = defineStore('Launch', () => {
worldId: L.worldId,
shortName
});
- ElMessage({
- message: 'Self invite sent',
- type: 'success'
- });
+ toast.success('Self invite sent');
} catch (e) {
console.error(e);
}
@@ -157,38 +152,25 @@ export const useLaunchStore = defineStore('Launch', () => {
args.join(' ')
);
if (!result) {
- ElMessage({
- message:
- 'Failed to launch VRChat, invalid custom path set',
- type: 'error'
- });
+ toast.error(
+ 'Failed to launch VRChat, invalid custom path set'
+ );
} else {
- ElMessage({
- message: 'VRChat launched',
- type: 'success'
- });
+ toast.success('VRChat launched');
}
} else {
const result = await AppApi.StartGame(args.join(' '));
if (!result) {
- ElMessage({
- message:
- 'Failed to find VRChat, set a custom path in launch options',
- type: 'error'
- });
+ toast.error(
+ 'Failed to find VRChat, set a custom path in launch options'
+ );
} else {
- ElMessage({
- message: 'VRChat launched',
- type: 'success'
- });
+ toast.success('VRChat launched');
}
}
} catch (e) {
console.error(e);
- ElMessage({
- message: `Failed to launch VRChat: ${e.message}`,
- type: 'error'
- });
+ toast.error(`Failed to launch VRChat: ${e.message}`);
}
console.log('Launch Game', args.join(' '), desktopMode);
}
diff --git a/src/stores/photon.js b/src/stores/photon.js
index e38c6620..4248ddd4 100644
--- a/src/stores/photon.js
+++ b/src/stores/photon.js
@@ -1,6 +1,7 @@
import { computed, reactive, ref } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import {
@@ -433,10 +434,7 @@ export const usePhotonStore = defineStore('Photon', () => {
userStore.lookupUser(ref);
}
} else {
- ElMessage({
- message: 'No user info available',
- type: 'error'
- });
+ toast.error('No user info available');
}
}
}
diff --git a/src/stores/search.js b/src/stores/search.js
index 4ef1259a..9afa19d1 100644
--- a/src/stores/search.js
+++ b/src/stores/search.js
@@ -1,6 +1,7 @@
import { computed, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@@ -367,10 +368,7 @@ export const useSearchStore = defineStore('Search', () => {
if (action === 'confirm' && value) {
const input = value.trim();
if (!directAccessParse(input)) {
- ElMessage({
- message: t('prompt.direct_access_omni.message.error'),
- type: 'error'
- });
+ toast.error(t('prompt.direct_access_omni.message.error'));
}
}
} catch (error) {
diff --git a/src/stores/settings/advanced.js b/src/stores/settings/advanced.js
index 108252c7..b9cceda7 100644
--- a/src/stores/settings/advanced.js
+++ b/src/stores/settings/advanced.js
@@ -1,10 +1,12 @@
import { reactive, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { AppDebug } from '../../service/appConfig';
import { database } from '../../service/database';
+import { languageCodes } from '../../localization';
import { useGameStore } from '../game';
import { useVRCXUpdaterStore } from '../vrcxUpdater';
import { useVrcxStore } from '../vrcx';
@@ -12,7 +14,6 @@ import { watchState } from '../../service/watchState';
import configRepository from '../../service/config';
import webApiService from '../../service/webapi';
-import { languageCodes } from '../../localization';
export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
const gameStore = useGameStore();
@@ -162,10 +163,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
configRepository.getString('VRCX_SentryEnabled', '')
]);
- if (
- !bioLanguageConfig ||
- !languageCodes.includes(bioLanguageConfig)
- ) {
+ if (!bioLanguageConfig || !languageCodes.includes(bioLanguageConfig)) {
bioLanguage.value = 'en';
} else {
bioLanguage.value = bioLanguageConfig;
@@ -628,10 +626,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
async function translateText(text, targetLang, overrides) {
if (!translationApi.value) {
- ElMessage({
- message: 'Translation API disabled',
- type: 'warning'
- });
+ toast.warning('Translation API disabled');
return null;
}
@@ -641,10 +636,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
if (provider === 'google') {
const keyToUse = overrides?.key ?? translationApiKey.value;
if (!keyToUse) {
- ElMessage({
- message: 'No Translation API key configured',
- type: 'warning'
- });
+ toast.warning('No Translation API key configured');
return null;
}
try {
@@ -672,10 +664,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
}
return data.data.translations[0].translatedText;
} catch (err) {
- ElMessage({
- message: `Translation failed: ${err.message}`,
- type: 'error'
- });
+ toast.error(`Translation failed: ${err.message}`);
return null;
}
}
@@ -692,10 +681,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
`You are a translation assistant. Translate the user message into ${targetLang}. Only return the translated text.`;
if (!endpoint || !model) {
- ElMessage({
- message: 'Translation endpoint/model missing',
- type: 'warning'
- });
+ toast.warning('Translation endpoint/model missing');
return null;
}
@@ -742,10 +728,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
const translated = data?.choices?.[0]?.message?.content;
return typeof translated === 'string' ? translated.trim() : null;
} catch (err) {
- ElMessage({
- message: `Translation failed: ${err.message}`,
- type: 'error'
- });
+ toast.error(`Translation failed: ${err.message}`);
return null;
}
}
@@ -769,25 +752,18 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
)
.then(async ({ action }) => {
if (action === 'confirm') {
- const msgBox = ElMessage({
- message: 'Batch print cropping in progress...',
- type: 'warning',
- duration: 0
- });
+ const msgBox = toast.warning(
+ 'Batch print cropping in progress...',
+ { duration: Infinity, position: 'bottom-right' }
+ );
try {
await AppApi.CropAllPrints(ugcFolderPath.value);
- ElMessage({
- message: 'Batch print cropping complete',
- type: 'success'
- });
+ toast.success('Batch print cropping complete');
} catch (err) {
console.error(err);
- ElMessage({
- message: `Batch print cropping failed: ${err}`,
- type: 'error'
- });
+ toast.error(`Batch print cropping failed: ${err}`);
} finally {
- msgBox.close();
+ toast.dismiss(msgBox);
}
}
})
@@ -836,25 +812,18 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
)
.then(async ({ action }) => {
if (action === 'confirm') {
- const msgBox = ElMessage({
- message: 'Batch metadata removal in progress...',
- type: 'warning',
- duration: 0
- });
+ const msgBox = toast.warning(
+ 'Batch metadata removal in progress...',
+ { duration: Infinity, position: 'bottom-right' }
+ );
try {
await AppApi.DeleteAllScreenshotMetadata();
- ElMessage({
- message: 'Batch metadata removal complete',
- type: 'success'
- });
+ toast.success('Batch metadata removal complete');
} catch (err) {
console.error(err);
- ElMessage({
- message: `Batch metadata removal failed: ${err}`,
- type: 'error'
- });
+ toast.error(`Batch metadata removal failed: ${err}`);
} finally {
- msgBox.close();
+ toast.dismiss(msgBox);
}
}
})
diff --git a/src/stores/ui.js b/src/stores/ui.js
index 4e64ead3..f212454f 100644
--- a/src/stores/ui.js
+++ b/src/stores/ui.js
@@ -1,6 +1,6 @@
import { ref, watch } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useMagicKeys } from '@vueuse/core';
import { useRouter } from 'vue-router';
@@ -54,10 +54,7 @@ export const useUiStore = defineStore('Ui', () => {
if (isPressed) {
refreshCustomCss();
updateLocalizedStrings();
- ElMessage({
- message: 'Custom CSS and localization strings refreshed',
- type: 'success'
- });
+ toast.success('Custom CSS and localization strings refreshed');
}
});
diff --git a/src/stores/user.js b/src/stores/user.js
index 750d4765..28265efb 100644
--- a/src/stores/user.js
+++ b/src/stores/user.js
@@ -1,6 +1,6 @@
import { computed, reactive, ref, shallowReactive, watch } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import Noty from 'noty';
@@ -842,10 +842,7 @@ export const useUserStore = defineStore('User', () => {
.catch((err) => {
D.loading = false;
D.visible = false;
- ElMessage({
- message: 'Failed to load user',
- type: 'error'
- });
+ toast.error('Failed to load user');
throw err;
})
.then((args) => {
@@ -1218,10 +1215,7 @@ export const useUserStore = defineStore('User', () => {
return;
}
}
- ElMessage({
- message: 'Own avatar not found',
- type: 'error'
- });
+ toast.error('Own avatar not found');
}
}
});
diff --git a/src/stores/vrcx.js b/src/stores/vrcx.js
index 6edf2589..bb89716d 100644
--- a/src/stores/vrcx.js
+++ b/src/stores/vrcx.js
@@ -1,6 +1,7 @@
import { reactive, ref, watch } from 'vue';
-import { ElMessage, ElMessageBox } from 'element-plus';
+import { ElMessageBox } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import Noty from 'noty';
@@ -160,12 +161,10 @@ export const useVrcxStore = defineStore('Vrcx', () => {
let msgBox;
if (state.databaseVersion < databaseVersion) {
if (state.databaseVersion) {
- msgBox = ElMessage({
- message:
- 'DO NOT CLOSE VRCX, database upgrade in progress...',
- type: 'warning',
- duration: 0
- });
+ msgBox = toast.warning(
+ 'DO NOT CLOSE VRCX, database upgrade in progress...',
+ { duration: Infinity, position: 'bottom-right' }
+ );
}
console.log(
`Updating database from ${state.databaseVersion} to ${databaseVersion}...`
@@ -188,24 +187,19 @@ export const useVrcxStore = defineStore('Vrcx', () => {
databaseVersion
);
console.log('Database update complete.');
- msgBox?.close();
+ toast.dismiss(msgBox);
if (state.databaseVersion) {
// only display when database exists
- ElMessage({
- message: 'Database upgrade complete',
- type: 'success'
- });
+ toast.success('Database upgrade complete');
}
state.databaseVersion = databaseVersion;
} catch (err) {
console.error(err);
- msgBox?.close();
- ElMessage({
- message:
- 'Database upgrade failed, check console for details',
- type: 'error',
- duration: 120000
- });
+ toast.dismiss(msgBox);
+ toast.error(
+ 'Database upgrade failed, check console for details',
+ { duration: 120000 }
+ );
AppApi.ShowDevTools();
}
}
@@ -365,12 +359,10 @@ export const useVrcxStore = defineStore('Vrcx', () => {
} catch (e) {
console.error('Failed to add screenshot metadata', e);
if (e.message?.includes('UnauthorizedAccessException')) {
- ElMessage({
- message:
- 'Failed to add screenshot metadata, access denied. Make sure VRCX has permission to access the screenshot folder.',
- type: 'error',
- duration: 10000
- });
+ toast.error(
+ 'Failed to add screenshot metadata, access denied. Make sure VRCX has permission to access the screenshot folder.',
+ { duration: 10000 }
+ );
}
return;
}
@@ -547,10 +539,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
}
}
- ElMessage({
- message: t('message.crash.vrcx_reload'),
- type: 'success'
- });
+ toast.success(t('message.crash.vrcx_reload'));
return;
}
eventLaunchCommand(command);
@@ -603,10 +592,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
const regexAvatarId =
/avtr_[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/g;
if (!avatarId.match(regexAvatarId) || avatarId.length !== 41) {
- ElMessage({
- message: 'Invalid Avatar ID',
- type: 'error'
- });
+ toast.error('Invalid Avatar ID');
break;
}
if (advancedSettingsStore.showConfirmationOnSwitchAvatar) {
diff --git a/src/stores/vrcxUpdater.js b/src/stores/vrcxUpdater.js
index 7efbee4c..073ed9a1 100644
--- a/src/stores/vrcxUpdater.js
+++ b/src/stores/vrcxUpdater.js
@@ -1,6 +1,6 @@
import { computed, ref } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { AppDebug } from '../service/appConfig';
@@ -211,12 +211,11 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
checkingForVRCXUpdate.value = false;
}
if (response.status !== 200) {
- ElMessage({
- message: t('message.vrcx_updater.failed', {
+ toast.error(
+ t('message.vrcx_updater.failed', {
message: `${response.status} ${response.data}`
- }),
- type: 'error'
- });
+ })
+ );
return;
}
pendingVRCXUpdate.value = false;
@@ -290,12 +289,11 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
checkingForVRCXUpdate.value = false;
}
if (response.status !== 200) {
- ElMessage({
- message: t('message.vrcx_updater.failed', {
+ toast.error(
+ t('message.vrcx_updater.failed', {
message: `${response.status} ${response.data}`
- }),
- type: 'error'
- });
+ })
+ );
return;
}
if (AppDebug.debugWebRequests) {
@@ -303,12 +301,11 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
}
const releases = [];
if (typeof json !== 'object' || json.message) {
- ElMessage({
- message: t('message.vrcx_updater.failed', {
+ toast.error(
+ t('message.vrcx_updater.failed', {
message: json.message
- }),
- type: 'error'
- });
+ })
+ );
return;
}
for (const release of json) {
@@ -347,10 +344,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => {
pendingVRCXInstall.value = releaseName;
} catch (err) {
console.error(err);
- ElMessage({
- message: `${t('message.vrcx_updater.failed_install')} ${err}`,
- type: 'error'
- });
+ toast.error(`${t('message.vrcx_updater.failed_install')} ${err}`);
} finally {
updateInProgress.value = false;
updateProgress.value = 0;
diff --git a/src/stores/world.js b/src/stores/world.js
index d9aec62d..23826212 100644
--- a/src/stores/world.js
+++ b/src/stores/world.js
@@ -1,6 +1,6 @@
import { reactive, shallowReactive, watch } from 'vue';
-import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia';
+import { toast } from 'vue-sonner';
import {
checkVRChatCache,
@@ -132,10 +132,7 @@ export const useWorldStore = defineStore('World', () => {
.catch((err) => {
D.loading = false;
D.visible = false;
- ElMessage({
- message: 'Failed to load world',
- type: 'error'
- });
+ toast.error('Failed to load world');
throw err;
})
.then((args) => {
diff --git a/src/views/Charts/components/MutualFriends.vue b/src/views/Charts/components/MutualFriends.vue
index c97f1bb3..9135b4ca 100644
--- a/src/views/Charts/components/MutualFriends.vue
+++ b/src/views/Charts/components/MutualFriends.vue
@@ -104,10 +104,11 @@