From 59d8a19c37e0387be0ef088ef6efbb36f6685f8e Mon Sep 17 00:00:00 2001 From: pa Date: Sun, 22 Mar 2026 22:17:02 +0900 Subject: [PATCH] add whats new dialog --- src/components/onboarding/WhatsNewDialog.vue | 193 +++++++++++++++++++ src/localization/en.json | 30 ++- src/shared/constants/whatsNewReleases.js | 66 +++++++ src/stores/vrcxUpdater.js | 66 ++++++- src/views/Layout/MainLayout.vue | 5 +- 5 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 src/components/onboarding/WhatsNewDialog.vue create mode 100644 src/shared/constants/whatsNewReleases.js diff --git a/src/components/onboarding/WhatsNewDialog.vue b/src/components/onboarding/WhatsNewDialog.vue new file mode 100644 index 00000000..d85c17af --- /dev/null +++ b/src/components/onboarding/WhatsNewDialog.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/src/localization/en.json b/src/localization/en.json index f71d27ff..c019000e 100644 --- a/src/localization/en.json +++ b/src/localization/en.json @@ -2983,7 +2983,35 @@ "whatsnew": { "title": "What's New in VRCX", "subtitle": "Here's what changed in this update.", - "cta": "Got It" + "cta": "Got It", + "common": { + "got_it": "Got It", + "view_details": "View detailed changelog", + "support": "Support VRCX" + }, + "releases": { + "v2026_04_a": { + "title": "What's New in VRCX", + "items": { + "quick_search": { + "title": "Quick Search", + "description": "Find friends, worlds, avatars, and groups from one search." + }, + "dashboard": { + "title": "Dashboard", + "description": "Build a workspace with widgets that fit how you work." + }, + "activity_insights": { + "title": "Activity Insights", + "description": "See activity heatmaps, overlap, and the worlds you visit most." + }, + "my_avatars": { + "title": "My Avatars", + "description": "Browse, manage, and edit your avatars in one place." + } + } + } + } } }, "status_bar": { diff --git a/src/shared/constants/whatsNewReleases.js b/src/shared/constants/whatsNewReleases.js new file mode 100644 index 00000000..bffd41d3 --- /dev/null +++ b/src/shared/constants/whatsNewReleases.js @@ -0,0 +1,66 @@ +const whatsNewReleases = { + v2026_04_a: { + titleKey: 'onboarding.whatsnew.releases.v2026_04_a.title', + releaseLabel: '2026.04', + items: [ + { + key: 'quick_search', + icon: 'search', + titleKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.quick_search.title', + descriptionKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.quick_search.description' + }, + { + key: 'dashboard', + icon: 'layout-dashboard', + titleKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.dashboard.title', + descriptionKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.dashboard.description' + }, + { + key: 'activity_insights', + icon: 'activity', + titleKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.activity_insights.title', + descriptionKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.activity_insights.description' + }, + { + key: 'my_avatars', + icon: 'images', + titleKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.my_avatars.title', + descriptionKey: + 'onboarding.whatsnew.releases.v2026_04_a.items.my_avatars.description' + } + ] + } +}; + +/** + * @param {string} version + * @returns {string} + */ +function getWhatsNewReleaseKey(version) { + const match = String(version || '').match(/(\d{4})\.(\d{2})(?:\.\d{2})?/); + if (!match) { + return ''; + } + return `v${match[1]}_${match[2]}_a`; +} + +/** + * @param {string} version + * @returns {{titleKey: string, releaseLabel?: string, items: Array<{key: string, icon: string, titleKey: string, descriptionKey: string}>} | null} + */ +function getWhatsNewRelease(version) { + const releaseKey = String(version || ''); + if (!releaseKey) { + return null; + } + return whatsNewReleases[releaseKey] ?? null; +} + +export { getWhatsNewRelease, getWhatsNewReleaseKey, whatsNewReleases }; diff --git a/src/stores/vrcxUpdater.js b/src/stores/vrcxUpdater.js index 5e0a46c6..e1ed2f25 100644 --- a/src/stores/vrcxUpdater.js +++ b/src/stores/vrcxUpdater.js @@ -5,6 +5,10 @@ import { useI18n } from 'vue-i18n'; import { logWebRequest } from '../services/appConfig'; import { branches } from '../shared/constants'; +import { + getWhatsNewRelease, + getWhatsNewReleaseKey +} from '../shared/constants/whatsNewReleases'; import { changeLogRemoveLinks } from '../shared/utils'; import configRepository from '../services/config'; @@ -36,6 +40,12 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { buildName: '', changeLog: '' }); + const whatsNewDialog = ref({ + visible: false, + version: '', + releaseKey: '', + items: [] + }); const pendingVRCXUpdate = ref(false); const pendingVRCXInstall = ref(''); const updateInProgress = ref(false); @@ -73,7 +83,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { await loadVrcxId(); if (await compareAppVersion()) { - showChangeLogDialog(); + await showWhatsNewDialog(); } if (autoUpdateVRCX.value !== 'Off') { await checkForVRCXUpdate(); @@ -134,6 +144,56 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { } return false; } + + /** + * @returns {string} + */ + function getCurrentWhatsNewReleaseKey() { + return getWhatsNewReleaseKey(currentVersion.value); + } + + /** + * @returns {Promise} + */ + async function showWhatsNewDialog() { + const releaseKey = getCurrentWhatsNewReleaseKey(); + const release = getWhatsNewRelease(releaseKey); + + if (!release) { + whatsNewDialog.value = { + visible: false, + version: '', + releaseKey: '', + items: [] + }; + return false; + } + + const displayVersion = currentVersion.value.replace(/^VRCX\s+/, ''); + + whatsNewDialog.value = { + visible: true, + version: release.releaseLabel || displayVersion, + releaseKey, + items: release.items.map((item) => ({ ...item })) + }; + + return true; + } + + function closeWhatsNewDialog() { + whatsNewDialog.value.visible = false; + } + + async function openChangeLogDialogOnly() { + changeLogDialog.value.visible = true; + if ( + !changeLogDialog.value.buildName || + !changeLogDialog.value.changeLog + ) { + await checkForVRCXUpdate(); + } + } async function loadVrcxId() { if (!vrcxId.value) { vrcxId.value = crypto.randomUUID(); @@ -416,6 +476,7 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { checkingForVRCXUpdate, VRCXUpdateDialog, changeLogDialog, + whatsNewDialog, pendingVRCXUpdate, pendingVRCXInstall, updateInProgress, @@ -426,6 +487,9 @@ export const useVRCXUpdaterStore = defineStore('VRCXUpdater', () => { setBranch, compareAppVersion, + showWhatsNewDialog, + closeWhatsNewDialog, + openChangeLogDialogOnly, checkForVRCXUpdate, loadBranchVersions, installVRCXUpdate, diff --git a/src/views/Layout/MainLayout.vue b/src/views/Layout/MainLayout.vue index b3c30c16..4f7cc4cc 100644 --- a/src/views/Layout/MainLayout.vue +++ b/src/views/Layout/MainLayout.vue @@ -83,9 +83,11 @@ + + - + @@ -120,6 +122,7 @@ import StatusBar from '../../components/StatusBar.vue'; import VRChatConfigDialog from '../Settings/dialogs/VRChatConfigDialog.vue'; import WorldImportDialog from '../Favorites/dialogs/WorldImportDialog.vue'; + import WhatsNewDialog from '../../components/onboarding/WhatsNewDialog.vue'; import SpotlightDialog from '../../components/onboarding/SpotlightDialog.vue'; const router = useRouter();