From d611caaade9d3666b4ef26a07f3691622d046da0 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Sat, 21 Sep 2024 10:51:02 +0300 Subject: [PATCH] refactor: Make createMenuItems be based on a signal Fixes #2752 --- package-lock.json | 1 + packages/ts/file-router/package.json | 1 + .../src/runtime/createMenuItems.ts | 46 ++++++++++-------- .../test/runtime/createMenuItems.spec.ts | 47 +++++++++---------- .../file-router/test/runtime/vaadinGlobals.ts | 4 ++ 5 files changed, 55 insertions(+), 44 deletions(-) create mode 100644 packages/ts/file-router/test/runtime/vaadinGlobals.ts diff --git a/package-lock.json b/package-lock.json index e85ca40323..2894e08729 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17969,6 +17969,7 @@ "dependencies": { "@vaadin/hilla-generator-utils": "24.5.0-beta1", "@vaadin/hilla-react-auth": "24.5.0-beta1", + "@vaadin/hilla-react-signals": "24.5.0-beta1", "react": "^18.2.0", "rollup": "^4.12.0", "typescript": "5.6.2" diff --git a/packages/ts/file-router/package.json b/packages/ts/file-router/package.json index 244a74671f..94429aac90 100644 --- a/packages/ts/file-router/package.json +++ b/packages/ts/file-router/package.json @@ -81,6 +81,7 @@ "dependencies": { "@vaadin/hilla-generator-utils": "24.5.0-beta1", "@vaadin/hilla-react-auth": "24.5.0-beta1", + "@vaadin/hilla-react-signals": "24.5.0-beta1", "react": "^18.2.0", "rollup": "^4.12.0", "typescript": "5.6.2" diff --git a/packages/ts/file-router/src/runtime/createMenuItems.ts b/packages/ts/file-router/src/runtime/createMenuItems.ts index 782c65fa8b..b6b610c6af 100644 --- a/packages/ts/file-router/src/runtime/createMenuItems.ts +++ b/packages/ts/file-router/src/runtime/createMenuItems.ts @@ -1,35 +1,41 @@ +import { signal } from '@vaadin/hilla-react-signals'; import type { VaadinWindow } from '../shared/internal.js'; import type { MenuItem, ViewConfig } from '../types.js'; +export const viewsSignal = signal((window as VaadinWindow).Vaadin?.views); + /** * Creates menu items from the views provided by the server. The views are sorted according to the * {@link ViewConfig.menu.order}, filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}. * Note that views with no order are put below views with an order. Ties are resolved based on the path string * comparison. * - * @param vaadinObject - The Vaadin object containing the server views. * @returns A list of menu items. */ -export function createMenuItems(vaadinObject = (window as VaadinWindow).Vaadin): readonly MenuItem[] { +export function createMenuItems(): readonly MenuItem[] { // @ts-expect-error: esbuild injection // eslint-disable-next-line @typescript-eslint/no-unsafe-call - __REGISTER__('createMenuItems', vaadinObject); + __REGISTER__('createMenuItems', (window as VaadinWindow).Vaadin?.views); const collator = new Intl.Collator('en-US'); - return vaadinObject?.views - ? Object.entries(vaadinObject.views) - // Filter out the views that are explicitly excluded from the menu. - .filter(([_, value]) => !value.menu?.exclude) - // Map the views to menu items. - .map(([path, config]) => ({ - to: path, - icon: config.menu?.icon, - title: config.menu?.title ?? config.title, - order: config.menu?.order, - })) - // Sort views according to the order specified in the view configuration. - .sort((menuA, menuB) => { - const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE); - return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to); - }) - : []; + if (!viewsSignal.value) { + return []; + } + + return ( + Object.entries(viewsSignal.value) + // Filter out the views that are explicitly excluded from the menu. + .filter(([_, value]) => !value.menu?.exclude) + // Map the views to menu items. + .map(([path, config]) => ({ + to: path, + icon: config.menu?.icon, + title: config.menu?.title ?? config.title, + order: config.menu?.order, + })) + // Sort views according to the order specified in the view configuration. + .sort((menuA, menuB) => { + const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE); + return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to); + }) + ); } diff --git a/packages/ts/file-router/test/runtime/createMenuItems.spec.ts b/packages/ts/file-router/test/runtime/createMenuItems.spec.ts index c42483f2e4..80b8c57111 100644 --- a/packages/ts/file-router/test/runtime/createMenuItems.spec.ts +++ b/packages/ts/file-router/test/runtime/createMenuItems.spec.ts @@ -1,7 +1,8 @@ +import './vaadinGlobals.js'; // eslint-disable-line import/no-unassigned-import import { expect, use } from '@esm-bundle/chai'; import chaiLike from 'chai-like'; import type { Writable } from 'type-fest'; -import { createMenuItems } from '../../src/runtime/createMenuItems.js'; +import { createMenuItems, viewsSignal } from '../../src/runtime/createMenuItems.js'; import type { MenuItem } from '../../src/types.js'; use(chaiLike); @@ -25,17 +26,16 @@ function cleanup(items: Array>) { describe('@vaadin/hilla-file-router', () => { describe('createMenuItems', () => { it('should generate a set of menu items', () => { - const items = createMenuItems({ - views: { - '/about': { route: 'about', title: 'About' }, - '/profile/': { title: 'Profile' }, - '/profile/account/security/password': { title: 'Password' }, - '/profile/account/security/two-factor-auth': { title: 'Two Factor Auth' }, - '/profile/friends/list': { title: 'List' }, - '/test/empty': {}, - '/test/no-default-export': { title: 'No Default Export' }, - }, - }); + viewsSignal.value = { + '/about': { route: 'about', title: 'About' }, + '/profile/': { title: 'Profile' }, + '/profile/account/security/password': { title: 'Password' }, + '/profile/account/security/two-factor-auth': { title: 'Two Factor Auth' }, + '/profile/friends/list': { title: 'List' }, + '/test/empty': {}, + '/test/no-default-export': { title: 'No Default Export' }, + }; + const items = createMenuItems(); cleanup(items as Array>); const expected = [ @@ -73,18 +73,17 @@ describe('@vaadin/hilla-file-router', () => { }); it('should sort menu items by order then by natural string comparison based on path', () => { - const items = createMenuItems({ - views: { - '/a/b': { route: 'about', title: 'About' }, - '': { title: 'Profile' }, - '/profile/account/security/password': { title: 'Password', menu: { order: 20 } }, - '/profile/account/security/two-factor-auth': { title: 'Two Factor Auth', menu: { order: 20 } }, - '/b': { title: 'List' }, - '/': { title: 'Root' }, - '/test/empty': { title: 'empty', menu: { order: 5 } }, - '/test/no-default-export': { title: 'No Default Export', menu: { order: 10 } }, - }, - }); + viewsSignal.value = { + '/a/b': { route: 'about', title: 'About' }, + '': { title: 'Profile' }, + '/profile/account/security/password': { title: 'Password', menu: { order: 20 } }, + '/profile/account/security/two-factor-auth': { title: 'Two Factor Auth', menu: { order: 20 } }, + '/b': { title: 'List' }, + '/': { title: 'Root' }, + '/test/empty': { title: 'empty', menu: { order: 5 } }, + '/test/no-default-export': { title: 'No Default Export', menu: { order: 10 } }, + }; + const items = createMenuItems(); const cleanedUp = (items as Array>).map((item) => ({ to: item.to, title: item.title, diff --git a/packages/ts/file-router/test/runtime/vaadinGlobals.ts b/packages/ts/file-router/test/runtime/vaadinGlobals.ts new file mode 100644 index 0000000000..cc35adec85 --- /dev/null +++ b/packages/ts/file-router/test/runtime/vaadinGlobals.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +(global as any).window = { Vaadin: {} }; + +export {};