diff --git a/package-lock.json b/package-lock.json index 3d0a5d2d..6538fad2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,6 @@ "@vitest/coverage-v8": "^4.0.18", "@vue/test-utils": "^2.4.6", "@vueuse/core": "^14.2.1", - "animate.css": "^4.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cross-env": "^10.1.0", @@ -4664,13 +4663,6 @@ "ajv": "^6.9.1" } }, - "node_modules/animate.css": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", - "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==", - "dev": true, - "license": "MIT" - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", diff --git a/package.json b/package.json index fa188477..35a56344 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "@vitest/coverage-v8": "^4.0.18", "@vue/test-utils": "^2.4.6", "@vueuse/core": "^14.2.1", - "animate.css": "^4.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cross-env": "^10.1.0", diff --git a/src/components/FullscreenImagePreview.vue b/src/components/FullscreenImagePreview.vue index c10712f6..d32674d9 100644 --- a/src/components/FullscreenImagePreview.vue +++ b/src/components/FullscreenImagePreview.vue @@ -108,8 +108,6 @@ import { useGeneralSettingsStore } from '@/stores/settings/general'; import { useI18n } from 'vue-i18n'; - import Noty from 'noty'; - import { escapeTag, extractFileId } from '../shared/utils'; import { useGalleryStore } from '../stores'; @@ -303,7 +301,7 @@ toast.success(t('message.image.copied_to_clipboard')); } catch (error) { console.error('Error downloading image:', error); - new Noty({ type: 'error', text: escapeTag(`Failed to download image. ${url}`) }).show(); + toast.error(escapeTag(`Failed to download image. ${url}`)); } finally { toast.dismiss(msg); } @@ -333,7 +331,7 @@ document.body.removeChild(link); } catch (error) { console.error('Error downloading image:', error); - new Noty({ type: 'error', text: escapeTag(`Failed to download image. ${url}`) }).show(); + toast.error(escapeTag(`Failed to download image. ${url}`)); } finally { toast.dismiss(msg); } diff --git a/src/components/dialogs/ChooseFavoriteGroupDialog.vue b/src/components/dialogs/ChooseFavoriteGroupDialog.vue index 41f7dc38..4c4bfd79 100644 --- a/src/components/dialogs/ChooseFavoriteGroupDialog.vue +++ b/src/components/dialogs/ChooseFavoriteGroupDialog.vue @@ -104,10 +104,9 @@ import { Button } from '@/components/ui/button'; import { Check } from 'lucide-vue-next'; import { storeToRefs } from 'pinia'; + import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; - import Noty from 'noty'; - import { useFavoriteStore, useUserStore } from '../../stores'; import { favoriteRequest } from '../../api'; @@ -159,6 +158,9 @@ } ); + /** + * @returns {void} + */ function initFavoriteDialog() { if (favoriteDialog.value.type === 'friend') { groups.value = favoriteFriendGroups.value; @@ -169,6 +171,11 @@ } } + /** + * + * @param {object} group + * @returns {void} + */ function addFavorite(group) { const D = favoriteDialog.value; loading.value = true; @@ -180,7 +187,7 @@ }) .then(() => { isVisible.value = false; - new Noty({ type: 'success', text: 'Favorite added!' }).show(); + toast.success('Favorite added!'); }) .finally(() => { loading.value = false; diff --git a/src/plugin/index.js b/src/plugin/index.js index 0348c00e..7b8f9258 100644 --- a/src/plugin/index.js +++ b/src/plugin/index.js @@ -3,13 +3,19 @@ import { initInteropApi } from './interopApi'; import { initNoty } from './noty'; import { initUi } from './ui'; +/** + * @param {boolean} isVrOverlay + * @returns {Promise} + */ export async function initPlugins(isVrOverlay = false) { await initInteropApi(isVrOverlay); if (!isVrOverlay) { await initUi(); } initDayjs(); - initNoty(isVrOverlay); + if (isVrOverlay) { + initNoty(true); + } } export * from './i18n'; diff --git a/src/service/request.js b/src/service/request.js index bdfd28ea..5ac6607f 100644 --- a/src/service/request.js +++ b/src/service/request.js @@ -1,7 +1,5 @@ import { toast } from 'vue-sonner'; -import Noty from 'noty'; - import { useAuthStore, useModalStore, @@ -189,12 +187,9 @@ export function request(endpoint, options) { text = data.OK; } if (text) { - new Noty({ - type: 'success', - text: options.customMsg - ? options.customMsg - : escapeTag(text) - }).show(); + toast.success( + options.customMsg ? options.customMsg : escapeTag(text) + ); } return data; } diff --git a/src/service/websocket.js b/src/service/websocket.js index 46f49045..4c262c5f 100644 --- a/src/service/websocket.js +++ b/src/service/websocket.js @@ -1,6 +1,6 @@ import { reactive } from 'vue'; -import Noty from 'noty'; +import { toast } from 'vue-sonner'; import { useFriendStore, @@ -99,12 +99,9 @@ function connectWebSocket(token) { }; socket.onerror = () => { if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'error', - text: 'WebSocket Error' - }).show(); + AppDebug.errorNoty = toast.error('WebSocket Error'); socket.onclose( new CloseEvent('close', { code: 1006, // Abnormal Closure @@ -190,12 +187,9 @@ function handlePipeline(args) { if (typeof err !== 'undefined') { console.error('PIPELINE: error', args); if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'error', - text: escapeTag(`WebSocket Error: ${err}`) - }).show(); + AppDebug.errorNoty = toast.error(escapeTag(`WebSocket Error: ${err}`)); return; } if (typeof content === 'undefined') { diff --git a/src/shared/utils/common.js b/src/shared/utils/common.js index 499346b5..74290ed1 100644 --- a/src/shared/utils/common.js +++ b/src/shared/utils/common.js @@ -1,8 +1,6 @@ import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; -import Noty from 'noty'; - import { useAvatarStore, useInstanceStore, @@ -44,10 +42,7 @@ function downloadAndSaveJson(fileName, data) { link.click(); document.body.removeChild(link); } catch { - new Noty({ - type: 'error', - text: escapeTag('Failed to download JSON.') - }).show(); + toast.error(escapeTag('Failed to download JSON.')); } } diff --git a/src/stores/auth.js b/src/stores/auth.js index 84af8048..485b52cf 100644 --- a/src/stores/auth.js +++ b/src/stores/auth.js @@ -246,18 +246,12 @@ export const useAuthStore = defineStore('Auth', () => { await webApiService.clearCookies(); delete user.cookies; relogin(user).then(() => { - new Noty({ - type: 'success', - text: t('message.auth.email_2fa_resent') - }).show(); + toast.success(t('message.auth.email_2fa_resent')); }); return; } } - new Noty({ - type: 'error', - text: t('message.auth.email_2fa_no_credentials') - }).show(); + toast.error(t('message.auth.email_2fa_no_credentials')); } /** @@ -548,10 +542,7 @@ export const useAuthStore = defineStore('Auth', () => { 'savedCredentials', JSON.stringify(savedCredentials) ); - new Noty({ - type: 'success', - text: t('message.auth.account_removed') - }).show(); + toast.success(t('message.auth.account_removed')); } /** @@ -915,27 +906,22 @@ export const useAuthStore = defineStore('Auth', () => { relogin, notifyAutoLoginSuccess: () => { if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'success', - text: t('message.auth.auto_login_success') - }).show(); + AppDebug.errorNoty = toast.success( + t('message.auth.auto_login_success') + ); }, notifyAutoLoginFailed: () => { if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'error', - text: t('message.auth.auto_login_failed') - }).show(); + AppDebug.errorNoty = toast.error( + t('message.auth.auto_login_failed') + ); }, notifyOffline: () => { - AppDebug.errorNoty = new Noty({ - type: 'error', - text: t('message.auth.offline') - }).show(); + AppDebug.errorNoty = toast.error(t('message.auth.offline')); }, flashWindow: () => AppApi.FlashWindow(), isOnline: () => navigator.onLine, diff --git a/src/stores/gallery.js b/src/stores/gallery.js index e8319f9f..89faa1d6 100644 --- a/src/stores/gallery.js +++ b/src/stores/gallery.js @@ -1,9 +1,8 @@ import { reactive, ref, shallowReactive, watch } from 'vue'; import { defineStore } from 'pinia'; +import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; -import Noty from 'noty'; - import { getEmojiFileName, getPrintFileName, @@ -379,9 +378,8 @@ export const useGalleryStore = defineStore('Gallery', () => { try { for (let i = 0; i < 100; i++) { params.offset = i * params.n; - const args = await inventoryRequest.getCachedInventoryItems( - params - ); + const args = + await inventoryRequest.getCachedInventoryItems(params); for (const item of args.json.data) { advancedSettingsStore.currentUserInventory.set( item.id, @@ -427,12 +425,9 @@ export const useGalleryStore = defineStore('Gallery', () => { await vrcPlusImageRequest.deletePrint(printId); const text = `Old print automatically deleted: ${printId}`; if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'info', - text - }).show(); + AppDebug.errorNoty = toast.info(text); } } catch (err) { console.error('Failed to delete old print:', err); diff --git a/src/stores/notification/index.js b/src/stores/notification/index.js index 41b11f62..b5ab4c4a 100644 --- a/src/stores/notification/index.js +++ b/src/stores/notification/index.js @@ -3,7 +3,6 @@ import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; -import Noty from 'noty'; import dayjs from 'dayjs'; import { @@ -369,13 +368,9 @@ export const useNotificationStore = defineStore('Notification', () => { .then((_args) => { const text = `Auto invite sent to ${ref.senderUsername}`; if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'info', - text - }); - AppDebug.errorNoty.show(); + AppDebug.errorNoty = toast.info(text); console.log(text); notificationRequest .hideNotification({ @@ -1341,10 +1336,7 @@ export const useNotificationStore = defineStore('Notification', () => { console.log('Notification response', args); if (!args.json) return; handleNotificationV2Hide(notificationId); - new Noty({ - type: 'success', - text: escapeTag(args.json) - }).show(); + toast.success(escapeTag(args.json)); }) .catch(() => { handleNotificationV2Hide(notificationId); diff --git a/src/stores/user.js b/src/stores/user.js index 8c880883..75c044c6 100644 --- a/src/stores/user.js +++ b/src/stores/user.js @@ -3,8 +3,6 @@ import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; -import Noty from 'noty'; - import { arraysMatch, compareByCreatedAt, @@ -1247,13 +1245,9 @@ export const useUserStore = defineStore('User', () => { userRequest.saveCurrentUser(params).then(() => { const text = `Status automatically changed to ${newStatus}`; if (AppDebug.errorNoty) { - AppDebug.errorNoty.close(); + toast.dismiss(AppDebug.errorNoty); } - AppDebug.errorNoty = new Noty({ - type: 'info', - text - }); - AppDebug.errorNoty.show(); + AppDebug.errorNoty = toast.info(text); console.log(text); }); } diff --git a/src/stores/vrcx.js b/src/stores/vrcx.js index 4a042e89..937cc940 100644 --- a/src/stores/vrcx.js +++ b/src/stores/vrcx.js @@ -3,8 +3,6 @@ import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; -import Noty from 'noty'; - import { DEFAULT_MAX_TABLE_SIZE, DEFAULT_SEARCH_LIMIT, @@ -727,10 +725,7 @@ export const useVrcxStore = defineStore('Vrcx', () => { avatarStore .selectAvatarWithoutConfirmation(avatarId) .then(() => { - new Noty({ - type: 'success', - text: 'Avatar changed via launch command' - }).show(); + toast.success('Avatar changed via launch command'); }); shouldFocusWindow = false; } diff --git a/src/styles/globals.css b/src/styles/globals.css index f9299d4b..b1a092f8 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,7 +1,6 @@ @import 'tailwindcss'; @import 'tw-animate-css'; -@import 'animate.css/animate.min.css'; @import 'noty/lib/noty.css'; @import 'remixicon/fonts/remixicon.css'; @import 'vue-sonner/style.css'; diff --git a/src/styles/noty.css b/src/styles/noty.css index 425a3434..ae32a066 100644 --- a/src/styles/noty.css +++ b/src/styles/noty.css @@ -1,3 +1,97 @@ +/* Noty animate.css keyframes (inlined, only the 4 animations used by noty.js) */ + +.animate__animated { + animation-duration: 1s; + animation-fill-mode: both; +} + +@keyframes bounceInLeft { + from, + 60%, + 75%, + 90%, + to { + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + + 0% { + opacity: 0; + transform: translate3d(-3000px, 0, 0) scaleX(3); + } + + 60% { + opacity: 1; + transform: translate3d(25px, 0, 0) scaleX(1); + } + + 75% { + transform: translate3d(-10px, 0, 0) scaleX(0.98); + } + + 90% { + transform: translate3d(5px, 0, 0) scaleX(0.995); + } + + to { + transform: translate3d(0, 0, 0); + } +} + +.animate__bounceInLeft { + animation-name: bounceInLeft; +} + +@keyframes bounceOutLeft { + 20% { + opacity: 1; + transform: translate3d(20px, 0, 0) scaleX(0.9); + } + + to { + opacity: 0; + transform: translate3d(-2000px, 0, 0) scaleX(2); + } +} + +.animate__bounceOutLeft { + animation-name: bounceOutLeft; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.animate__fadeIn { + animation-name: fadeIn; +} + +@keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + transform: scale3d(0.3, 0.3, 0.3); + } + + to { + opacity: 0; + } +} + +.animate__zoomOut { + animation-name: zoomOut; +} + +/* Noty mint theme */ + .noty_layout { word-break: break-all; } diff --git a/src/vr/vr.css b/src/vr/vr.css index 69384ee8..f6b98abd 100644 --- a/src/vr/vr.css +++ b/src/vr/vr.css @@ -1,6 +1,6 @@ @import 'tailwindcss'; -@import 'animate.css/animate.min.css'; +@import '../styles/noty.css'; @import 'noty/lib/noty.css'; @import 'remixicon/fonts/remixicon.css';