mirror of
https://github.com/vrcx-team/VRCX.git
synced 2026-04-06 00:32:02 +02:00
feat: add notification layout setting
This commit is contained in:
@@ -162,6 +162,7 @@
|
||||
import { DragDropProvider } from '@dnd-kit/vue';
|
||||
import { isSortable } from '@dnd-kit/vue/sortable';
|
||||
import { openExternalLink } from '@/shared/utils/common';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
@@ -172,7 +173,7 @@
|
||||
import { isToolNavKey } from '../../shared/constants';
|
||||
import { navDefinitions } from '../../shared/constants/ui.js';
|
||||
import { DASHBOARD_NAV_KEY_PREFIX, DEFAULT_DASHBOARD_ICON } from '../../shared/constants/dashboard';
|
||||
import { useDashboardStore, useModalStore } from '../../stores';
|
||||
import { useDashboardStore, useModalStore, useNotificationsSettingsStore } from '../../stores';
|
||||
|
||||
import SortableTreeNode from './SortableTreeNode.vue';
|
||||
|
||||
@@ -207,6 +208,7 @@
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const modalStore = useModalStore();
|
||||
const { notificationLayout } = storeToRefs(useNotificationsSettingsStore());
|
||||
|
||||
const cloneLayout = (source) => {
|
||||
if (!Array.isArray(source)) return [];
|
||||
@@ -270,7 +272,12 @@
|
||||
const map = new Map();
|
||||
const source = props.definitions?.length ? props.definitions : navDefinitions;
|
||||
source.forEach((def) => {
|
||||
if (def?.key) map.set(def.key, def);
|
||||
if (def?.key) {
|
||||
if (def.key === 'notification' && notificationLayout.value === 'notification-center') {
|
||||
return;
|
||||
}
|
||||
map.set(def.key, def);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
} from '../navConfigUtils';
|
||||
import { normalizeHiddenKeys, sanitizeLayout } from '../navMenuUtils';
|
||||
|
||||
import { useNotificationsSettingsStore } from '../../../stores/settings/notifications';
|
||||
|
||||
export function useNavLayout({
|
||||
t,
|
||||
locale,
|
||||
@@ -42,15 +44,17 @@ export function useNavLayout({
|
||||
const navLayout = ref([]);
|
||||
const navLayoutReady = ref(false);
|
||||
const navHiddenKeys = ref([]);
|
||||
const notificationsSettingsStore = useNotificationsSettingsStore();
|
||||
|
||||
const allNavDefinitions = computed(() => [
|
||||
...navDefinitions,
|
||||
...dashboardStore.getDashboardNavDefinitions()
|
||||
]);
|
||||
|
||||
const navDefinitionMap = computed(() =>
|
||||
createNavDefinitionMap(allNavDefinitions.value)
|
||||
);
|
||||
const navDefinitionMap = computed(() => {
|
||||
const map = createNavDefinitionMap(allNavDefinitions.value);
|
||||
return map;
|
||||
});
|
||||
|
||||
// Tool nav items are add/remove only; they no longer participate in hidden state.
|
||||
const getDefaultHiddenKeys = (layout = []) => {
|
||||
@@ -60,9 +64,24 @@ export function useNavLayout({
|
||||
|
||||
const createDefaultNavLayout = () => createBaseDefaultNavLayout(t);
|
||||
|
||||
const menuItems = computed(() =>
|
||||
buildMenuItems(navLayout.value, navDefinitionMap.value, t)
|
||||
);
|
||||
const menuItems = computed(() => {
|
||||
const items = buildMenuItems(navLayout.value, navDefinitionMap.value, t);
|
||||
if (notificationsSettingsStore.notificationLayout === 'notification-center') {
|
||||
return items.filter((item) => {
|
||||
if (item.index === 'notification') {
|
||||
return false;
|
||||
}
|
||||
if (item.children) {
|
||||
item.children = item.children.filter(
|
||||
(child) => child.index !== 'notification'
|
||||
);
|
||||
return item.children.length > 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
const getFirstNavEntryLocal = (layout) => {
|
||||
return findFirstNavEntry(layout, navDefinitionMap.value);
|
||||
|
||||
@@ -950,6 +950,9 @@
|
||||
"notifications": {
|
||||
"notifications": {
|
||||
"header": "Notifications",
|
||||
"layout": "Notification Layout",
|
||||
"layout_notification_center": "Notification Center",
|
||||
"layout_table": "Table",
|
||||
"notification_filter": "Notification Filter",
|
||||
"test_notification": "Test Notification",
|
||||
"steamvr_notifications": {
|
||||
|
||||
@@ -119,6 +119,7 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
const notificationTTSTest = ref('');
|
||||
const notificationPosition = ref('topCenter');
|
||||
const notificationTimeout = ref(3000);
|
||||
const notificationLayout = ref('notification-center');
|
||||
|
||||
async function initNotificationsSettings() {
|
||||
const [
|
||||
@@ -136,7 +137,8 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
sharedFeedFiltersConfig,
|
||||
notificationTTSVoiceConfig,
|
||||
notificationPositionConfig,
|
||||
notificationTimeoutConfig
|
||||
notificationTimeoutConfig,
|
||||
notificationLayoutConfig
|
||||
] = await Promise.all([
|
||||
configRepository.getString('VRCX_overlayToast', 'Game Running'),
|
||||
configRepository.getBool('VRCX_overlayNotifications', true),
|
||||
@@ -158,7 +160,11 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
'VRCX_notificationPosition',
|
||||
'topCenter'
|
||||
),
|
||||
configRepository.getString('VRCX_notificationTimeout', '3000')
|
||||
configRepository.getString('VRCX_notificationTimeout', '3000'),
|
||||
configRepository.getString(
|
||||
'VRCX_notificationLayout',
|
||||
'notification-center'
|
||||
)
|
||||
]);
|
||||
|
||||
overlayToast.value = overlayToastConfig;
|
||||
@@ -177,6 +183,7 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
TTSvoices.value = speechSynthesis.getVoices();
|
||||
notificationPosition.value = notificationPositionConfig;
|
||||
notificationTimeout.value = Number(notificationTimeoutConfig);
|
||||
notificationLayout.value = notificationLayoutConfig;
|
||||
|
||||
initSharedFeedFilters();
|
||||
|
||||
@@ -424,6 +431,14 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
vrStore.updateVRConfigVars();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
function setNotificationLayout(value) {
|
||||
notificationLayout.value = value;
|
||||
configRepository.setString('VRCX_notificationLayout', value);
|
||||
}
|
||||
|
||||
function promptNotificationTimeout() {
|
||||
modalStore
|
||||
.prompt({
|
||||
@@ -470,6 +485,7 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
notificationTTSTest,
|
||||
notificationPosition,
|
||||
notificationTimeout,
|
||||
notificationLayout,
|
||||
|
||||
setOverlayToast,
|
||||
setOpenVR,
|
||||
@@ -488,6 +504,7 @@ export const useNotificationsSettingsStore = defineStore(
|
||||
testNotificationTTS,
|
||||
speak,
|
||||
changeNotificationPosition,
|
||||
setNotificationLayout,
|
||||
setNotificationTimeout,
|
||||
promptNotificationTimeout
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ import { showAvatarDialog } from '../coordinators/avatarCoordinator';
|
||||
import { showUserDialog } from '../coordinators/userCoordinator';
|
||||
import { useInstanceStore } from './instance';
|
||||
import { useNotificationStore } from './notification';
|
||||
import { useNotificationsSettingsStore } from './settings/notifications';
|
||||
import { useSearchStore } from './search';
|
||||
import { useUserStore } from './user';
|
||||
import { useWorldStore } from './world';
|
||||
@@ -286,6 +287,11 @@ export const useUiStore = defineStore('Ui', () => {
|
||||
const name = String(routeName);
|
||||
removeNotify(name);
|
||||
if (name === 'notification') {
|
||||
const notificationsSettingsStore = useNotificationsSettingsStore();
|
||||
if (notificationsSettingsStore.notificationLayout === 'notification-center') {
|
||||
router.replace({ name: 'feed' });
|
||||
return;
|
||||
}
|
||||
notificationStore.clearUnseenNotifications();
|
||||
}
|
||||
}
|
||||
@@ -314,10 +320,19 @@ export const useUiStore = defineStore('Ui', () => {
|
||||
}
|
||||
|
||||
function updateTrayIconNotify(force = false) {
|
||||
const newState =
|
||||
appearanceSettings.notificationIconDot &&
|
||||
(notifiedMenus.value.includes('notification') ||
|
||||
notifiedMenus.value.includes('friend-log'));
|
||||
const notificationsSettingsStore = useNotificationsSettingsStore();
|
||||
let newState;
|
||||
if (notificationsSettingsStore.notificationLayout === 'notification-center') {
|
||||
newState =
|
||||
appearanceSettings.notificationIconDot &&
|
||||
(notificationStore.hasUnseenNotifications ||
|
||||
notifiedMenus.value.includes('friend-log'));
|
||||
} else {
|
||||
newState =
|
||||
appearanceSettings.notificationIconDot &&
|
||||
(notifiedMenus.value.includes('notification') ||
|
||||
notifiedMenus.value.includes('friend-log'));
|
||||
}
|
||||
|
||||
if (trayIconNotify.value !== newState || force) {
|
||||
trayIconNotify.value = newState;
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-10 py-2">
|
||||
<SettingsGroup :title="t('view.settings.notifications.notifications.header')">
|
||||
<SettingsItem :label="t('view.settings.notifications.notifications.layout')">
|
||||
<Select
|
||||
:model-value="notificationLayout"
|
||||
@update:modelValue="setNotificationLayout">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="notification-center">{{
|
||||
t('view.settings.notifications.notifications.layout_notification_center')
|
||||
}}</SelectItem>
|
||||
<SelectItem value="table">{{
|
||||
t('view.settings.notifications.notifications.layout_table')
|
||||
}}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem :label="t('view.settings.notifications.notifications.notification_filter')">
|
||||
<Button size="sm" variant="outline" @click="showNotyFeedFiltersDialog">{{
|
||||
t('view.settings.notifications.notifications.notification_filter')
|
||||
@@ -163,7 +181,8 @@
|
||||
notificationTTSNickName,
|
||||
isTestTTSVisible,
|
||||
notificationTTSTest,
|
||||
TTSvoices
|
||||
TTSvoices,
|
||||
notificationLayout
|
||||
} = storeToRefs(notificationsSettingsStore);
|
||||
|
||||
const {
|
||||
@@ -173,7 +192,8 @@
|
||||
getTTSVoiceName,
|
||||
changeTTSVoice,
|
||||
saveNotificationTTS,
|
||||
testNotificationTTS
|
||||
testNotificationTTS,
|
||||
setNotificationLayout
|
||||
} = notificationsSettingsStore;
|
||||
|
||||
const { testNotification } = useNotificationStore();
|
||||
|
||||
@@ -26,35 +26,37 @@
|
||||
<RefreshCw v-else />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<ContextMenu v-if="hasUnseenNotifications">
|
||||
<ContextMenuTrigger as-child>
|
||||
<TooltipWrapper side="bottom" :content="t('side_panel.notification_center.title')">
|
||||
<Button
|
||||
class="rounded-full relative"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
@click="isNotificationCenterOpen = !isNotificationCenterOpen">
|
||||
<Bell />
|
||||
<span class="absolute top-1 right-1.25 size-1.5 rounded-full bg-red-500" />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem @click="markNotificationsRead">
|
||||
{{ t('nav_menu.mark_all_read') }}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
<TooltipWrapper v-else side="bottom" :content="t('side_panel.notification_center.title')">
|
||||
<Button
|
||||
class="rounded-full relative"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
@click="isNotificationCenterOpen = !isNotificationCenterOpen"
|
||||
@contextmenu.prevent="toast.info(t('side_panel.notification_center.no_unseen_notifications'))">
|
||||
<Bell />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<template v-if="notificationLayout !== 'table'">
|
||||
<ContextMenu v-if="hasUnseenNotifications">
|
||||
<ContextMenuTrigger as-child>
|
||||
<TooltipWrapper side="bottom" :content="t('side_panel.notification_center.title')">
|
||||
<Button
|
||||
class="rounded-full relative"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
@click="isNotificationCenterOpen = !isNotificationCenterOpen">
|
||||
<Bell />
|
||||
<span class="absolute top-1 right-1.25 size-1.5 rounded-full bg-red-500" />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem @click="markNotificationsRead">
|
||||
{{ t('nav_menu.mark_all_read') }}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
<TooltipWrapper v-else side="bottom" :content="t('side_panel.notification_center.title')">
|
||||
<Button
|
||||
class="rounded-full relative"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
@click="isNotificationCenterOpen = !isNotificationCenterOpen"
|
||||
@contextmenu.prevent="toast.info(t('side_panel.notification_center.no_unseen_notifications'))">
|
||||
<Bell />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
</template>
|
||||
<Popover v-model:open="isSettingsPopoverOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button class="rounded-full" variant="ghost" size="icon-sm">
|
||||
@@ -334,7 +336,8 @@
|
||||
useFavoriteStore,
|
||||
useFriendStore,
|
||||
useGroupStore,
|
||||
useNotificationStore
|
||||
useNotificationStore,
|
||||
useNotificationsSettingsStore
|
||||
} from '../../stores';
|
||||
import { runRefreshFriendsListFlow } from '../../coordinators/friendSyncCoordinator';
|
||||
import { normalizeFavoriteGroupsChange, resolveFavoriteGroups } from './sidebarSettingsUtils';
|
||||
@@ -350,6 +353,7 @@
|
||||
const { groupInstances } = storeToRefs(useGroupStore());
|
||||
const notificationStore = useNotificationStore();
|
||||
const { isNotificationCenterOpen, hasUnseenNotifications } = storeToRefs(notificationStore);
|
||||
const { notificationLayout } = storeToRefs(useNotificationsSettingsStore());
|
||||
const quickSearchStore = useQuickSearchStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user