@vaadin/hilla-file-router 24.4.0-beta5 → 24.5.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/runtime/createMenuItems.d.ts +12 -1
- package/runtime/createMenuItems.d.ts.map +1 -1
- package/runtime/createMenuItems.js +7 -13
- package/runtime/createMenuItems.js.map +2 -2
- package/shared/internal.d.ts +1 -6
- package/vite-plugin/createViewConfigJson.js +1 -1
- package/vite-plugin/createViewConfigJson.js.map +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/hilla-file-router",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.5.0-alpha1",
|
|
4
4
|
"description": "Hilla file-based router",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.js",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"type-fest": "^4.9.0"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@vaadin/hilla-generator-utils": "24.
|
|
83
|
-
"@vaadin/hilla-react-auth": "24.
|
|
82
|
+
"@vaadin/hilla-generator-utils": "24.5.0-alpha1",
|
|
83
|
+
"@vaadin/hilla-react-auth": "24.5.0-alpha1",
|
|
84
84
|
"react": "^18.2.0",
|
|
85
85
|
"rollup": "^4.12.0",
|
|
86
86
|
"typescript": "5.3.2"
|
|
@@ -9,6 +9,17 @@ import type { MenuItem } from '../types.js';
|
|
|
9
9
|
* @returns A list of menu items.
|
|
10
10
|
*/
|
|
11
11
|
export declare function createMenuItems(vaadinObject?: Readonly<{
|
|
12
|
-
views: Readonly<Record<string,
|
|
12
|
+
views: Readonly<Record<string, Readonly<{
|
|
13
|
+
title?: string | undefined;
|
|
14
|
+
rolesAllowed?: readonly [string, ...string[]] | undefined;
|
|
15
|
+
loginRequired?: boolean | undefined;
|
|
16
|
+
route?: string | undefined;
|
|
17
|
+
menu?: Readonly<{
|
|
18
|
+
title?: string | undefined;
|
|
19
|
+
order?: number | undefined;
|
|
20
|
+
exclude?: boolean | undefined;
|
|
21
|
+
icon?: string | undefined;
|
|
22
|
+
}> | undefined;
|
|
23
|
+
}>>>;
|
|
13
24
|
}> | undefined): readonly MenuItem[];
|
|
14
25
|
//# sourceMappingURL=createMenuItems.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createMenuItems.d.ts","sourceRoot":"","sources":["../src/runtime/createMenuItems.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,aAAa,CAAC;AAExD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,YAAY;;;;;;;;;;;;;cAAkC,GAAG,SAAS,QAAQ,EAAE,CAsBnG"}
|
|
@@ -2,24 +2,18 @@ function __REGISTER__(feature, vaadinObj = window.Vaadin ??= {}) {
|
|
|
2
2
|
vaadinObj.registrations ??= [];
|
|
3
3
|
vaadinObj.registrations.push({
|
|
4
4
|
is: feature ? `${"@vaadin/hilla-file-router"}/${feature}` : "@vaadin/hilla-file-router",
|
|
5
|
-
version: "24.
|
|
5
|
+
version: "24.5.0-alpha1"
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
import { RouteParamType } from "../shared/routeParamType.js";
|
|
9
8
|
function createMenuItems(vaadinObject = window.Vaadin) {
|
|
10
9
|
__REGISTER__("createMenuItems", vaadinObject);
|
|
11
10
|
const collator = new Intl.Collator("en-US");
|
|
12
|
-
return vaadinObject?.views ? Object.entries(vaadinObject.views).filter(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
icon: config.menu?.icon,
|
|
19
|
-
title: config.menu?.title ?? config.title,
|
|
20
|
-
order: config.menu?.order
|
|
21
|
-
};
|
|
22
|
-
}).sort((menuA, menuB) => {
|
|
11
|
+
return vaadinObject?.views ? Object.entries(vaadinObject.views).filter(([_, value]) => !value.menu?.exclude).map(([path, config]) => ({
|
|
12
|
+
to: path,
|
|
13
|
+
icon: config.menu?.icon,
|
|
14
|
+
title: config.menu?.title ?? config.title,
|
|
15
|
+
order: config.menu?.order
|
|
16
|
+
})).sort((menuA, menuB) => {
|
|
23
17
|
const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);
|
|
24
18
|
return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);
|
|
25
19
|
}) : [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../scripts/register.js", "../src/runtime/createMenuItems.ts"],
|
|
4
|
-
"sourcesContent": ["export function __REGISTER__(feature, vaadinObj = (window.Vaadin ??= {})) {\n vaadinObj.registrations ??= [];\n vaadinObj.registrations.push({\n is: feature ? `${__NAME__}/${feature}` : __NAME__,\n version: __VERSION__,\n });\n}\n", "import type { VaadinWindow } from '../shared/internal.js';\nimport
|
|
5
|
-
"mappings": "AAAO,SAAS,aAAa,SAAS,YAAa,OAAO,WAAW,CAAC,GAAI;AACxE,YAAU,kBAAkB,CAAC;AAC7B,YAAU,cAAc,KAAK;AAAA,IAC3B,IAAI,UAAU,GAAG,2BAAQ,IAAI,OAAO,KAAK;AAAA,IACzC,SAAS;AAAA,EACX,CAAC;AACH;
|
|
4
|
+
"sourcesContent": ["export function __REGISTER__(feature, vaadinObj = (window.Vaadin ??= {})) {\n vaadinObj.registrations ??= [];\n vaadinObj.registrations.push({\n is: feature ? `${__NAME__}/${feature}` : __NAME__,\n version: __VERSION__,\n });\n}\n", "import type { VaadinWindow } from '../shared/internal.js';\nimport type { MenuItem, ViewConfig } from '../types.js';\n\n/**\n * Creates menu items from the views provided by the server. The views are sorted according to the\n * {@link ViewConfig.menu.order}, filtered out if they are explicitly excluded via {@link ViewConfig.menu.exclude}.\n * Note that views with no order are put below views with an order. Ties are resolved based on the path string\n * comparison.\n *\n * @param vaadinObject - The Vaadin object containing the server views.\n * @returns A list of menu items.\n */\nexport function createMenuItems(vaadinObject = (window as VaadinWindow).Vaadin): readonly MenuItem[] {\n // @ts-expect-error: esbuild injection\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call\n __REGISTER__('createMenuItems', vaadinObject);\n const collator = new Intl.Collator('en-US');\n return vaadinObject?.views\n ? Object.entries(vaadinObject.views)\n // Filter out the views that are explicitly excluded from the menu.\n .filter(([_, value]) => !value.menu?.exclude)\n // Map the views to menu items.\n .map(([path, config]) => ({\n to: path,\n icon: config.menu?.icon,\n title: config.menu?.title ?? config.title,\n order: config.menu?.order,\n }))\n // Sort views according to the order specified in the view configuration.\n .sort((menuA, menuB) => {\n const ordersDiff = (menuA.order ?? Number.MAX_VALUE) - (menuB.order ?? Number.MAX_VALUE);\n return ordersDiff !== 0 ? ordersDiff : collator.compare(menuA.to, menuB.to);\n })\n : [];\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,aAAa,SAAS,YAAa,OAAO,WAAW,CAAC,GAAI;AACxE,YAAU,kBAAkB,CAAC;AAC7B,YAAU,cAAc,KAAK;AAAA,IAC3B,IAAI,UAAU,GAAG,2BAAQ,IAAI,OAAO,KAAK;AAAA,IACzC,SAAS;AAAA,EACX,CAAC;AACH;ACMO,SAAS,gBAAgB,eAAgB,OAAwB,QAA6B;AAGnG,eAAa,mBAAmB,YAAY;AAC5C,QAAM,WAAW,IAAI,KAAK,SAAS,OAAO;AAC1C,SAAO,cAAc,QACjB,OAAO,QAAQ,aAAa,KAAK,EAE9B,OAAO,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,MAAM,MAAM,OAAO,EAE3C,IAAI,CAAC,CAAC,MAAM,MAAM,OAAO;AAAA,IACxB,IAAI;AAAA,IACJ,MAAM,OAAO,MAAM;AAAA,IACnB,OAAO,OAAO,MAAM,SAAS,OAAO;AAAA,IACpC,OAAO,OAAO,MAAM;AAAA,EACtB,EAAE,EAED,KAAK,CAAC,OAAO,UAAU;AACtB,UAAM,cAAc,MAAM,SAAS,OAAO,cAAc,MAAM,SAAS,OAAO;AAC9E,WAAO,eAAe,IAAI,aAAa,SAAS,QAAQ,MAAM,IAAI,MAAM,EAAE;AAAA,EAC5E,CAAC,IACH,CAAC;AACP;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/shared/internal.d.ts
CHANGED
|
@@ -11,13 +11,8 @@ export type ServerViewConfig = Readonly<{
|
|
|
11
11
|
}> &
|
|
12
12
|
ViewConfig;
|
|
13
13
|
|
|
14
|
-
export type ServerViewMapItem = Readonly<{
|
|
15
|
-
params?: Readonly<Record<string, RouteParamType>>;
|
|
16
|
-
}> &
|
|
17
|
-
ViewConfig;
|
|
18
|
-
|
|
19
14
|
export type VaadinObject = Readonly<{
|
|
20
|
-
views: Readonly<Record<string,
|
|
15
|
+
views: Readonly<Record<string, ViewConfig>>;
|
|
21
16
|
}>;
|
|
22
17
|
|
|
23
18
|
export type VaadinWindow = Readonly<{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/vite-plugin/createViewConfigJson.ts"],
|
|
4
|
-
"sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: readonly RouteMeta[]): Promise<string> {\n const res = await transformTree<readonly RouteMeta[], Promise<readonly ServerViewConfig[]>>(\n views,\n async (routes, next) =>\n await Promise.all(\n routes.map(async ({ path, file, layout, children }) => {\n const newChildren = children ? await next(...children) : undefined;\n\n if (!file && !layout) {\n return {\n route: path,\n params: extractParameterFromRouteSegment(path),\n children: newChildren,\n } satisfies ServerViewConfig;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n break;\n }\n }\n\n let title: string;\n\n if (config?.title) {\n ({ title } = config);\n } else {\n if (!componentName) {\n throw new Error(\n `The file \"${String(file ?? layout!)}\" must contain a default export of a component whose name will be used as title by default`,\n );\n }\n\n title = convertComponentNameToTitle(componentName);\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config?.route ?? path),\n title,\n children: newChildren,\n } satisfies ServerViewConfig;\n }),\n ),\n );\n\n return JSON.stringify(res);\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,OAAO,YAAuB;AAC9B,SAAS,mCAAmC;AAE5C,SAAS,qBAAqB;AAG9B,SAAS,yCAAyC,wCAAwC;AAO1F,UAAU,QAAQ,MAA6B;AAC7C,QAAM;AAEN,aAAW,SAAS,KAAK,YAAY,GAAG;AACtC,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAQA,eAAO,qBAA4C,OAA8C;AAC/F,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,OAAO,QAAQ,SACb,MAAM,QAAQ;AAAA,MACZ,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,QAAQ,SAAS,MAAM;AACrD,cAAM,cAAc,WAAW,MAAM,KAAK,GAAG,QAAQ,IAAI;AAEzD,YAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,iBAAO;AAAA,YACL,OAAO;AAAA,YACP,QAAQ,iCAAiC,IAAI;AAAA,YAC7C,UAAU;AAAA,UACZ;AAAA,QACF;AAEA,cAAM,aAAa,GAAG;AAAA,UACpB;AAAA,UACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,UACtC,GAAG,aAAa;AAAA,UAChB;AAAA,QACF;AACA,YAAI;AACJ,YAAI,uBAAuB;AAC3B,YAAI;AAEJ,mBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,cAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,gBAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,oBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,oBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,uBAAS,OAAO,iBAAiB;AAAA,YACnC;AAAA,UACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,mCAAuB;AAAA,UACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,4BAAgB,KAAK;AACrB;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AAEJ,YAAI,QAAQ,OAAO;AACjB,WAAC,EAAE,MAAM,IAAI;AAAA,QACf,OAAO;AACL,cAAI,CAAC,eAAe;AAClB,kBAAM,IAAI;AAAA,cACR,aAAa,OAAO,QAAQ,MAAO,CAAC;AAAA,YACtC;AAAA,UACF;AAEA,kBAAQ,4BAA4B,aAAa;AAAA,QACnD;AAEA,eAAO;AAAA,UACL,OAAO,wCAAwC,IAAI;AAAA,UACnD,GAAG;AAAA,UACH,QAAQ,iCAAiC,QAAQ,SAAS,IAAI;AAAA,UAC9D;AAAA,UACA,UAAU;AAAA,
|
|
4
|
+
"sourcesContent": ["import { readFile } from 'node:fs/promises';\nimport { Script } from 'node:vm';\nimport ts, { type Node } from 'typescript';\nimport { convertComponentNameToTitle } from '../shared/convertComponentNameToTitle.js';\nimport type { ServerViewConfig } from '../shared/internal.js';\nimport { transformTree } from '../shared/transformTree.js';\nimport type { ViewConfig } from '../types.js';\nimport type { RouteMeta } from './collectRoutesFromFS.js';\nimport { convertFSRouteSegmentToURLPatternFormat, extractParameterFromRouteSegment } from './utils.js';\n\n/**\n * Walks the TypeScript AST using the deep-first search algorithm.\n *\n * @param node - The node to walk.\n */\nfunction* walkAST(node: Node): Generator<Node> {\n yield node;\n\n for (const child of node.getChildren()) {\n yield* walkAST(child);\n }\n}\n\n/**\n * Creates a map of all leaf routes to their configuration. This file is used by the server to provide server-side\n * routes along with managing the client-side routes.\n *\n * @param views - The route metadata tree.\n */\nexport default async function createViewConfigJson(views: readonly RouteMeta[]): Promise<string> {\n const res = await transformTree<readonly RouteMeta[], Promise<readonly ServerViewConfig[]>>(\n views,\n async (routes, next) =>\n await Promise.all(\n routes.map(async ({ path, file, layout, children }) => {\n const newChildren = children ? await next(...children) : undefined;\n\n if (!file && !layout) {\n return {\n route: path,\n params: extractParameterFromRouteSegment(path),\n children: newChildren,\n } satisfies ServerViewConfig;\n }\n\n const sourceFile = ts.createSourceFile(\n 'f.ts',\n await readFile(file ?? layout!, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n let config: ViewConfig | undefined;\n let waitingForIdentifier = false;\n let componentName: string | undefined;\n\n for (const node of walkAST(sourceFile)) {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === 'config') {\n if (node.initializer && ts.isObjectLiteralExpression(node.initializer)) {\n const code = node.initializer.getText(sourceFile);\n const script = new Script(`(${code})`);\n config = script.runInThisContext() as ViewConfig;\n }\n } else if (node.getText(sourceFile).startsWith('export default')) {\n waitingForIdentifier = true;\n } else if (waitingForIdentifier && ts.isIdentifier(node)) {\n componentName = node.text;\n break;\n }\n }\n\n let title: string;\n\n if (config?.title) {\n ({ title } = config);\n } else {\n if (!componentName) {\n throw new Error(\n `The file \"${String(file ?? layout!)}\" must contain a default export of a component whose name will be used as title by default`,\n );\n }\n\n title = convertComponentNameToTitle(componentName);\n }\n\n return {\n route: convertFSRouteSegmentToURLPatternFormat(path),\n ...config,\n params: extractParameterFromRouteSegment(config?.route ?? path),\n title,\n children: newChildren ?? (layout ? [] : undefined),\n } satisfies ServerViewConfig;\n }),\n ),\n );\n\n return JSON.stringify(res);\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,OAAO,YAAuB;AAC9B,SAAS,mCAAmC;AAE5C,SAAS,qBAAqB;AAG9B,SAAS,yCAAyC,wCAAwC;AAO1F,UAAU,QAAQ,MAA6B;AAC7C,QAAM;AAEN,aAAW,SAAS,KAAK,YAAY,GAAG;AACtC,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAQA,eAAO,qBAA4C,OAA8C;AAC/F,QAAM,MAAM,MAAM;AAAA,IAChB;AAAA,IACA,OAAO,QAAQ,SACb,MAAM,QAAQ;AAAA,MACZ,OAAO,IAAI,OAAO,EAAE,MAAM,MAAM,QAAQ,SAAS,MAAM;AACrD,cAAM,cAAc,WAAW,MAAM,KAAK,GAAG,QAAQ,IAAI;AAEzD,YAAI,CAAC,QAAQ,CAAC,QAAQ;AACpB,iBAAO;AAAA,YACL,OAAO;AAAA,YACP,QAAQ,iCAAiC,IAAI;AAAA,YAC7C,UAAU;AAAA,UACZ;AAAA,QACF;AAEA,cAAM,aAAa,GAAG;AAAA,UACpB;AAAA,UACA,MAAM,SAAS,QAAQ,QAAS,MAAM;AAAA,UACtC,GAAG,aAAa;AAAA,UAChB;AAAA,QACF;AACA,YAAI;AACJ,YAAI,uBAAuB;AAC3B,YAAI;AAEJ,mBAAW,QAAQ,QAAQ,UAAU,GAAG;AACtC,cAAI,GAAG,sBAAsB,IAAI,KAAK,GAAG,aAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,UAAU;AAC/F,gBAAI,KAAK,eAAe,GAAG,0BAA0B,KAAK,WAAW,GAAG;AACtE,oBAAM,OAAO,KAAK,YAAY,QAAQ,UAAU;AAChD,oBAAM,SAAS,IAAI,OAAO,IAAI,IAAI,GAAG;AACrC,uBAAS,OAAO,iBAAiB;AAAA,YACnC;AAAA,UACF,WAAW,KAAK,QAAQ,UAAU,EAAE,WAAW,gBAAgB,GAAG;AAChE,mCAAuB;AAAA,UACzB,WAAW,wBAAwB,GAAG,aAAa,IAAI,GAAG;AACxD,4BAAgB,KAAK;AACrB;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AAEJ,YAAI,QAAQ,OAAO;AACjB,WAAC,EAAE,MAAM,IAAI;AAAA,QACf,OAAO;AACL,cAAI,CAAC,eAAe;AAClB,kBAAM,IAAI;AAAA,cACR,aAAa,OAAO,QAAQ,MAAO,CAAC;AAAA,YACtC;AAAA,UACF;AAEA,kBAAQ,4BAA4B,aAAa;AAAA,QACnD;AAEA,eAAO;AAAA,UACL,OAAO,wCAAwC,IAAI;AAAA,UACnD,GAAG;AAAA,UACH,QAAQ,iCAAiC,QAAQ,SAAS,IAAI;AAAA,UAC9D;AAAA,UACA,UAAU,gBAAgB,SAAS,CAAC,IAAI;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACJ;AAEA,SAAO,KAAK,UAAU,GAAG;AAC3B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|