diff --git a/src/components/NavMenu.vue b/src/components/NavMenu.vue index 0e40290f..30633de1 100644 --- a/src/components/NavMenu.vue +++ b/src/components/NavMenu.vue @@ -374,7 +374,14 @@ items: ['friend-log', 'friend-list', 'moderation'] }, { type: 'item', key: 'notification' }, - { type: 'item', key: 'charts' }, + { + type: 'folder', + id: 'default-folder-charts', + nameKey: 'nav_tooltip.charts', + name: t('nav_tooltip.charts'), + icon: 'ri-pie-chart-line', + items: ['charts-instance', 'charts-mutual'] + }, { type: 'item', key: 'tools' }, { type: 'item', key: 'direct-access' } ]; @@ -504,6 +511,7 @@ const sanitizeLayout = (layout) => { const usedKeys = new Set(); const normalized = []; + const chartsKeys = ['charts-instance', 'charts-mutual']; const appendItemEntry = (key, target = normalized) => { if (!key || usedKeys.has(key) || !navDefinitionMap.has(key)) { @@ -513,9 +521,31 @@ usedKeys.add(key); }; + const appendChartsFolder = (target = normalized) => { + if (chartsKeys.some((key) => usedKeys.has(key))) { + return; + } + if (!chartsKeys.every((key) => navDefinitionMap.has(key))) { + return; + } + chartsKeys.forEach((key) => usedKeys.add(key)); + target.push({ + type: 'folder', + id: 'default-folder-charts', + nameKey: 'nav_tooltip.charts', + name: t('nav_tooltip.charts'), + icon: 'ri-pie-chart-line', + items: [...chartsKeys] + }); + }; + if (Array.isArray(layout)) { layout.forEach((entry) => { if (entry?.type === 'item') { + if (entry.key === 'charts') { + appendChartsFolder(); + return; + } appendItemEntry(entry.key); return; } @@ -550,11 +580,17 @@ navDefinitions.forEach((item) => { if (!usedKeys.has(item.key)) { - normalized.push({ type: 'item', key: item.key }); - usedKeys.add(item.key); + if (chartsKeys.includes(item.key)) { + return; + } + appendItemEntry(item.key); } }); + if (!chartsKeys.some((key) => usedKeys.has(key))) { + appendChartsFolder(); + } + return normalized; }; @@ -670,7 +706,11 @@ console.error('Failed to load custom nav', error); } finally { const fallbackLayout = layoutData?.length ? layoutData : createDefaultNavLayout(); - navLayout.value = sanitizeLayout(fallbackLayout); + const sanitized = sanitizeLayout(fallbackLayout); + navLayout.value = sanitized; + if (layoutData?.length && JSON.stringify(sanitized) !== JSON.stringify(fallbackLayout)) { + await saveNavLayout(sanitized); + } navLayoutReady.value = true; navigateToFirstNavEntry(); } diff --git a/src/plugin/router.js b/src/plugin/router.js index 819f01df..b4d25ad3 100644 --- a/src/plugin/router.js +++ b/src/plugin/router.js @@ -2,8 +2,6 @@ import { createRouter, createWebHashHistory } from 'vue-router'; import { watchState } from './../service/watchState'; -import MainLayout from '../views/Layout/MainLayout.vue'; -import Charts from './../views/Charts/Charts.vue'; import FavoritesAvatar from './../views/Favorites/FavoritesAvatar.vue'; import FavoritesFriend from './../views/Favorites/FavoritesFriend.vue'; import FavoritesWorld from './../views/Favorites/FavoritesWorld.vue'; @@ -11,16 +9,17 @@ import Feed from './../views/Feed/Feed.vue'; import FriendList from './../views/FriendList/FriendList.vue'; import FriendLog from './../views/FriendLog/FriendLog.vue'; import FriendsLocations from './../views/FriendsLocations/FriendsLocations.vue'; +import Gallery from './../views/Tools/Gallery.vue'; import GameLog from './../views/GameLog/GameLog.vue'; import Login from './../views/Login/Login.vue'; +import MainLayout from '../views/Layout/MainLayout.vue'; import Moderation from './../views/Moderation/Moderation.vue'; import Notification from './../views/Notifications/Notification.vue'; import PlayerList from './../views/PlayerList/PlayerList.vue'; +import ScreenshotMetadata from './../views/Tools/ScreenshotMetadata.vue'; import Search from './../views/Search/Search.vue'; import Settings from './../views/Settings/Settings.vue'; import Tools from './../views/Tools/Tools.vue'; -import Gallery from './../views/Tools/Gallery.vue'; -import ScreenshotMetadata from './../views/Tools/ScreenshotMetadata.vue'; const routes = [ { @@ -82,7 +81,19 @@ const routes = [ { path: 'charts', name: 'charts', - component: Charts + redirect: { name: 'charts-instance' } + }, + { + path: 'charts/instance', + name: 'charts-instance', + component: () => + import('./../views/Charts/components/InstanceActivity.vue') + }, + { + path: 'charts/mutual', + name: 'charts-mutual', + component: () => + import('./../views/Charts/components/MutualFriends.vue') }, { path: 'tools', name: 'tools', component: Tools }, { diff --git a/src/shared/constants/ui.js b/src/shared/constants/ui.js index 4a82f94e..3616a039 100644 --- a/src/shared/constants/ui.js +++ b/src/shared/constants/ui.js @@ -84,11 +84,18 @@ const navDefinitions = [ routeName: 'notification' }, { - key: 'charts', - icon: 'ri-bar-chart-line', - tooltip: 'nav_tooltip.charts', - labelKey: 'nav_tooltip.charts', - routeName: 'charts' + key: 'charts-instance', + icon: 'ri-bar-chart-horizontal-line', + tooltip: 'view.charts.instance_activity.header', + labelKey: 'view.charts.instance_activity.header', + routeName: 'charts-instance' + }, + { + key: 'charts-mutual', + icon: 'ri-group-2-line', + tooltip: 'view.charts.mutual_friend.tab_label', + labelKey: 'view.charts.mutual_friend.tab_label', + routeName: 'charts-mutual' }, { key: 'tools', diff --git a/src/stores/charts.js b/src/stores/charts.js index 7fbe7f51..608c5cd9 100644 --- a/src/stores/charts.js +++ b/src/stores/charts.js @@ -1,4 +1,4 @@ -import { computed, reactive, ref, watch } from 'vue'; +import { computed, reactive, watch } from 'vue'; import { defineStore } from 'pinia'; import { toast } from 'vue-sonner'; import { useI18n } from 'vue-i18n'; @@ -16,7 +16,6 @@ export const useChartsStore = defineStore('Charts', () => { const { t } = useI18n(); - const activeTab = ref('instance'); const mutualGraphFetchState = reactive(createDefaultFetchState()); const mutualGraphStatus = reactive({ isFetching: false, @@ -94,7 +93,6 @@ export const useChartsStore = defineStore('Charts', () => { } return { - activeTab, mutualGraphFetchState, mutualGraphStatus, resetMutualGraphState diff --git a/src/stores/settings/appearance.js b/src/stores/settings/appearance.js index d596d4aa..3df47c68 100644 --- a/src/stores/settings/appearance.js +++ b/src/stores/settings/appearance.js @@ -97,11 +97,13 @@ export const useAppearanceSettingsStore = defineStore( const isNavCollapsed = ref(true); const isSideBarTabShow = computed(() => { const currentRouteName = router.currentRoute.value?.name; - return !( - currentRouteName === 'friends-locations' || - currentRouteName === 'friend-list' || - currentRouteName === 'charts' - ); + return ![ + 'friends-locations', + 'friend-list', + 'charts', + 'charts-instance', + 'charts-mutual' + ].includes(currentRouteName); }); const isDataTableStriped = ref(false); diff --git a/src/views/Charts/Charts.vue b/src/views/Charts/Charts.vue deleted file mode 100644 index c80b196d..00000000 --- a/src/views/Charts/Charts.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/src/views/Charts/components/InstanceActivity.vue b/src/views/Charts/components/InstanceActivity.vue index a635cb94..b305e918 100644 --- a/src/views/Charts/components/InstanceActivity.vue +++ b/src/views/Charts/components/InstanceActivity.vue @@ -1,165 +1,175 @@