refactor custom nav dialog

This commit is contained in:
pa
2026-03-01 23:34:35 +09:00
parent a9d465017b
commit 865ae0ab05
7 changed files with 829 additions and 652 deletions

View File

@@ -302,8 +302,9 @@
<CustomNavDialog
v-model:visible="customNavDialogVisible"
:layout="navLayout"
@save="handleCustomNavSave"
@reset="handleCustomNavReset" />
:hidden-keys="navHiddenKeys"
:default-layout="defaultNavLayout"
@save="handleCustomNavSave" />
</template>
<script setup>
@@ -346,7 +347,6 @@
import {
useAppearanceSettingsStore,
useAuthStore,
useModalStore,
useSearchStore,
useUiStore,
useVRCXUpdaterStore
@@ -360,7 +360,6 @@
const { t, locale } = useI18n();
const router = useRouter();
const modalStore = useModalStore();
const createDefaultNavLayout = () => [
{ type: 'item', key: 'feed' },
@@ -399,7 +398,23 @@
];
const navDefinitionMap = new Map(navDefinitions.map((item) => [item.key, item]));
const DEFAULT_FOLDER_ICON = 'ri-menu-fold-line';
const DEFAULT_FOLDER_ICON = 'ri-folder-line';
const normalizeHiddenKeys = (hiddenKeys = []) => {
if (!Array.isArray(hiddenKeys)) {
return [];
}
const seen = new Set();
const normalized = [];
hiddenKeys.forEach((key) => {
if (!key || seen.has(key) || !navDefinitionMap.has(key)) {
return;
}
seen.add(key);
normalized.push(key);
});
return normalized;
};
const VRCXUpdaterStore = useVRCXUpdaterStore();
const { pendingVRCXUpdate, pendingVRCXInstall, appVersion } = storeToRefs(VRCXUpdaterStore);
@@ -434,14 +449,7 @@
if (entry.type === 'folder') {
const folderDefinitions = (entry.items || []).map((key) => navDefinitionMap.get(key)).filter(Boolean);
if (folderDefinitions.length < 2) {
folderDefinitions.forEach((definition) => {
items.push({
...definition,
index: definition.key,
titleIsCustom: false
});
});
if (folderDefinitions.length === 0) {
return;
}
@@ -518,10 +526,17 @@
}
);
const generateFolderId = () => `nav-folder-${dayjs().toISOString()}-${Math.random().toString().slice(2, 4)}`;
const generateFolderId = () => {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return `nav-folder-${crypto.randomUUID()}`;
}
return `nav-folder-${dayjs().toISOString()}-${Math.random().toString().slice(2, 4)}`;
};
const sanitizeLayout = (layout) => {
const sanitizeLayout = (layout, hiddenKeys = []) => {
const usedKeys = new Set();
const normalizedHiddenKeys = normalizeHiddenKeys(hiddenKeys);
const hiddenSet = new Set(normalizedHiddenKeys);
const normalized = [];
const chartsKeys = ['charts-instance', 'charts-mutual'];
@@ -572,7 +587,7 @@
usedKeys.add(key);
});
if (folderItems.length >= 2) {
if (folderItems.length >= 1) {
const folderNameKey = entry.nameKey || null;
const folderName = folderNameKey ? t(folderNameKey) : entry.name || '';
normalized.push({
@@ -583,15 +598,13 @@
icon: entry.icon || DEFAULT_FOLDER_ICON,
items: folderItems
});
} else {
folderItems.forEach((key) => appendItemEntry(key));
}
}
});
}
navDefinitions.forEach((item) => {
if (!usedKeys.has(item.key)) {
if (!usedKeys.has(item.key) && !hiddenSet.has(item.key)) {
if (chartsKeys.includes(item.key)) {
return;
}
@@ -599,7 +612,7 @@
}
});
if (!chartsKeys.some((key) => usedKeys.has(key))) {
if (!chartsKeys.some((key) => usedKeys.has(key)) && !chartsKeys.some((key) => hiddenSet.has(key))) {
appendChartsFolder();
}
@@ -659,13 +672,17 @@
};
const customNavDialogVisible = ref(false);
const navHiddenKeys = ref([]);
const defaultNavLayout = computed(() => sanitizeLayout(createDefaultNavLayout(), []));
const saveNavLayout = async (layout) => {
const saveNavLayout = async (layout, hiddenKeys = []) => {
const normalizedHiddenKeys = normalizeHiddenKeys(hiddenKeys);
try {
await configRepository.setString(
'VRCX_customNavMenuLayoutList',
JSON.stringify({
layout
layout,
hiddenKeys: normalizedHiddenKeys
})
);
} catch (error) {
@@ -677,33 +694,18 @@
customNavDialogVisible.value = true;
};
const handleCustomNavSave = async (layout) => {
const sanitized = sanitizeLayout(layout);
const handleCustomNavSave = async (layout, hiddenKeys = []) => {
const normalizedHiddenKeys = normalizeHiddenKeys(hiddenKeys);
const sanitized = sanitizeLayout(layout, normalizedHiddenKeys);
navLayout.value = sanitized;
await saveNavLayout(sanitized);
navHiddenKeys.value = normalizedHiddenKeys;
await saveNavLayout(sanitized, normalizedHiddenKeys);
customNavDialogVisible.value = false;
};
const handleCustomNavReset = () => {
modalStore
.confirm({
description: t('nav_menu.custom_nav.restore_default_confirm'),
title: t('confirm.title'),
confirmText: t('nav_menu.custom_nav.restore_default'),
cancelText: t('nav_menu.custom_nav.cancel')
})
.then(async ({ ok }) => {
if (!ok) return;
const defaults = sanitizeLayout(createDefaultNavLayout());
navLayout.value = defaults;
await saveNavLayout(defaults);
customNavDialogVisible.value = false;
})
.catch(() => {});
};
const loadNavMenuConfig = async () => {
let layoutData = null;
let hiddenKeysData = [];
try {
const storedValue = await configRepository.getString('VRCX_customNavMenuLayoutList');
if (storedValue) {
@@ -712,16 +714,23 @@
layoutData = parsed;
} else if (Array.isArray(parsed?.layout)) {
layoutData = parsed.layout;
hiddenKeysData = Array.isArray(parsed.hiddenKeys) ? parsed.hiddenKeys : [];
}
}
} catch (error) {
console.error('Failed to load custom nav', error);
} finally {
const normalizedHiddenKeys = normalizeHiddenKeys(hiddenKeysData);
const fallbackLayout = layoutData?.length ? layoutData : createDefaultNavLayout();
const sanitized = sanitizeLayout(fallbackLayout);
const sanitized = sanitizeLayout(fallbackLayout, normalizedHiddenKeys);
navLayout.value = sanitized;
if (layoutData?.length && JSON.stringify(sanitized) !== JSON.stringify(fallbackLayout)) {
await saveNavLayout(sanitized);
navHiddenKeys.value = normalizedHiddenKeys;
if (
layoutData?.length &&
(JSON.stringify(sanitized) !== JSON.stringify(fallbackLayout) ||
JSON.stringify(normalizedHiddenKeys) !== JSON.stringify(hiddenKeysData))
) {
await saveNavLayout(sanitized, normalizedHiddenKeys);
}
navLayoutReady.value = true;
navigateToFirstNavEntry();