From d0f8fbfadaf34203db07218804bb73538cbcc1cc Mon Sep 17 00:00:00 2001 From: pa Date: Sun, 15 Mar 2026 18:25:20 +0900 Subject: [PATCH] refactor useNavLayout --- .../nav-menu/composables/useNavLayout.js | 135 ++---------------- src/components/nav-menu/navActionUtils.js | 51 +++++++ src/components/nav-menu/navLayoutHelpers.js | 125 ++++++++++++++++ 3 files changed, 189 insertions(+), 122 deletions(-) create mode 100644 src/components/nav-menu/navActionUtils.js create mode 100644 src/components/nav-menu/navLayoutHelpers.js diff --git a/src/components/nav-menu/composables/useNavLayout.js b/src/components/nav-menu/composables/useNavLayout.js index 150e39ff..7d16c4de 100644 --- a/src/components/nav-menu/composables/useNavLayout.js +++ b/src/components/nav-menu/composables/useNavLayout.js @@ -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 = []) => { diff --git a/src/components/nav-menu/navActionUtils.js b/src/components/nav-menu/navActionUtils.js new file mode 100644 index 00000000..090fa100 --- /dev/null +++ b/src/components/nav-menu/navActionUtils.js @@ -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); + } +} diff --git a/src/components/nav-menu/navLayoutHelpers.js b/src/components/nav-menu/navLayoutHelpers.js new file mode 100644 index 00000000..1393adb6 --- /dev/null +++ b/src/components/nav-menu/navLayoutHelpers.js @@ -0,0 +1,125 @@ +import { isNavEntryActionable } from './navActionUtils'; + +const DEFAULT_FOLDER_ICON = 'ri-folder-line'; + +/** + * @param {Array} layout + * @returns {Set} + */ +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} 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} 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} navDefinitionMap + * @returns {string | null} + */ +export function findFirstNavKey(layout, navDefinitionMap) { + const entry = findFirstNavEntry(layout, navDefinitionMap); + return entry?.key || null; +}