refactor useNavLayout

This commit is contained in:
pa
2026-03-15 18:25:20 +09:00
parent cc08f29800
commit d0f8fbfada
3 changed files with 189 additions and 122 deletions

View File

@@ -7,10 +7,15 @@ import {
DASHBOARD_NAV_KEY_PREFIX,
navDefinitions
} from '../../../shared/constants';
import { triggerNavEntryAction } from '../navActionUtils';
import {
buildMenuItems,
collectLayoutKeys,
findFirstNavEntry,
findFirstNavKey
} from '../navLayoutHelpers';
import { normalizeHiddenKeys, sanitizeLayout } from '../navMenuUtils';
const DEFAULT_FOLDER_ICON = 'ri-folder-line';
export function useNavLayout({
t,
locale,
@@ -72,82 +77,16 @@ export function useNavLayout({
{ type: 'item', key: 'direct-access' }
];
const menuItems = computed(() => {
const items = [];
navLayout.value.forEach((entry) => {
if (entry.type === 'item') {
const definition = navDefinitionMap.value.get(entry.key);
if (!definition) {
return;
}
items.push({
...definition,
index: definition.key,
title: definition.tooltip || definition.labelKey,
titleIsCustom: Boolean(definition.isDashboard)
});
return;
}
if (entry.type === 'folder') {
const folderDefinitions = (entry.items || [])
.map((key) => navDefinitionMap.value.get(key))
.filter(Boolean);
if (folderDefinitions.length === 0) {
return;
}
const folderEntries = folderDefinitions.map((definition) => ({
label: definition.labelKey,
routeName: definition.routeName,
routeParams: definition.routeParams,
index: definition.key,
icon: definition.icon,
action: definition.action,
titleIsCustom: Boolean(definition.isDashboard)
}));
items.push({
index: entry.id,
icon: entry.icon || DEFAULT_FOLDER_ICON,
title:
entry.name?.trim() ||
t('nav_menu.custom_nav.folder_name_placeholder'),
titleIsCustom: true,
children: folderEntries
});
}
});
return items;
});
const menuItems = computed(() =>
buildMenuItems(navLayout.value, navDefinitionMap.value, t)
);
const getFirstNavEntryLocal = (layout) => {
for (const entry of layout) {
if (entry.type === 'item') {
const definition = navDefinitionMap.value.get(entry.key);
if (
definition?.routeName ||
definition?.action ||
definition?.path
) {
return definition;
}
}
if (entry.type === 'folder' && entry.items?.length) {
const definition = entry.items
.map((key) => navDefinitionMap.value.get(key))
.find((def) => def?.routeName || def?.action || def?.path);
if (definition) {
return definition;
}
}
}
return null;
return findFirstNavEntry(layout, navDefinitionMap.value);
};
const getFirstNavKeyLocal = (layout) => {
const entry = getFirstNavEntryLocal(layout);
return entry?.key || null;
return findFirstNavKey(layout, navDefinitionMap.value);
};
const activeMenuIndex = computed(() => {
@@ -182,27 +121,6 @@ export function useNavLayout({
return `nav-folder-${dayjs().toISOString()}-${Math.random().toString().slice(2, 4)}`;
};
const collectLayoutKeys = (layout) => {
const keys = new Set();
if (!Array.isArray(layout)) {
return keys;
}
layout.forEach((entry) => {
if (entry?.type === 'item' && entry.key) {
keys.add(entry.key);
return;
}
if (entry?.type === 'folder' && Array.isArray(entry.items)) {
entry.items.forEach((key) => {
if (key) {
keys.add(key);
}
});
}
});
return keys;
};
const getAppendDefinitions = (layout, hiddenKeys = []) => {
const keysInLayout = collectLayoutKeys(layout);
const hiddenSet = new Set(Array.isArray(hiddenKeys) ? hiddenKeys : []);
@@ -246,35 +164,8 @@ export function useNavLayout({
return sanitizeLayoutLocal(base, []);
});
const handleRouteChange = (routeName, routeParams = undefined) => {
if (!routeName) {
return;
}
if (routeParams) {
router.push({ name: routeName, params: routeParams });
return;
}
router.push({ name: routeName });
};
const triggerNavAction = (entry) => {
if (!entry) {
return;
}
if (entry.action === 'direct-access') {
directAccessPaste();
return;
}
if (entry.routeName) {
handleRouteChange(entry.routeName, entry.routeParams);
return;
}
if (entry.path) {
router.push(entry.path);
}
triggerNavEntryAction(entry, { router, directAccessPaste });
};
const saveNavLayout = async (layout, hiddenKeys = []) => {

View File

@@ -0,0 +1,51 @@
/**
* @param {object | null | undefined} entry
* @returns {boolean}
*/
export function isNavEntryActionable(entry) {
return Boolean(entry?.routeName || entry?.action || entry?.path);
}
/**
* @param {object} router
* @param {string} routeName
* @param {object | undefined} routeParams
*/
export function navigateToRoute(router, routeName, routeParams = undefined) {
if (!routeName) {
return;
}
if (routeParams) {
router.push({ name: routeName, params: routeParams });
return;
}
router.push({ name: routeName });
}
/**
* @param {object | null | undefined} entry
* @param {object} deps
* @param {object} deps.router
* @param {Function} deps.directAccessPaste
*/
export function triggerNavEntryAction(entry, { router, directAccessPaste }) {
if (!entry) {
return;
}
if (entry.action === 'direct-access') {
directAccessPaste();
return;
}
if (entry.routeName) {
navigateToRoute(router, entry.routeName, entry.routeParams);
return;
}
if (entry.path) {
router.push(entry.path);
}
}

View File

@@ -0,0 +1,125 @@
import { isNavEntryActionable } from './navActionUtils';
const DEFAULT_FOLDER_ICON = 'ri-folder-line';
/**
* @param {Array} layout
* @returns {Set<string>}
*/
export function collectLayoutKeys(layout) {
const keys = new Set();
if (!Array.isArray(layout)) {
return keys;
}
layout.forEach((entry) => {
if (entry?.type === 'item' && entry.key) {
keys.add(entry.key);
return;
}
if (entry?.type === 'folder' && Array.isArray(entry.items)) {
entry.items.forEach((key) => {
if (key) {
keys.add(key);
}
});
}
});
return keys;
}
/**
* @param {Array} layout
* @param {Map<string, any>} navDefinitionMap
* @param {Function} t
* @returns {Array}
*/
export function buildMenuItems(layout, navDefinitionMap, t) {
const items = [];
layout.forEach((entry) => {
if (entry.type === 'item') {
const definition = navDefinitionMap.get(entry.key);
if (!definition) {
return;
}
items.push({
...definition,
index: definition.key,
title: definition.tooltip || definition.labelKey,
titleIsCustom: Boolean(definition.isDashboard)
});
return;
}
if (entry.type === 'folder') {
const folderDefinitions = (entry.items || [])
.map((key) => navDefinitionMap.get(key))
.filter(Boolean);
if (folderDefinitions.length === 0) {
return;
}
items.push({
index: entry.id,
icon: entry.icon || DEFAULT_FOLDER_ICON,
title:
entry.name?.trim() ||
t('nav_menu.custom_nav.folder_name_placeholder'),
titleIsCustom: true,
children: folderDefinitions.map((definition) => ({
label: definition.labelKey,
routeName: definition.routeName,
routeParams: definition.routeParams,
index: definition.key,
icon: definition.icon,
action: definition.action,
path: definition.path,
titleIsCustom: Boolean(definition.isDashboard)
}))
});
}
});
return items;
}
/**
* @param {Array} layout
* @param {Map<string, any>} navDefinitionMap
* @returns {object | null}
*/
export function findFirstNavEntry(layout, navDefinitionMap) {
for (const entry of layout) {
if (entry.type === 'item') {
const definition = navDefinitionMap.get(entry.key);
if (isNavEntryActionable(definition)) {
return definition;
}
}
if (entry.type === 'folder' && entry.items?.length) {
const definition = entry.items
.map((key) => navDefinitionMap.get(key))
.find((def) => isNavEntryActionable(def));
if (definition) {
return definition;
}
}
}
return null;
}
/**
* @param {Array} layout
* @param {Map<string, any>} navDefinitionMap
* @returns {string | null}
*/
export function findFirstNavKey(layout, navDefinitionMap) {
const entry = findFirstNavEntry(layout, navDefinitionMap);
return entry?.key || null;
}