From afb6e09ae03c49093851cacb4135ceefef233060 Mon Sep 17 00:00:00 2001 From: Waldemar Mazurek Date: Wed, 26 Jun 2024 15:53:26 +0200 Subject: [PATCH 1/3] Replaces JS with TS in client --- client/luigi-client.d.ts | 162 +- client/luigi-element.d.ts | 55 +- client/package-lock.json | 443 ++++-- client/package.json | 3 +- client/src/{baseClass.js => baseClass.ts} | 8 +- client/src/{helpers.js => helpers.ts} | 114 +- ...ifecycleManager.js => lifecycleManager.ts} | 284 ++-- client/src/{linkManager.js => linkManager.ts} | 305 ++-- client/src/luigi-client.js | 135 -- client/src/luigi-client.ts | 202 +++ .../{luigi-element.js => luigi-element.ts} | 344 +++-- ...{splitViewHandle.js => splitViewHandle.ts} | 114 +- .../{storageManager.js => storageManager.ts} | 84 +- client/src/{uxManager.js => uxManager.ts} | 158 +- client/tsconfig.json | 23 + client/webpack.config.js | 21 +- container/package.json | 10 +- docs/luigi-client-api.md | 1316 ----------------- scripts/package.json | 6 +- 19 files changed, 1597 insertions(+), 2190 deletions(-) rename client/src/{baseClass.js => baseClass.ts} (73%) rename client/src/{helpers.js => helpers.ts} (51%) rename client/src/{lifecycleManager.js => lifecycleManager.ts} (71%) rename client/src/{linkManager.js => linkManager.ts} (77%) delete mode 100644 client/src/luigi-client.js create mode 100644 client/src/luigi-client.ts rename client/src/{luigi-element.js => luigi-element.ts} (53%) rename client/src/{splitViewHandle.js => splitViewHandle.ts} (66%) rename client/src/{storageManager.js => storageManager.ts} (73%) rename client/src/{uxManager.js => uxManager.ts} (74%) create mode 100644 client/tsconfig.json diff --git a/client/luigi-client.d.ts b/client/luigi-client.d.ts index 16694e724c..b33f3034de 100644 --- a/client/luigi-client.d.ts +++ b/client/luigi-client.d.ts @@ -1,5 +1,4 @@ // Type definitions for Luigi Client - export as namespace LuigiClient; export declare interface AuthData { @@ -10,82 +9,84 @@ export declare interface AuthData { } export declare interface ConfirmationModalSettings { - type?: string; - header?: string; body?: string; buttonConfirm?: string | boolean; buttonDismiss?: string; + header?: string; + type?: string; } export declare interface ModalSettings { - title?: string; + closebtn_data_testid?: string; + height?: string; size?: 'fullscreen' | 'l' | 'm' | 's'; + title?: string; width?: string; - height?: string; - closebtn_data_testid?: string; } export declare interface SplitViewSettings { - title?: string; - size?: number; collapsed?: boolean; + size?: number; + title?: string; } -export enum SplitViewEvents { - 'expand', - 'collapse', - 'resize', - 'close' -} +export type SplitViewEvents = 'close' | 'collapse' | 'expand' | 'resize'; export declare interface SplitViewInstance { collapse: () => void; - expand: () => void; - setSize: (value: number) => void; - on: (key: SplitViewEvents, callback: () => void) => string; // exists: () => boolean; + expand: () => void; getSize: () => number; isCollapsed: () => boolean; isExpanded: () => boolean; + on: (key: SplitViewEvents, callback: () => void) => string; + setSize: (value: number) => void; } export declare interface DrawerSettings { - header?: any; - size?: 'l' | 'm' | 's' | 'xs'; backdrop?: boolean; + header?: any; overlap?: boolean; + size?: 'l' | 'm' | 's' | 'xs'; +} + +export declare interface NodeParams { + [key: string]: string; +} + +export declare interface PathParams { + [key: string]: string; } export declare interface Context { + parentNavigationContext?: string[]; + [key: string]: any; +} + +export declare interface InternalContext { + anchor?: string; authData?: AuthData; - context?: { parentNavigationContext?: string[] }; + context?: Context; internal?: { userSettings?: getUserSettings; + [key: string]: any; }; nodeParams?: NodeParams; pathParams?: PathParams; - anchor?: string; [key: string]: any; } -export declare interface NodeParams { - [key: string]: string; -} export declare interface ClientPermissions { [key: string]: any; } export declare interface AlertSettings { - text?: string; - type: 'info' | 'success' | 'warning' | 'error'; + closeAfter?: number; links?: { [key: string]: { text: string; url?: string; dismissKey?: string }; }; - closeAfter?: number; -} - -export declare interface PathParams { - [key: string]: string; + text?: string; + type: 'info' | 'success' | 'warning' | 'error'; } export declare interface CoreSearchParams { @@ -100,6 +101,21 @@ export declare interface UserSettings { [key: string]: number | string | boolean; } +export declare interface CustomMessageData { + customMessage: Record; + listenerId: string; +} + +export declare interface InternalMessageData { + data: any; + msg: string; +} + +export declare interface PostMessageData { + errorObj: Partial; + msg: string; +} + export declare interface UxManager { /** * Adds a backdrop to block the top and side navigation. It is based on the Fundamental UI Modal, which you can use in your micro frontend to achieve the same behavior. @@ -345,7 +361,12 @@ export declare interface LinkManager { * LuigiClient.linkManager().navigate('/settings', null, true) // preserve view * LuigiClient.linkManager().navigate('#?intent=Sales-order?id=13') // intent navigation */ - navigate: (path: string, sessionId?: string, preserveView?: boolean, modalSettings?: ModalSettings) => void; + navigate: ( + path: string, + sessionId?: string, + preserveView?: boolean, + modalSettings?: ModalSettings + ) => void; /** * Offers an alternative way of navigating with intents. This involves specifying a semanticSlug and an object containing @@ -426,7 +447,10 @@ export declare interface LinkManager { * console.log(res.data) //=> {foo: 'bar'} * }); */ - openAsModal: (nodepath: string, modalSettings?: ModalSettings) => Promise; + openAsModal: ( + nodepath: string, + modalSettings?: ModalSettings + ) => Promise; /** * Updates the current title and size of a modal. If `routing.showModalPathInUrl` is set to `true`, the URL will be updated with the modal settings data. @@ -441,7 +465,10 @@ export declare interface LinkManager { * @example * LuigiClient.linkManager().updateModalSettings({title:'LuigiModal', size:'l'}); */ - updateModalSettings: (updatedModalSettings: Object, addHistoryEntry?: boolean) => void; + updateModalSettings: ( + updatedModalSettings: Object, + addHistoryEntry?: boolean + ) => void; /** * Opens a view in a split view. You can specify the split view's title and size. If you don't specify the title, it is the node label. If there is no node label, the title remains empty. The default size of the split view is `40`, which means 40% height of the split view. @@ -457,7 +484,10 @@ export declare interface LinkManager { * @example * const splitViewHandle = LuigiClient.linkManager().openAsSplitView('projects/pr1/logs', {title: 'Logs', size: 40, collapsed: true}); */ - openAsSplitView: (path: string, splitViewSettings?: SplitViewSettings) => SplitViewInstance; + openAsSplitView: ( + path: string, + splitViewSettings?: SplitViewSettings + ) => SplitViewInstance; /** * Opens a view in a drawer. You can specify if the drawer has a header, if a backdrop is active in the background and configure the size of the drawer. By default the header is shown. The backdrop is not visible and has to be activated. The size of the drawer is by default set to `s` which means 25% of the micro frontend size. You can also use `l`(75%), `m`(50%) or `xs`(15.5%). Optionally, use it in combination with any of the navigation functions. @@ -491,7 +521,11 @@ export declare interface LinkManager { * @example * LuigiClient.linkManager().withoutSync().updateModalPathInternalNavigation('/projects/xy/foobar'); */ - updateModalPathInternalNavigation: (path: string, modalSettings?: Object, addHistoryEntry?: boolean) => void; + updateModalPathInternalNavigation: ( + path: string, + modalSettings?: Object, + addHistoryEntry?: boolean + ) => void; /** * Enables navigating to a new tab. @@ -587,7 +621,7 @@ export declare interface StorageManager { * @example * LuigiClient.storageManager().getAllKeys().then((keys) => console.log('keys are : '+keys)); */ - getAllKeys: () => Promise; + getAllKeys: () => Promise; } /** @@ -595,8 +629,12 @@ export declare interface StorageManager { * @param {Lifecycle~initListenerCallback} initFn the function that is called once Luigi is initialized, receives current context and origin as parameters * @memberof Lifecycle */ -export function addInitListener(initFn: (context: Context, origin?: string) => void): number; -export type addInitListener = (initFn: (context: Context, origin?: string) => void) => number; +export function addInitListener( + initFn: (context: Context, origin?: string) => void +): number; +export type addInitListener = ( + initFn: (context: Context, origin?: string) => void +) => number; /** * Callback of the addInitListener @@ -609,16 +647,20 @@ export type addInitListener = (initFn: (context: Context, origin?: string) => vo * @param {string} id the id that was returned by the `addInitListener` function. * @memberof Lifecycle */ -export function removeInitListener(id: number): boolean; -export type removeInitListener = (id: number) => boolean; +export function removeInitListener(id: string): boolean; +export type removeInitListener = (id: string) => boolean; /** * Registers a listener called with the context object when the URL is changed. For example, you can use this when changing environments in a context switcher in order for the micro frontend to do an API call to the environment picked. * @param {function} contextUpdatedFn the listener function called each time Luigi context changes * @memberof Lifecycle */ -export function addContextUpdateListener(contextUpdatedFn: (context: Context) => void): string; -export type addContextUpdateListener = (contextUpdatedFn: (context: Context) => void) => string; +export function addContextUpdateListener( + contextUpdatedFn: (context: Context) => void +): string; +export type addContextUpdateListener = ( + contextUpdatedFn: (context: Context) => void +) => string; /** * Removes a context update listener. @@ -705,6 +747,7 @@ export type getEventData = () => Context; */ export function getContext(): Context; export type getContext = () => Context; + /** * Sets node parameters in Luigi Core. The parameters will be added to the URL. * @param {Object} params @@ -713,8 +756,14 @@ export type getContext = () => Context; * LuigiClient.addNodeParams({luigi:'rocks'}); * LuigiClient.addNodeParams({luigi:'rocks', false}); */ -export function addNodeParams(params: NodeParams, keepBrowserHistory: Boolean): void; -export type addNodeParams = (params: NodeParams, keepBrowserHistory: Boolean) => void; +export function addNodeParams( + params: NodeParams, + keepBrowserHistory: boolean +): void; +export type addNodeParams = ( + params: NodeParams, + keepBrowserHistory: boolean +) => void; /** * Returns the node parameters of the active URL. @@ -735,8 +784,8 @@ export type getNodeParams = (shouldDesanitise?: boolean) => NodeParams; * @returns {Object} node parameters, where the object property name is the node parameter name without the prefix, and its value is the value of the node parameter. For example `{sort: 'asc', page: 3}` * @memberof Lifecycle */ -export function getActiveFeatureToggles(): Array; -export type getActiveFeatureToggles = () => Array; +export function getActiveFeatureToggles(): string[]; +export type getActiveFeatureToggles = () => string[]; /** * Returns the dynamic path parameters of the active URL. @@ -757,8 +806,8 @@ export type getPathParams = () => PathParams; * @example * LuigiClient.getAnchor(); */ -export function getAnchor(): String; -export type getAnchor = () => String; +export function getAnchor(): string; +export type getAnchor = () => string; /** * Sets the anchor of active URL. @@ -767,8 +816,8 @@ export type getAnchor = () => String; * @example * LuigiClient.setAnchor('luigi'); */ -export function setAnchor(anchor: String): void; -export type setAnchor = (anchor: String) => void; +export function setAnchor(anchor: string): void; +export type setAnchor = (anchor: string) => void; /** * Allows you to change node labels within the same {@link navigation-advanced.md#view-groups view group}, e.g. in your node config: `label: 'my Node {viewGroupData.vg1}'`. @@ -799,8 +848,14 @@ export type getCoreSearchParams = () => CoreSearchParams; * LuigiClient.addCoreSearchParams({luigi:'rocks'}); * LuigiClient.addCoreSearchParams({luigi:'rocks', false}); */ -export function addCoreSearchParams(searchParams: CoreSearchParams, keepBrowserHistory: Boolean): void; -export type addCoreSearchParams = (searchParams: CoreSearchParams, keepBrowserHistory: Boolean) => void; +export function addCoreSearchParams( + searchParams: CoreSearchParams, + keepBrowserHistory: boolean +): void; +export type addCoreSearchParams = ( + searchParams: CoreSearchParams, + keepBrowserHistory: boolean +) => void; /** * Returns the current client permissions as specified in the navigation node or an empty object. For details, see [Node parameters](navigation-parameters-reference.md). @@ -857,6 +912,7 @@ export type uxManager = () => UxManager; */ export function storageManager(): StorageManager; export type storageManager = () => StorageManager; + /** * Returns the current user settings. * @returns {Object} current user settings diff --git a/client/luigi-element.d.ts b/client/luigi-element.d.ts index 7a07e60d0f..f5ee468ad8 100644 --- a/client/luigi-element.d.ts +++ b/client/luigi-element.d.ts @@ -1,60 +1,55 @@ // Type definitions for Luigi Client web components export declare interface ConfirmationModalSettings { - type?: string; - header?: string; body?: string; buttonConfirm?: string | boolean; buttonDismiss?: string; + header?: string; + type?: string; } export declare interface ModalSettings { - title?: string; - size?: 'fullscreen' | 'l' | 'm' | 's'; - width?: string; + closebtn_data_testid?: string; height?: string; keepPrevious?: boolean; - closebtn_data_testid?: string; + size?: 'fullscreen' | 'l' | 'm' | 's'; + title?: string; + width?: string; } export declare interface SplitViewSettings { - title?: string; - size?: number; collapsed?: boolean; + size?: number; + title?: string; } -export declare enum SplitViewEvents { - 'expand', - 'collapse', - 'resize', - 'close' -} +export type SplitViewEvents = 'close' | 'collapse' | 'expand' | 'resize'; export declare interface SplitViewInstance { collapse: () => void; - expand: () => void; - setSize: (value: number) => void; - on: (key: SplitViewEvents, callback: () => void) => string; exists: () => boolean; + expand: () => void; getSize: () => number; isCollapsed: () => boolean; isExpanded: () => boolean; + on: (key: SplitViewEvents, callback: () => void) => string; + setSize: (value: number) => void; } export declare interface DrawerSettings { - header?: any; - size?: 'l' | 'm' | 's' | 'xs'; backdrop?: boolean; + header?: any; overlap?: boolean; + size?: 'l' | 'm' | 's' | 'xs'; } export declare interface AlertSettings { - text?: string; - type: 'info' | 'success' | 'warning' | 'error'; + closeAfter?: number; links?: { [key: string]: { text: string; url?: string; dismissKey?: string }; }; - closeAfter?: number; + text?: string; + type: 'info' | 'success' | 'warning' | 'error'; } export declare interface NodeParams { @@ -227,7 +222,11 @@ export declare interface LinkManager { * @example * LuigiClient.linkManager().openAsModal('projects/pr1/users', {title:'Users', size:'m'}); */ - openAsModal: (nodepath: string, modalSettings?: ModalSettings, onCloseCallback?: Function) => void; + openAsModal: ( + nodepath: string, + modalSettings?: ModalSettings, + onCloseCallback?: Function + ) => void; /** * Opens a view in a split view. You can specify the split view's title and size. If you don't specify the title, it is the node label. If there is no node label, the title remains empty. The default size of the split view is 40, which means 40% height of the split view. @@ -244,7 +243,10 @@ export declare interface LinkManager { * @example * LuigiClient.linkManager().openAsSplitView('projects/pr1/users', {title:'Users', size:'40'}); */ - openAsSplitView: (path: string, splitViewSettings?: SplitViewSettings) => SplitViewInstance; + openAsSplitView: ( + path: string, + splitViewSettings?: SplitViewSettings + ) => SplitViewInstance; /** * Opens a view in a drawer. You can specify if the drawer has a header, if a backdrop is active in the background and configure the size of the drawer. By default the header is shown. The backdrop is not visible and has to be activated. The size of the drawer is by default set to `s` which means 25% of the micro frontend size. You can also use `l`(75%), `m`(50%) or `xs`(15.5%). Optionally, use it in combination with any of the navigation functions. @@ -403,7 +405,10 @@ export declare interface Options { * @param {String} literal The literal to process. * @returns {String} Returns the processed literal. */ -export declare const html: (strings: TemplateStringsArray, ...keys: unknown[]) => string; +export declare const html: ( + strings: TemplateStringsArray, + ...keys: unknown[] +) => string; export interface LuigiClient { /** diff --git a/client/package-lock.json b/client/package-lock.json index d481d81c97..092740967e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,6 @@ "@babel/preset-env": "^7.2.3", "@babel/runtime": "^7.8.7", "acorn": "^8.8.1", - "babel-loader": "^8.0.5", "copy-webpack-plugin": "^5.1.1", "core-js": "^3.27.1", "diff": ">=3.5.0", @@ -22,6 +21,8 @@ "mixin-deep": ">=1.3.2", "serialize-javascript": ">=3.1.0", "set-value": ">=4.0.1", + "ts-loader": "^9.5.1", + "typescript": "^5.5.2", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" }, @@ -2100,25 +2101,6 @@ "node": ">=0.10.0" } }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", @@ -2189,6 +2171,18 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -2826,21 +2820,16 @@ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, "node_modules/find-up": { @@ -3114,6 +3103,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3254,20 +3252,6 @@ "node": ">=6.11.5" } }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -3301,27 +3285,25 @@ "yallist": "^3.0.2" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3560,6 +3542,18 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -3805,24 +3799,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -4076,12 +4052,148 @@ "node": ">=4" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -5926,18 +6038,6 @@ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true }, - "babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "requires": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - } - }, "babel-plugin-polyfill-corejs2": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", @@ -5996,6 +6096,15 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, "browserslist": { "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", @@ -6484,15 +6593,13 @@ "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" + "to-regex-range": "^5.0.1" } }, "find-up": { @@ -6717,6 +6824,12 @@ "is-extglob": "^2.1.1" } }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -6820,17 +6933,6 @@ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true }, - "loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -6861,21 +6963,22 @@ "yallist": "^3.0.2" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -7066,6 +7169,12 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -7278,17 +7387,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -7469,12 +7567,103 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true }, + "typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/client/package.json b/client/package.json index 5729141329..3e62f381a6 100644 --- a/client/package.json +++ b/client/package.json @@ -22,7 +22,6 @@ "@babel/preset-env": "^7.2.3", "@babel/runtime": "^7.8.7", "acorn": "^8.8.1", - "babel-loader": "^8.0.5", "copy-webpack-plugin": "^5.1.1", "core-js": "^3.27.1", "diff": ">=3.5.0", @@ -30,6 +29,8 @@ "mixin-deep": ">=1.3.2", "serialize-javascript": ">=3.1.0", "set-value": ">=4.0.1", + "ts-loader": "^9.5.1", + "typescript": "^5.5.2", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" }, diff --git a/client/src/baseClass.js b/client/src/baseClass.ts similarity index 73% rename from client/src/baseClass.js rename to client/src/baseClass.ts index 8b2527c3f8..a7674ac04d 100644 --- a/client/src/baseClass.js +++ b/client/src/baseClass.ts @@ -3,24 +3,28 @@ * @abstract */ export class LuigiClientBase { + private promises: Record; + /** * @private */ constructor() { this.promises = {}; } + /** * Returns the promises object * @private */ - setPromise(name, value) { + setPromise(name: string, value: any) { this.promises[name] = value; } + /** * Sets the promises object * @private */ - getPromise(name) { + getPromise(name: string): any { return this.promises[name]; } } diff --git a/client/src/helpers.js b/client/src/helpers.ts similarity index 51% rename from client/src/helpers.js rename to client/src/helpers.ts index 8599b140bd..9a6bf1e304 100644 --- a/client/src/helpers.js +++ b/client/src/helpers.ts @@ -1,41 +1,54 @@ +import { InternalMessageData } from '../luigi-client'; + /** @private */ class Helpers { + listeners: any[] = []; + origin: string = ''; + /** @private */ constructor() { - this.listeners = []; - this.origin = ''; - - const helperListener = function(evt) { + const helperListener = (evt: MessageEvent): void => { if (!evt.data.msg) { return; } + if (evt.data.msg === 'custom') { - const message = this.convertCustomMessageInternalToUser(evt.data); + const message: Record< + string, + any + > = this.convertCustomMessageInternalToUser(evt.data); + this.listeners - .filter(listener => listener.name === message.id) + .filter(listener => listener.name === message['id']) .map(listener => listener.eventFn(message, listener.listenerId)); } else { this.listeners .filter(listener => listener.name === evt.data.msg) .map(listener => listener.eventFn(evt, listener.listenerId)); } - }.bind(this); + }; window.addEventListener('message', helperListener); } - convertCustomMessageInternalToUser(internalMessage) { - return internalMessage.data; + convertCustomMessageInternalToUser( + internalMessage: InternalMessageData + ): Record { + return internalMessage.data as Record; } - convertCustomMessageUserToInternal(message) { + convertCustomMessageUserToInternal( + message: Record + ): InternalMessageData { return { msg: 'custom', data: message }; } - convertStorageMessageToInternal(message) { + convertStorageMessageToInternal( + message: Record + ): InternalMessageData { return { msg: 'storage', data: message @@ -45,87 +58,110 @@ class Helpers { /** * Registers a post message listener * Don't forget to remove the event listener at the end of - * your eventFn if you do not need it anymore. + * your eventFn if you do not need it anymore * @private * @param {string} name event name * @param {function} eventFn callback function - * @returns {string} listener id + * @returns {number} listener id */ - addEventListener(name, eventFn) { - const listenerId = this.getRandomId(); + addEventListener( + name: string, + eventFn: (event: any, listener?: any) => void + ): string { + const listenerId: number = this.getRandomId(); + this.listeners.push({ name, eventFn, listenerId }); - return listenerId; + + return `${listenerId}`; } /** * Removes a post message listener * @private * @param {string} id listenerId + * @returns {boolean} */ - removeEventListener(id) { - const listenerExists = Boolean(this.listeners.find(l => l.listenerId === id)); + removeEventListener(id: string): boolean { + const listenerExists: boolean = this.listeners.find( + listener => listener.listenerId === id + ); + if (listenerExists) { - this.listeners = this.listeners.filter(l => l.listenerId !== id); + this.listeners = this.listeners.filter( + listener => listener.listenerId !== id + ); + return true; } + return false; } /** - * Creates a random Id + * Creates a random ID * @private + * @returns {number} */ - getRandomId() { + getRandomId(): number { // window.msCrypto for IE 11 - return (window.crypto || window.msCrypto).getRandomValues(new Uint32Array(1))[0]; + return (window.crypto || (window as any).msCrypto).getRandomValues( + new Uint32Array(1) + )[0]; } /** - * Simple function check. + * Simple function check * @private * @param {function} item * @returns {boolean} */ - isFunction(item) { + isFunction(item: any): boolean { return typeof item === 'function'; } /** - * Simple object check. + * Simple object check * @private * @param {Object} item * @returns {boolean} */ - isObject(item) { + isObject(item: any): boolean { return Object.prototype.toString.call(item) === '[object Object]'; } - getLuigiCoreDomain() { + getLuigiCoreDomain(): string { return this.origin; } - setLuigiCoreDomain(origin) { + setLuigiCoreDomain(origin: string): void { // protect against "null" string set by at least Chrome browser when file protocol used if (origin && origin !== 'null') { this.origin = origin; } } - setTargetOrigin(origin) { + setTargetOrigin(origin: string): void { this.setLuigiCoreDomain(origin); } - sendPostMessageToLuigiCore(msg) { + sendPostMessageToLuigiCore(msg: any): void { if (this.origin) { // protect against potential postMessage problems, since origin value may be set incorrectly try { window.parent.postMessage(msg, this.origin); } catch (error) { - console.warn('Unable to post message ' + msg + ' to Luigi Core from origin ' + this.origin + ': ' + error); + console.warn( + 'Unable to post message ' + + msg + + ' to Luigi Core from origin ' + + this.origin + + ': ' + + error + ); } } else { console.warn( @@ -137,19 +173,23 @@ class Helpers { /** * Checks if given path contains intent navigation special syntax * @param {string} path to check + * @returns {boolean} */ - hasIntent(path) { + hasIntent(path: string): boolean { return !!path && path.toLowerCase().includes('#?intent='); } - deSanitizeParamsMap(paramsMap) { - return Object.entries(paramsMap).reduce((sanitizedMap, paramPair) => { - sanitizedMap[this.deSanitizeParam(paramPair[0])] = this.deSanitizeParam(paramPair[1]); + deSanitizeParamsMap(paramsMap: Record) { + return Object.entries(paramsMap).reduce((sanitizedMap, paramPair) => { + sanitizedMap[ + this.deSanitizeParam(paramPair[0] as string) + ] = this.deSanitizeParam(paramPair[1] as string); + return sanitizedMap; }, {}); } - deSanitizeParam(param = '') { + deSanitizeParam(param = ''): string { return String(param) .replaceAll('<', '<') .replaceAll('>', '>') @@ -159,4 +199,4 @@ class Helpers { } } -export const helpers = new Helpers(); +export const helpers: Helpers = new Helpers(); diff --git a/client/src/lifecycleManager.js b/client/src/lifecycleManager.ts similarity index 71% rename from client/src/lifecycleManager.js rename to client/src/lifecycleManager.ts index 68dbb22c49..6a8071ea84 100644 --- a/client/src/lifecycleManager.js +++ b/client/src/lifecycleManager.ts @@ -1,3 +1,14 @@ +import { + AuthData, + ClientPermissions, + Context, + CoreSearchParams, + InternalContext, + InternalMessageData, + NodeParams, + PathParams, + UserSettings +} from '../luigi-client'; import { LuigiClientBase } from './baseClass'; import { helpers } from './helpers'; @@ -6,16 +17,36 @@ import { helpers } from './helpers'; * @name Lifecycle */ class LifecycleManager extends LuigiClientBase { + currentContext!: InternalContext; + private authData: AuthData; + private defaultContextKeys: string[]; + private luigiInitialized: boolean; + private _onContextUpdatedFns: Record; + private _onInactiveFns: Record; + private _onInitFns: Record; + /** @private */ constructor() { super(); + this.luigiInitialized = false; - this.defaultContextKeys = ['context', 'internal', 'nodeParams', 'pathParams', 'searchParams']; + this.defaultContextKeys = [ + 'context', + 'internal', + 'nodeParams', + 'pathParams', + 'searchParams' + ]; + this.setCurrentContext( - this.defaultContextKeys.reduce(function(acc, key) { - acc[key] = {}; - return acc; - }, {}) + this.defaultContextKeys.reduce( + (acc: Record, key: string) => { + acc[key] = {}; + + return acc; + }, + {} + ) ); this._onContextUpdatedFns = {}; @@ -33,7 +64,7 @@ class LifecycleManager extends LuigiClientBase { * @private * @memberof Lifecycle */ - _isDeferInitDefined() { + _isDeferInitDefined(): boolean { return window.document.head.hasAttribute('defer-luigi-init'); } @@ -45,7 +76,7 @@ class LifecycleManager extends LuigiClientBase { * @example * const init = LuigiClient.isLuigiClientInitialized() */ - isLuigiClientInitialized() { + isLuigiClientInitialized(): boolean { return this.luigiInitialized; } @@ -56,69 +87,91 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.luigiClientInit() */ - luigiClientInit() { + luigiClientInit(): void { if (this.luigiInitialized) { console.warn('Luigi Client has been already initialized'); return; } + /** * Save context data every time navigation to a different node happens * @private */ - const setContext = rawData => { + const setContext = (rawData: any) => { for (let index = 0; index < this.defaultContextKeys.length; index++) { - let key = this.defaultContextKeys[index]; + let key: string = this.defaultContextKeys[index]; + try { if (typeof rawData[key] === 'string') { rawData[key] = JSON.parse(rawData[key]); } - } catch (e) { - console.info('unable to parse luigi context data for', key, rawData[key], e); + } catch (error) { + console.info( + 'unable to parse luigi context data for', + key, + rawData[key], + error + ); } } this.setCurrentContext(rawData); }; - const setAuthData = eventPayload => { + const setAuthData = (eventPayload: any) => { if (eventPayload) { this.authData = eventPayload; } }; - helpers.addEventListener('luigi.init', e => { - setContext(e.data); - setAuthData(e.data.authData); - helpers.setLuigiCoreDomain(e.origin); + helpers.addEventListener('luigi.init', (event: any) => { + setContext(event.data); + setAuthData(event.data.authData); + helpers.setLuigiCoreDomain(event.origin); this.luigiInitialized = true; - this._notifyInit(e.origin); + this._notifyInit(event.origin); helpers.sendPostMessageToLuigiCore({ msg: 'luigi.init.ok' }); }); - helpers.addEventListener('luigi-client.inactive-microfrontend', e => { - this._notifyInactive(e.origin); - }); + helpers.addEventListener( + 'luigi-client.inactive-microfrontend', + (event: any) => { + this._notifyInactive(event.origin); + } + ); - helpers.addEventListener('luigi.auth.tokenIssued', e => { - setAuthData(e.data.authData); + helpers.addEventListener('luigi.auth.tokenIssued', (event: any) => { + setAuthData(event.data.authData); }); - helpers.addEventListener('luigi.navigate', e => { - setContext(e.data); - if (!this.currentContext.internal.isNavigateBack && !this.currentContext.withoutSync) { - const previousHash = window.location.hash; - history.replaceState({ luigiInduced: true }, '', e.data.viewUrl); - window.dispatchEvent(new PopStateEvent('popstate', { state: 'luiginavigation' })); + helpers.addEventListener('luigi.navigate', (event: any) => { + setContext(event.data); + + if ( + !(this.currentContext.internal as Record)[ + 'isNavigateBack' + ] && + !this.currentContext['withoutSync'] + ) { + const previousHash: string = window.location.hash; + + history.replaceState({ luigiInduced: true }, '', event.data.viewUrl); + window.dispatchEvent( + new PopStateEvent('popstate', { state: 'luiginavigation' }) + ); + if (window.location.hash !== previousHash) { window.dispatchEvent(new HashChangeEvent('hashchange')); } } + // pass additional data to context to enable micro frontend developer to act on internal routing change - if (this.currentContext.withoutSync) { - Object.assign(this.currentContext.context, { - viewUrl: e.data.viewUrl ? e.data.viewUrl : undefined, - pathParams: e.data.pathParams ? e.data.pathParams : undefined + if (this.currentContext['withoutSync']) { + Object.assign(this.currentContext.context as Context, { + viewUrl: event.data.viewUrl ? event.data.viewUrl : undefined, + pathParams: event.data.pathParams ? event.data.pathParams : undefined }); } + // execute the context change listener if set by the micro frontend this._notifyUpdate(); helpers.sendPostMessageToLuigiCore({ msg: 'luigi.navigate.ok' }); @@ -143,7 +196,11 @@ class LifecycleManager extends LuigiClientBase { * @private * @memberof Lifecycle */ - _callAllFns(objWithFns, payload, origin) { + _callAllFns( + objWithFns: Record, + payload: Context | null, + origin?: string + ): void { for (let id in objWithFns) { if (objWithFns.hasOwnProperty(id) && helpers.isFunction(objWithFns[id])) { objWithFns[id](payload, origin); @@ -156,8 +213,12 @@ class LifecycleManager extends LuigiClientBase { * @private * @memberof Lifecycle */ - _notifyInit(origin) { - this._callAllFns(this._onInitFns, this.currentContext.context, origin); + _notifyInit(origin: string) { + this._callAllFns( + this._onInitFns, + this.currentContext.context as Context, + origin + ); } /** @@ -166,7 +227,10 @@ class LifecycleManager extends LuigiClientBase { * @memberof Lifecycle */ _notifyUpdate() { - this._callAllFns(this._onContextUpdatedFns, this.currentContext.context); + this._callAllFns( + this._onContextUpdatedFns, + this.currentContext.context as Context + ); } /** @@ -174,15 +238,15 @@ class LifecycleManager extends LuigiClientBase { * @private * @memberof Lifecycle */ - _notifyInactive() { - this._callAllFns(this._onInactiveFns); + _notifyInactive(origin: any) { + this._callAllFns(this._onInactiveFns, null, origin); } /** * @private * @memberof Lifecycle */ - setCurrentContext(value) { + setCurrentContext(value: InternalContext) { this.currentContext = value; } @@ -193,12 +257,18 @@ class LifecycleManager extends LuigiClientBase { * @example * const initListenerId = LuigiClient.addInitListener((context) => storeContextToMF(context)) */ - addInitListener(initFn) { - const id = helpers.getRandomId(); - this._onInitFns[id] = initFn; + addInitListener(initFn: (context: Context, origin?: string) => void): number { + const id: number = helpers.getRandomId(); + + this._onInitFns[`${id}`] = initFn; + if (this.luigiInitialized && helpers.isFunction(initFn)) { - initFn(this.currentContext.context, helpers.getLuigiCoreDomain()); + initFn( + this.currentContext.context as Context, + helpers.getLuigiCoreDomain() + ); } + return id; } @@ -215,11 +285,13 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.removeInitListener(initListenerId) */ - removeInitListener(id) { + removeInitListener(id: string): boolean { if (this._onInitFns[id]) { this._onInitFns[id] = undefined; + return true; } + return false; } @@ -230,13 +302,18 @@ class LifecycleManager extends LuigiClientBase { * @example * const updateListenerId = LuigiClient.addContextUpdateListener((context) => storeContextToMF(context)) */ - addContextUpdateListener(contextUpdatedFn) { - const id = helpers.getRandomId(); - this._onContextUpdatedFns[id] = contextUpdatedFn; + addContextUpdateListener( + contextUpdatedFn: (context: Context) => void + ): string { + const id: number = helpers.getRandomId(); + + this._onContextUpdatedFns[`${id}`] = contextUpdatedFn; + if (this.luigiInitialized && helpers.isFunction(contextUpdatedFn)) { - contextUpdatedFn(this.currentContext.context); + contextUpdatedFn(this.currentContext.context as Context); } - return id; + + return `${id}`; } /** @@ -246,11 +323,13 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.removeContextUpdateListener(updateListenerId) */ - removeContextUpdateListener(id) { + removeContextUpdateListener(id: string): boolean { if (this._onContextUpdatedFns[id]) { this._onContextUpdatedFns[id] = undefined; + return true; } + return false; } @@ -268,10 +347,12 @@ class LifecycleManager extends LuigiClientBase { * LuigiClient.addInactiveListener(() => mfIsInactive = true) * const inactiveListenerId = LuigiClient.addInactiveListener(() => mfIsInactive = true) */ - addInactiveListener(inactiveFn) { - const id = helpers.getRandomId(); - this._onInactiveFns[id] = inactiveFn; - return id; + addInactiveListener(inactiveFn: () => void): string { + const id: number = helpers.getRandomId(); + + this._onInactiveFns[`${id}`] = inactiveFn; + + return `${id}`; } /** @@ -281,11 +362,13 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.removeInactiveListener(inactiveListenerId) */ - removeInactiveListener(id) { + removeInactiveListener(id: string): boolean { if (this._onInactiveFns[id]) { this._onInactiveFns[id] = undefined; + return true; } + return false; } @@ -298,10 +381,16 @@ class LifecycleManager extends LuigiClientBase { * @example * const customMsgId = LuigiClient.addCustomMessageListener('myapp.project-updated', (data) => doSomething(data)) */ - addCustomMessageListener(customMessageId, customMessageListener) { - return helpers.addEventListener(customMessageId, (customMessage, listenerId) => { - return customMessageListener(customMessage, listenerId); - }); + addCustomMessageListener( + customMessageId: string, + customMessageListener: (customMessage: Object, listenerId: string) => void + ): string { + return helpers.addEventListener( + customMessageId, + (customMessage: Object, listenerId: string) => { + return customMessageListener(customMessage, listenerId); + } + ); } /** @@ -319,7 +408,7 @@ class LifecycleManager extends LuigiClientBase { * @since 0.6.2 * LuigiClient.removeCustomMessageListener(customMsgId) */ - removeCustomMessageListener(id) { + removeCustomMessageListener(id: string): boolean { return helpers.removeEventListener(id); } @@ -330,8 +419,8 @@ class LifecycleManager extends LuigiClientBase { * @example * const accessToken = LuigiClient.getToken() */ - getToken() { - return this.authData.accessToken; + getToken(): AuthData['accessToken'] { + return this.authData['accessToken']; } /** @@ -341,7 +430,7 @@ class LifecycleManager extends LuigiClientBase { * @example * const context = LuigiClient.getContext() */ - getContext() { + getContext(): Context { return this.getEventData(); } @@ -351,8 +440,8 @@ class LifecycleManager extends LuigiClientBase { * @memberof Lifecycle * @deprecated */ - getEventData() { - return this.currentContext.context; + getEventData(): Context { + return this.currentContext.context as Context; } /** @@ -363,8 +452,10 @@ class LifecycleManager extends LuigiClientBase { * @example * const activeFeatureToggleList = LuigiClient.getActiveFeatureToggles() */ - getActiveFeatureToggles() { - return this.currentContext.internal.activeFeatureToggleList; + getActiveFeatureToggles(): string[] { + return (this.currentContext.internal as Record)[ + 'activeFeatureToggleList' + ]; } /** @@ -375,7 +466,7 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.addNodeParams({luigi:'rocks'}, true); */ - addNodeParams(params, keepBrowserHistory = true) { + addNodeParams(params: NodeParams, keepBrowserHistory = true): void { if (params) { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.addNodeParams', @@ -397,9 +488,11 @@ class LifecycleManager extends LuigiClientBase { * const nodeParams = LuigiClient.getNodeParams() * const nodeParams = LuigiClient.getNodeParams(true) */ - getNodeParams(shouldDesanitise = false) { + getNodeParams(shouldDesanitise = false): NodeParams { return shouldDesanitise - ? helpers.deSanitizeParamsMap(this.currentContext.nodeParams) + ? helpers.deSanitizeParamsMap( + this.currentContext.nodeParams as NodeParams + ) : this.currentContext.nodeParams; } @@ -414,8 +507,8 @@ class LifecycleManager extends LuigiClientBase { * @example * const pathParams = LuigiClient.getPathParams() */ - getPathParams() { - return this.currentContext.pathParams; + getPathParams(): PathParams { + return this.currentContext.pathParams as PathParams; } /** @@ -425,20 +518,22 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.getCoreSearchParams(); */ - getCoreSearchParams() { - return this.currentContext.searchParams || {}; + getCoreSearchParams(): CoreSearchParams { + return this.currentContext['searchParams'] || {}; } /** * Sends search query parameters to Luigi Core. The search parameters will be added to the URL if they are first allowed on a node level using {@link navigation-parameters-reference.md#clientpermissionsurlparameters clientPermissions.urlParameters}. - * @param {Object} searchParams * @param {boolean} keepBrowserHistory * @memberof Lifecycle * @example * LuigiClient.addCoreSearchParams({luigi:'rocks'}, false); */ - addCoreSearchParams(searchParams, keepBrowserHistory = true) { + addCoreSearchParams( + searchParams: CoreSearchParams, + keepBrowserHistory = true + ): void { if (searchParams) { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.addSearchParams', @@ -455,8 +550,12 @@ class LifecycleManager extends LuigiClientBase { * @example * const permissions = LuigiClient.getClientPermissions() */ - getClientPermissions() { - return this.currentContext.internal.clientPermissions || {}; + getClientPermissions(): ClientPermissions { + return ( + (this.currentContext.internal as Record)[ + 'clientPermissions' + ] || {} + ); } /** @@ -467,7 +566,7 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.setTargetOrigin(window.location.origin) */ - setTargetOrigin(origin) { + setTargetOrigin(origin: string): void { helpers.setTargetOrigin(origin); } @@ -482,8 +581,11 @@ class LifecycleManager extends LuigiClientBase { * @memberof Lifecycle * @since 0.6.2 */ - sendCustomMessage(message) { - const customMessageInternal = helpers.convertCustomMessageUserToInternal(message); + sendCustomMessage(message: Object) { + const customMessageInternal: InternalMessageData = helpers.convertCustomMessageUserToInternal( + message + ); + helpers.sendPostMessageToLuigiCore(customMessageInternal); } @@ -495,8 +597,11 @@ class LifecycleManager extends LuigiClientBase { * @example * const userSettings = LuigiClient.getUserSettings() */ - getUserSettings() { - return this.currentContext.internal.userSettings || {}; + getUserSettings(): UserSettings { + return ( + (this.currentContext.internal as Record)['userSettings'] || + {} + ); } /** @@ -507,8 +612,10 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.getAnchor(); */ - getAnchor() { - return this.currentContext.internal.anchor || ''; + getAnchor(): string { + return ( + (this.currentContext.internal as Record)['anchor'] || '' + ); } /** @@ -519,7 +626,7 @@ class LifecycleManager extends LuigiClientBase { * @example * LuigiClient.setAnchor('luigi'); */ - setAnchor(anchor) { + setAnchor(anchor: string): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.setAnchor', anchor @@ -533,11 +640,12 @@ class LifecycleManager extends LuigiClientBase { * @memberof Lifecycle * @example LuigiClient.setViewGroupData({'vg1':' Luigi rocks!'}) */ - setViewGroupData(data) { + setViewGroupData(data: Object): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.setVGData', data }); } } + export const lifecycleManager = new LifecycleManager(); diff --git a/client/src/linkManager.js b/client/src/linkManager.ts similarity index 77% rename from client/src/linkManager.js rename to client/src/linkManager.ts index 2412c00f8a..45ab33fc23 100644 --- a/client/src/linkManager.js +++ b/client/src/linkManager.ts @@ -1,5 +1,17 @@ +import { + Context, + DrawerSettings, + InternalContext, + InternalMessageData, + ModalSettings, + NodeParams, + RouteChangingOptions, + SplitViewInstance, + SplitViewSettings +} from '../luigi-client'; import { LuigiClientBase } from './baseClass'; import { helpers } from './helpers'; +import { lifecycleManager } from './lifecycleManager'; import { splitViewHandle } from './splitViewHandle'; /** @@ -10,28 +22,31 @@ import { splitViewHandle } from './splitViewHandle'; * @name linkManager */ export class linkManager extends LuigiClientBase { + private options: Record; + /** * @private */ - constructor(values) { + constructor(values: Record) { super(); + Object.assign(this, values); this.options = { - preserveView: false, - nodeParams: {}, + anchor: '', errorSkipNavigation: false, - fromContext: null, fromClosestContext: false, - fromVirtualTreeRoot: false, + fromContext: null, fromParent: false, - relative: false, + fromVirtualTreeRoot: false, link: '', newTab: false, + nodeParams: {}, preserveQueryParams: false, - anchor: '', + preserveView: false, preventContextUpdate: false, - preventHistoryEntry: false + preventHistoryEntry: false, + relative: false }; } @@ -62,25 +77,39 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().navigate('/settings', null, true) // preserve view * LuigiClient.linkManager().navigate('#?Intent=Sales-order?id=13') // intent navigation */ - navigate(path, sessionId, preserveView, modalSettings, splitViewSettings, drawerSettings) { - if (this.options.errorSkipNavigation) { - this.options.errorSkipNavigation = false; + navigate( + path: string, + sessionId?: string, + preserveView?: boolean, + modalSettings?: ModalSettings, + splitViewSettings?: SplitViewSettings, + drawerSettings?: DrawerSettings + ): void { + if (this.options['errorSkipNavigation']) { + this.options['errorSkipNavigation'] = false; + return; } + if (modalSettings && splitViewSettings && drawerSettings) { console.warn( 'modalSettings, splitViewSettings and drawerSettings cannot be used together. Only modal setting will be taken into account.' ); } - this.options.preserveView = preserveView; - const relativePath = path[0] !== '/'; + this.options['preserveView'] = preserveView; - if (path === '/' && (modalSettings || splitViewSettings || drawerSettings)) { + const relativePath: boolean = path[0] !== '/'; + + if ( + path === '/' && + (modalSettings || splitViewSettings || drawerSettings) + ) { console.warn('Navigation with an absolute path prevented.'); return; } - const navigationOpenMsg = { + + const navigationOpenMsg: Record = { msg: 'luigi.navigation.open', sessionId: sessionId, params: Object.assign(this.options, { @@ -92,6 +121,7 @@ export class linkManager extends LuigiClientBase { drawer: drawerSettings }) }; + helpers.sendPostMessageToLuigiCore(navigationOpenMsg); } @@ -105,13 +135,19 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().updateModalPathInternalNavigation('microfrontend') */ - updateModalPathInternalNavigation(path, modalSettings = {}, addHistoryEntry = false) { + updateModalPathInternalNavigation( + path: string, + modalSettings = {}, + addHistoryEntry = false + ): void { if (!path) { - console.warn('Updating path of the modal upon internal navigation prevented. No path specified.'); + console.warn( + 'Updating path of the modal upon internal navigation prevented. No path specified.' + ); return; } - const navigationOpenMsg = { + const navigationOpenMsg: Record = { msg: 'luigi.navigation.updateModalDataPath', params: Object.assign(this.options, { link: path, @@ -119,6 +155,7 @@ export class linkManager extends LuigiClientBase { history: addHistoryEntry }) }; + helpers.sendPostMessageToLuigiCore(navigationOpenMsg); } @@ -136,21 +173,27 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().navigateToIntent('Sales-settings', {project: 'pr2', user: 'john'}) * LuigiClient.linkManager().navigateToIntent('Sales-settings') */ - navigateToIntent(semanticSlug, params = {}) { - let newPath = '#?intent='; + navigateToIntent(semanticSlug: string, params = {}): void { + let newPath: string = '#?intent='; + newPath += semanticSlug; + if (params) { - const paramList = Object.entries(params); + const paramList: any[] = Object.entries(params); + // append parameters to the path if any if (paramList.length > 0) { newPath += '?'; + for (const [key, value] of paramList) { newPath += key + '=' + value + '&'; } + // trim potential excessive ampersand & at the end newPath = newPath.slice(0, -1); } } + this.navigate(newPath); } @@ -172,23 +215,32 @@ export class linkManager extends LuigiClientBase { * console.log(res.data) //=> {foo: 'bar'} * }); */ - openAsModal(path, modalSettings = {}) { - helpers.addEventListener('luigi.navigation.modal.close', (e, listenerId) => { - const promise = this.getPromise('modal'); - if (promise) { - promise.resolveFn(e.data); - this.setPromise('modal', undefined); + openAsModal(path: string, modalSettings = {}): Promise { + helpers.addEventListener( + 'luigi.navigation.modal.close', + (event: any, listenerId: string) => { + const promise = this.getPromise('modal'); + + if (promise) { + promise.resolveFn(event.data); + this.setPromise('modal', undefined); + } + + helpers.removeEventListener(listenerId); } - helpers.removeEventListener(listenerId); - }); - const modalPromise = {}; - modalPromise.promise = new Promise((resolve, reject) => { - modalPromise.resolveFn = resolve; - modalPromise.rejectFn = reject; + ); + + const modalPromise: Record = {}; + + modalPromise['promise'] = new Promise((resolve, reject) => { + modalPromise['resolveFn'] = resolve; + modalPromise['rejectFn'] = reject; }); + this.setPromise('modal', modalPromise); - this.navigate(path, 0, true, modalSettings); - return modalPromise.promise; + this.navigate(path, '0', true, modalSettings); + + return modalPromise['promise']; } /** @@ -204,12 +256,16 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().updateModalSettings({title:'LuigiModal', size:'l'}); */ - updateModalSettings(updatedModalSettings = {}, addHistoryEntry = false) { - const message = { + updateModalSettings( + updatedModalSettings = {}, + addHistoryEntry = false + ): void { + const message: Record = { msg: 'luigi.navigation.updateModalSettings', updatedModalSettings, addHistoryEntry }; + helpers.sendPostMessageToLuigiCore(message); } @@ -227,9 +283,12 @@ export class linkManager extends LuigiClientBase { * @example * const splitViewHandle = LuigiClient.linkManager().openAsSplitView('projects/pr1/logs', {title: 'Logs', size: 40, collapsed: true}); */ - openAsSplitView(path, splitViewSettings = {}) { - this.navigate(path, 0, true, undefined, splitViewSettings); - return new splitViewHandle(splitViewSettings); + openAsSplitView(path: string, splitViewSettings = {}): SplitViewInstance { + this.navigate(path, '0', true, undefined, splitViewSettings); + + return (new splitViewHandle( + splitViewSettings + ) as unknown) as SplitViewInstance; } /** @@ -246,8 +305,8 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().openAsDrawer('projects/pr1/drawer', {header:true, backdrop:true, size:'s'}); * LuigiClient.linkManager().openAsDrawer('projects/pr1/drawer', {header:{title:'My drawer component'}, backdrop:true, size:'xs'}); */ - openAsDrawer(path, drawerSettings = {}) { - this.navigate(path, 0, true, undefined, undefined, drawerSettings); + openAsDrawer(path: string, drawerSettings = {}): void { + this.navigate(path, '0', true, undefined, undefined, drawerSettings); } /** @@ -258,16 +317,26 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().fromContext('project').navigate('/settings') */ - fromContext(navigationContext) { - const navigationContextInParent = - this.currentContext.context.parentNavigationContexts && - this.currentContext.context.parentNavigationContexts.indexOf(navigationContext) !== -1; + fromContext(navigationContext: string): this { + const navigationContextInParent: boolean = + (lifecycleManager.currentContext.context as Context)[ + 'parentNavigationContexts' + ] && + (lifecycleManager.currentContext.context as Context)[ + 'parentNavigationContexts' + ]?.indexOf(navigationContext) !== -1; + if (navigationContextInParent) { - this.options.fromContext = navigationContext; + this.options['fromContext'] = navigationContext; } else { - this.options.errorSkipNavigation = true; - console.error('Navigation not possible, navigationContext ' + navigationContext + ' not found.'); + this.options['errorSkipNavigation'] = true; + console.error( + 'Navigation not possible, navigationContext ' + + navigationContext + + ' not found.' + ); } + return this; } @@ -278,14 +347,24 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().fromClosestContext().navigate('/users/groups/stakeholders') */ - fromClosestContext() { - const hasParentNavigationContext = this.currentContext?.context.parentNavigationContexts.length > 0; + fromClosestContext(): this { + const hasParentNavigationContext: boolean = + (lifecycleManager.currentContext.context as Context)[ + 'parentNavigationContexts' + ] && + (lifecycleManager.currentContext.context as Context)[ + 'parentNavigationContexts' + ]?.length > 0; + if (hasParentNavigationContext) { - this.options.fromContext = null; - this.options.fromClosestContext = true; + this.options['fromContext'] = null; + this.options['fromClosestContext'] = true; } else { - console.error('Navigation not possible, no parent navigationContext found.'); + console.error( + 'Navigation not possible, no parent navigationContext found.' + ); } + return this; } @@ -297,10 +376,11 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().fromVirtualTreeRoot().navigate('/users/groups/stakeholders') */ - fromVirtualTreeRoot() { - this.options.fromContext = null; - this.options.fromClosestContext = false; - this.options.fromVirtualTreeRoot = true; + fromVirtualTreeRoot(): this { + this.options['fromContext'] = null; + this.options['fromClosestContext'] = false; + this.options['fromVirtualTreeRoot'] = true; + return this; } @@ -312,8 +392,9 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().fromParent().navigate('/sibling') */ - fromParent() { - this.options.fromParent = true; + fromParent(): this { + this.options['fromParent'] = true; + return this; } @@ -328,10 +409,11 @@ export class linkManager extends LuigiClientBase { * // Can be chained with context setting functions such as: * LuigiClient.linkManager().fromContext("currentTeam").withParams({foo: "bar"}).navigate("path") */ - withParams(nodeParams) { + withParams(nodeParams: NodeParams): this { if (nodeParams) { - Object.assign(this.options.nodeParams, nodeParams); + Object.assign(this.options['nodeParams'], nodeParams); } + return this; } @@ -348,15 +430,15 @@ export class linkManager extends LuigiClientBase { * { preventContextUpdate:true, preventHistoryEntry: true } * ).navigate('/overview') */ - withOptions(options) { + withOptions(options: RouteChangingOptions): this { if (!helpers.isObject(options)) return this; if (options['preventHistoryEntry'] !== undefined) { - this.options.preventHistoryEntry = options['preventHistoryEntry']; + this.options['preventHistoryEntry'] = options['preventHistoryEntry']; } if (options['preventContextUpdate'] !== undefined) { - this.options.preventContextUpdate = options['preventContextUpdate']; + this.options['preventContextUpdate'] = options['preventContextUpdate']; } return this; @@ -377,35 +459,36 @@ export class linkManager extends LuigiClientBase { * (pathExists) => { } * ); */ - pathExists(path) { - const currentId = helpers.getRandomId(); + pathExists(path: string): Promise { + const currentId: number = helpers.getRandomId(); const pathExistsPromises = this.getPromise('pathExistsPromises') || {}; - pathExistsPromises[currentId] = { - resolveFn: function() {}, - then: function(resolveFn) { - this.resolveFn = resolveFn; - } + + pathExistsPromises[`${currentId}`] = { + resolveFn: () => {}, + then: (resolveFn: () => void) => (resolveFn = resolveFn) }; this.setPromise('pathExistsPromises', pathExistsPromises); // register event listener, which will be cleaned up after this usage helpers.addEventListener( 'luigi.navigation.pathExists.answer', - function(e, listenerId) { - const data = e.data.data; + (event, listenerId) => { + const data = event.data.data; const pathExistsPromises = this.getPromise('pathExistsPromises') || {}; + if (data.correlationId === currentId) { if (pathExistsPromises[data.correlationId]) { pathExistsPromises[data.correlationId].resolveFn(data.pathExists); delete pathExistsPromises[data.correlationId]; this.setPromise('pathExistsPromises', pathExistsPromises); } + helpers.removeEventListener(listenerId); } - }.bind(this) + } ); - const pathExistsMsg = { + const pathExistsMsg: InternalMessageData = { msg: 'luigi.navigation.pathExists', data: Object.assign(this.options, { id: currentId, @@ -414,8 +497,10 @@ export class linkManager extends LuigiClientBase { relative: path[0] !== '/' }) }; + helpers.sendPostMessageToLuigiCore(pathExistsMsg); - return pathExistsPromises[currentId]; + + return pathExistsPromises[`${currentId}`]; } /** @@ -423,8 +508,15 @@ export class linkManager extends LuigiClientBase { * @memberof linkManager * @returns {boolean} indicating if there is a preserved view you can return to */ - hasBack() { - return !!this.currentContext.internal.modal || this.currentContext.internal.viewStackSize !== 0; + hasBack(): boolean { + return ( + !!(lifecycleManager.currentContext.internal as Record)[ + 'modal' + ] || + (lifecycleManager.currentContext.internal as Record)[ + 'viewStackSize' + ] !== 0 + ); } /** @@ -435,7 +527,7 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().goBack({ foo: 'bar' }); * LuigiClient.linkManager().goBack(true); */ - goBack(goBackValue) { + goBack(goBackValue: any): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.navigation.back', goBackContext: goBackValue && JSON.stringify(goBackValue) @@ -451,8 +543,9 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().withoutSync().navigate('/projects/xy/foobar'); * LuigiClient.linkManager().withoutSync().fromClosestContext().navigate('settings'); */ - withoutSync() { - this.options.withoutSync = true; + withoutSync(): this { + this.options['withoutSync'] = true; + return this; } @@ -462,8 +555,9 @@ export class linkManager extends LuigiClientBase { * @example * LuigiClient.linkManager().newTab().navigate('/projects/xy/foobar'); */ - newTab() { - this.options.newTab = true; + newTab(): this { + this.options['newTab'] = true; + return this; } @@ -475,8 +569,9 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().preserveQueryParams(true).navigate('/projects/xy/foobar'); * LuigiClient.linkManager().preserveQueryParams(false).navigate('/projects/xy/foobar'); */ - preserveQueryParams(preserve = false) { - this.options.preserveQueryParams = preserve; + preserveQueryParams(preserve = false): this { + this.options['preserveQueryParams'] = preserve; + return this; } @@ -489,33 +584,37 @@ export class linkManager extends LuigiClientBase { * LuigiClient.linkManager().fromContext('project').getCurrentRoute(); * LuigiClient.linkManager().fromVirtualTreeRoot().getCurrentRoute(); */ - getCurrentRoute() { - const currentId = helpers.getRandomId(); + getCurrentRoute(): Promise { + const currentId: number = helpers.getRandomId(); const currentRoutePromise = this.getPromise('getCurrentRoute') || {}; - currentRoutePromise[currentId] = { - resolveFn: function() {}, - then: function(resolveFn) { - this.resolveFn = resolveFn; - } + + currentRoutePromise[`${currentId}`] = { + resolveFn: () => {}, + then: (resolveFn: () => void) => (resolveFn = resolveFn) }; this.setPromise('getCurrentRoute', currentRoutePromise); - helpers.addEventListener('luigi.navigation.currentRoute.answer', (e, listenerId) => { - const data = e.data.data; - const currentRoutePromise = this.getPromise('getCurrentRoute') || {}; + helpers.addEventListener( + 'luigi.navigation.currentRoute.answer', + (event, listenerId) => { + const data = event.data.data; + const currentRoutePromise = this.getPromise('getCurrentRoute') || {}; + + if (data.correlationId === currentId) { + if (currentRoutePromise[data.correlationId]) { + currentRoutePromise[data.correlationId].resolveFn(data.route); + delete currentRoutePromise[data.correlationId]; + this.setPromise('getCurrentRoute', currentRoutePromise); + } - if (data.correlationId === currentId) { - if (currentRoutePromise[data.correlationId]) { - currentRoutePromise[data.correlationId].resolveFn(data.route); - delete currentRoutePromise[data.correlationId]; - this.setPromise('getCurrentRoute', currentRoutePromise); + helpers.removeEventListener(listenerId); } + helpers.removeEventListener(listenerId); } - helpers.removeEventListener(listenerId); - }); + ); helpers.sendPostMessageToLuigiCore({ msg: 'luigi.navigation.currentRoute', diff --git a/client/src/luigi-client.js b/client/src/luigi-client.js deleted file mode 100644 index 670bc5e375..0000000000 --- a/client/src/luigi-client.js +++ /dev/null @@ -1,135 +0,0 @@ -import { lifecycleManager } from './lifecycleManager'; -import { linkManager } from './linkManager'; -import { uxManager } from './uxManager'; -import { storageManager } from './storageManager'; -import { helpers } from './helpers'; - -/** - * @name LuigiClient - * @private - */ -class LuigiClient { - constructor() { - if (window !== window.top) { - if (window.document.head.getAttribute('disable-luigi-history-handling') !== 'true') { - history.pushState = history.replaceState.bind(history); - } - if (window.document.head.getAttribute('disable-luigi-runtime-error-handling') !== 'true') { - window.addEventListener('error', ({ filename, message, lineno, colno, error }) => { - const msg = { - msg: 'luigi-runtime-error-handling', - errorObj: { filename, message, lineno, colno, error } - }; - helpers.sendPostMessageToLuigiCore(msg); - }); - } - } - } - - addInitListener(initFn) { - return lifecycleManager.addInitListener(initFn); - } - removeInitListener(id) { - return lifecycleManager.removeInitListener(id); - } - addContextUpdateListener(contextUpdatedFn) { - return lifecycleManager.addContextUpdateListener(contextUpdatedFn); - } - removeContextUpdateListener(id) { - return lifecycleManager.removeContextUpdateListener(id); - } - getToken() { - return lifecycleManager.getToken(); - } - getEventData() { - return lifecycleManager.getEventData(); - } - getContext() { - return lifecycleManager.getContext(); - } - addNodeParams(params, keepBrowserHistory) { - return lifecycleManager.addNodeParams(params, keepBrowserHistory); - } - getNodeParams(shouldDesanitise) { - return lifecycleManager.getNodeParams(shouldDesanitise); - } - getActiveFeatureToggles() { - return lifecycleManager.getActiveFeatureToggles(); - } - getPathParams() { - return lifecycleManager.getPathParams(); - } - getCoreSearchParams() { - return lifecycleManager.getCoreSearchParams(); - } - addCoreSearchParams(searchParams, keepBrowserHistory) { - return lifecycleManager.addCoreSearchParams(searchParams, keepBrowserHistory); - } - getClientPermissions() { - return lifecycleManager.getClientPermissions(); - } - sendCustomMessage(message) { - return lifecycleManager.sendCustomMessage(message); - } - addCustomMessageListener(messageId, listener) { - return lifecycleManager.addCustomMessageListener(messageId, listener); - } - removeCustomMessageListener(listenerId) { - return lifecycleManager.removeCustomMessageListener(listenerId); - } - addInactiveListener(messageId, listener) { - return lifecycleManager.addInactiveListener(messageId, listener); - } - removeInactiveListener(listenerId) { - return lifecycleManager.removeInactiveListener(listenerId); - } - setTargetOrigin(origin) { - return lifecycleManager.setTargetOrigin(origin); - } - getUserSettings() { - return lifecycleManager.getUserSettings(); - } - isLuigiClientInitialized() { - return lifecycleManager.isLuigiClientInitialized(); - } - luigiClientInit() { - return lifecycleManager.luigiClientInit(); - } - getAnchor() { - return lifecycleManager.getAnchor(); - } - setAnchor(value) { - return lifecycleManager.setAnchor(value); - } - setViewGroupData(value) { - return lifecycleManager.setViewGroupData(value); - } - - /** - * @private - */ - linkManager() { - return new linkManager({ - currentContext: lifecycleManager.currentContext - }); - } - /** - * @private - */ - uxManager() { - return uxManager; - } - /** - * @private - */ - lifecycleManager() { - return lifecycleManager; - } - /** - * @private - */ - storageManager() { - return storageManager; - } -} -export default LuigiClient = new LuigiClient(); diff --git a/client/src/luigi-client.ts b/client/src/luigi-client.ts new file mode 100644 index 0000000000..d36545d41c --- /dev/null +++ b/client/src/luigi-client.ts @@ -0,0 +1,202 @@ +import { + AuthData, + ClientPermissions, + CoreSearchParams, + InternalContext, + LinkManager, + NodeParams, + PathParams, + PostMessageData, + StorageManager, + UserSettings, + UxManager +} from '../luigi-client'; +import { helpers } from './helpers'; +import { lifecycleManager } from './lifecycleManager'; +import { linkManager } from './linkManager'; +import { storageManager } from './storageManager'; +import { uxManager } from './uxManager'; + +/** + * @name LuigiClient + * @private + */ +class LuigiClient { + constructor() { + if (window !== window.top) { + if ( + window.document.head.getAttribute('disable-luigi-history-handling') !== + 'true' + ) { + history.pushState = history.replaceState.bind(history); + } + + if ( + window.document.head.getAttribute( + 'disable-luigi-runtime-error-handling' + ) !== 'true' + ) { + window.addEventListener( + 'error', + ({ filename, message, lineno, colno, error }: ErrorEvent) => { + const msg: PostMessageData = { + errorObj: { filename, message, lineno, colno, error }, + msg: 'luigi-runtime-error-handling' + }; + + helpers.sendPostMessageToLuigiCore(msg); + } + ); + } + } + } + + addInitListener( + initFn: (context: InternalContext, origin?: string) => void + ): number { + return lifecycleManager.addInitListener(initFn); + } + + removeInitListener(id: string): boolean { + return lifecycleManager.removeInitListener(id); + } + + addContextUpdateListener( + contextUpdatedFn: (context: InternalContext) => void + ): string { + return lifecycleManager.addContextUpdateListener(contextUpdatedFn); + } + + removeContextUpdateListener(id: string): boolean { + return lifecycleManager.removeContextUpdateListener(id); + } + + getToken(): AuthData['accessToken'] { + return lifecycleManager.getToken(); + } + + getEventData(): InternalContext { + return lifecycleManager.getEventData(); + } + + getContext(): InternalContext { + return lifecycleManager.getContext(); + } + + addNodeParams(params: NodeParams, keepBrowserHistory: boolean): void { + return lifecycleManager.addNodeParams(params, keepBrowserHistory); + } + + getNodeParams(shouldDesanitise?: boolean): NodeParams { + return lifecycleManager.getNodeParams(shouldDesanitise); + } + + getActiveFeatureToggles(): string[] { + return lifecycleManager.getActiveFeatureToggles(); + } + + getPathParams(): PathParams { + return lifecycleManager.getPathParams(); + } + + getCoreSearchParams(): CoreSearchParams { + return lifecycleManager.getCoreSearchParams(); + } + + addCoreSearchParams( + searchParams: CoreSearchParams, + keepBrowserHistory: boolean + ): void { + return lifecycleManager.addCoreSearchParams( + searchParams, + keepBrowserHistory + ); + } + + getClientPermissions(): ClientPermissions { + return lifecycleManager.getClientPermissions(); + } + + sendCustomMessage(message: Object): void { + return lifecycleManager.sendCustomMessage(message); + } + + addCustomMessageListener( + messageId: string, + listener: (customMessage: Object, listenerId: string) => void + ): string { + return lifecycleManager.addCustomMessageListener(messageId, listener); + } + + removeCustomMessageListener(listenerId: string): boolean { + return lifecycleManager.removeCustomMessageListener(listenerId); + } + + addInactiveListener(inactiveFn: () => void): string { + return lifecycleManager.addInactiveListener(inactiveFn); + } + + removeInactiveListener(listenerId: string): boolean { + return lifecycleManager.removeInactiveListener(listenerId); + } + + setTargetOrigin(origin: string): void { + return lifecycleManager.setTargetOrigin(origin); + } + + getUserSettings(): UserSettings { + return lifecycleManager.getUserSettings(); + } + + isLuigiClientInitialized(): boolean { + return lifecycleManager.isLuigiClientInitialized(); + } + + luigiClientInit(): void { + return lifecycleManager.luigiClientInit(); + } + + getAnchor(): string { + return lifecycleManager.getAnchor(); + } + + setAnchor(value: string): void { + return lifecycleManager.setAnchor(value); + } + + setViewGroupData(value: Object): void { + return lifecycleManager.setViewGroupData(value); + } + + /** + * @private + */ + linkManager(): LinkManager { + return new linkManager({ + currentContext: lifecycleManager.currentContext + }); + } + + /** + * @private + */ + uxManager(): UxManager { + return uxManager; + } + + /** + * @private + */ + lifecycleManager(): any { + return lifecycleManager; + } + + /** + * @private + */ + storageManager(): StorageManager { + return storageManager; + } +} + +window.LuigiClient = new LuigiClient(); diff --git a/client/src/luigi-element.js b/client/src/luigi-element.ts similarity index 53% rename from client/src/luigi-element.js rename to client/src/luigi-element.ts index b917fb8149..f0bac863a9 100644 --- a/client/src/luigi-element.js +++ b/client/src/luigi-element.ts @@ -1,158 +1,186 @@ -/** - * Base class for Luigi web component micro frontends. - */ -export class LuigiElement extends HTMLElement { - constructor(options) { - super(); - const openShadow = options ? options.openShadow : false; - this._shadowRoot = this.attachShadow({ - mode: openShadow ? 'open' : 'closed', - delegatesFocus: false - }); - this.__initialized = false; - this.deferLuigiClientWCInit = options ? options.deferLuigiClientWCInit : false; - } - - /** - * Invoked by luigi core if present, internal, don't override. - * @private - */ - __postProcess(ctx, luigiClient, module_location_path) { - this.LuigiClient = luigiClient; - this.context = ctx; - const template = document.createElement('template'); - template.innerHTML = this.render(ctx); - const attCnt = () => { - if (!this.__initialized) { - this._shadowRoot.appendChild(template.content.cloneNode(true)); - Reflect.ownKeys(Reflect.getPrototypeOf(this)).forEach(el => { - if (el.startsWith('$_')) { - this._shadowRoot[el] = this[el].bind(this); - } - }); - const elementsWithIds = this._shadowRoot.querySelectorAll('[id]'); - if (elementsWithIds) { - elementsWithIds.forEach(el => { - this['$' + el.getAttribute('id')] = el; - }); - } - this.afterInit(ctx); - this.__initialized = true; - } - }; - if (this.luigiConfig && this.luigiConfig.styleSources && this.luigiConfig.styleSources.length > 0) { - let nr_styles = this.luigiConfig.styleSources.length; - const loadStylesSync = this.luigiConfig.loadStylesSync; - const afterLoadOrError = () => { - nr_styles--; - if (nr_styles < 1) { - attCnt(); - } - }; - - this.luigiConfig.styleSources.forEach((element, index) => { - const link = document.createElement('link'); - link.setAttribute('rel', 'stylesheet'); - link.setAttribute('href', module_location_path + element); - if (loadStylesSync) { - link.addEventListener('load', afterLoadOrError); - link.addEventListener('error', afterLoadOrError); - } - this._shadowRoot.appendChild(link); - }); - if (!loadStylesSync) { - attCnt(); - } - } else { - attCnt(); - } - } - - /** - * Override to execute logic after initialization of the web component, i.e. - * after internal rendering and all context data set. - * - * @param {*} ctx The context object passed by luigi core - */ - afterInit(ctx) { - return; - } - - /** - * Override to return the html template string defining the web component view. - * - * @param {*} ctx The context object passed by luigi core - */ - render(ctx) { - return ''; - } - - /** - * Override to execute logic after an attribute of this web component has changed. - */ - update() { - return; - } - - /** - * Override to execute logic when a new context object is set. - * - * @param {*} ctx The new context object passed by luigi core - */ - onContextUpdate(ctx) { - return; - } - - /** - * Query selector operating on shadow root. - * - * @see ParentNode.querySelector - */ - querySelector(selector) { - return this._shadowRoot.querySelector(selector); - } - - /** - * Handles changes on the context property. - * - * @private - */ - set context(ctx) { - this.__lui_ctx = ctx; - if (this.__initialized) { - this.onContextUpdate(ctx); - this.attributeChangedCallback(); - } - } - - get context() { - return this.__lui_ctx; - } - - /** - * Handles changes on attributes. - * - * @private - */ - attributeChangedCallback(name, oldVal, newVal) { - this.update(); - } -} - -/** - * Html string processing according to luigi functionality. - * Also useful in combination with LitElement VS Code plugins. - * - * @param {String} literal The literal to process. - * @returns {String} Returns the processed literal. - */ -export function html(literal, ...keys) { - let html = ''; - literal.forEach((el, index) => { - html += el; - if (index < keys.length && keys[index] !== undefined && keys[index] !== null) { - html += keys[index]; - } - }); - return html.replace(/\$\_/gi, 'this.getRootNode().$_'); -} +/** + * Base class for Luigi web component micro frontends. + */ +export class LuigiElement extends HTMLElement { + private deferLuigiClientWCInit: boolean; + private LuigiClient!: any; + private luigiConfig!: Record; + private _shadowRoot: ShadowRoot; + private __initialized: boolean; + private __lui_ctx!: Record; + + constructor(options: Record) { + super(); + + const openShadow: boolean = options['openShadow'] || false; + + this._shadowRoot = this.attachShadow({ + mode: openShadow ? 'open' : 'closed', + delegatesFocus: false + }); + this.__initialized = false; + this.deferLuigiClientWCInit = options['deferLuigiClientWCInit'] || false; + } + + /** + * Invoked by luigi core if present, internal, don't override. + * @private + */ + __postProcess(ctx: Record, luigiClient: any, module_location_path: string): void { + this.LuigiClient = luigiClient; + this.context = ctx; + + const template: HTMLTemplateElement = document.createElement('template'); + + template.innerHTML = this.render(ctx); + + const attCnt = (): void => { + if (this.__initialized) { + return; + } + + this._shadowRoot.appendChild(template.content.cloneNode(true)); + Reflect.ownKeys(Reflect.getPrototypeOf(this) as any).forEach((el: string | symbol) => { + if (typeof el === 'string' && el.startsWith('$_')) { + // @ts-ignore + this._shadowRoot[el] = this[el].bind(this); + } + }); + + const elementsWithIds: NodeListOf = this._shadowRoot.querySelectorAll('[id]'); + + if (elementsWithIds) { + elementsWithIds.forEach((el: Element) => { + // @ts-ignore + this['$' + el.getAttribute('id')] = el; + }); + } + this.afterInit(ctx); + this.__initialized = true; + }; + + if (this.luigiConfig && this.luigiConfig['styleSources']?.length) { + let nr_styles: number = this.luigiConfig['styleSources'].length; + const loadStylesSync: boolean = this.luigiConfig['loadStylesSync']; + const afterLoadOrError = (): void => { + nr_styles--; + + if (nr_styles < 1) { + attCnt(); + } + }; + + this.luigiConfig['styleSources']?.forEach((element: string) => { + const link: HTMLLinkElement = document.createElement('link'); + + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('href', module_location_path + element); + + if (loadStylesSync) { + link.addEventListener('load', afterLoadOrError); + link.addEventListener('error', afterLoadOrError); + } + + this._shadowRoot.appendChild(link); + }); + + if (!loadStylesSync) { + attCnt(); + } + } else { + attCnt(); + } + } + + /** + * Override to execute logic after initialization of the web component, i.e. + * after internal rendering and all context data set. + * + * @param {*} ctx The context object passed by luigi core + */ + afterInit(ctx: Record): void { + return; + } + + /** + * Override to return the html template string defining the web component view. + * + * @param {*} ctx The context object passed by luigi core + */ + render(ctx: Record): string { + return ''; + } + + /** + * Override to execute logic after an attribute of this web component has changed. + */ + update(): void { + return; + } + + /** + * Override to execute logic when a new context object is set. + * + * @param {*} ctx The new context object passed by luigi core + */ + onContextUpdate(ctx: Record): void { + return; + } + + /** + * Query selector operating on shadow root. + * + * @see ParentNode.querySelector + */ + override querySelector(selector: string): HTMLElement | null { + return this._shadowRoot.querySelector(selector); + } + + /** + * Handles changes on the context property. + * + * @private + */ + set context(ctx: Record) { + this.__lui_ctx = ctx; + + if (this.__initialized) { + this.onContextUpdate(ctx); + this.attributeChangedCallback(); + } + } + + get context(): Record { + return this.__lui_ctx; + } + + /** + * Handles changes on attributes. + * + * @private + */ + attributeChangedCallback(name?: string, oldVal?: any, newVal?: any): void { + this.update(); + } +} + +/** + * Html string processing according to luigi functionality. + * Also useful in combination with LitElement VS Code plugins. + * + * @param {String} literal The literal to process. + * @returns {String} Returns the processed literal. + */ +export function html(literal: string, ...keys: any[]): string { + let html: string = ''; + + [...literal].forEach((el: string, index: number) => { + html += el; + + if (index < keys.length && keys[index] !== undefined && keys[index] !== null) { + html += keys[index]; + } + }); + + return html.replace(/\$\_/gi, 'this.getRootNode().$_'); +} diff --git a/client/src/splitViewHandle.js b/client/src/splitViewHandle.ts similarity index 66% rename from client/src/splitViewHandle.js rename to client/src/splitViewHandle.ts index 02344159fa..a1bf9f84f0 100644 --- a/client/src/splitViewHandle.js +++ b/client/src/splitViewHandle.ts @@ -1,9 +1,10 @@ +import { SplitViewEvents } from '../luigi-client'; import { LuigiClientBase } from './baseClass'; import { helpers } from './helpers'; /** - * Split view - Allows to open a micro frontend in a split screen in the lower part of the content area. Open it by calling `const splitViewHandle = LuigiClient.linkManager().openAsSplitView`. + * Split view + Allows to open a micro frontend in a split screen in the lower part of the content area. Open it by calling `const splitViewHandle = LuigiClient.linkManager().openAsSplitView`. At a given time, you can open only one split view. It closes automatically when you navigate to a different route. When you call `handle.collapse()`, the split view gets destroyed. It recreates when you use `handle.expand()`. `openAsSplitView` returns an instance of the split view handle. The functions, actions, and event handlers listed below allow you to control and manage the split view. @@ -11,45 +12,43 @@ import { helpers } from './helpers'; * @since 0.6.0 */ export class splitViewHandle extends LuigiClientBase { + private splitView: Record; + private validSplitViewEvents: string[] = [ + 'close', + 'collapse', + 'expand', + 'resize' + ]; + /** * @private */ - constructor(settings) { + constructor(settings: Record) { super(); - this.validSplitViewEvents = ['expand', 'collapse', 'resize', 'close']; - this.splitView = { + collapsed: false, exists: true, - size: 40, - collapsed: false + size: 40 }; Object.assign(this.splitView, settings); - const removeSplitViewListeners = () => { - this.splitView.listeners.forEach(id => helpers.removeEventListener(id)); + const removeSplitViewListeners = (): void => { + this.splitView['listeners'].forEach((id: string) => + helpers.removeEventListener(id) + ); }; - this.splitView.listeners = [ - helpers.addEventListener(`luigi.navigation.splitview.internal`, e => { - Object.assign(this.splitView, e.data.data); - }) + this.splitView['listeners'] = [ + helpers.addEventListener( + `luigi.navigation.splitview.internal`, + (event: any): void => Object.assign(this.splitView, event.data.data) + ) ]; - this.on('resize', newSize => { - this.splitView.size = newSize; - }); + this.on('resize', (newSize: any) => (this.splitView['size'] = newSize)); this.on('close', removeSplitViewListeners); } - /* - * @private - */ - sendSplitViewEvent(action, data) { - helpers.sendPostMessageToLuigiCore({ - msg: `luigi.navigation.splitview.${action}`, - data - }); - } /** * Collapses the split view @@ -58,9 +57,10 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.collapse(); */ - collapse() { + collapse(): void { this.sendSplitViewEvent('collapse'); } + /** * Expands the split view * @memberof splitView @@ -68,7 +68,8 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.expand(); */ - expand() { + + expand(): void { this.sendSplitViewEvent('expand'); } @@ -79,9 +80,10 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.close(); */ - close() { + close(): void { this.sendSplitViewEvent('close'); } + /** * Sets the height of the split view * @memberof splitView @@ -90,9 +92,10 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.setSize(60); */ - setSize(value) { + setSize(value: number): void { this.sendSplitViewEvent('resize', value); } + /** * Registers a listener for split view events * @memberof splitView @@ -106,27 +109,40 @@ export class splitViewHandle extends LuigiClientBase { * const listenerId = splitViewHandle.on('resize', () => {}); * const listenerId = splitViewHandle.on('close', () => {}); **/ - on(name, callback) { + on( + name: SplitViewEvents, + callback: (param: number | undefined) => void + ): boolean | string { if (!this.validSplitViewEvents.includes(name)) { console.warn(name + ' is not a valid split view event'); return false; } - const id = helpers.addEventListener(`luigi.navigation.splitview.${name}.ok`, e => { - const filterParam = typeof e.data.data == 'number' ? e.data.data : undefined; - callback(filterParam); - }); - this.splitView.listeners.push(id); + + const id: string = helpers.addEventListener( + `luigi.navigation.splitview.${name}.ok`, + (event: any) => { + const filterParam: number | undefined = + typeof event.data.data == 'number' ? event.data.data : undefined; + + callback(filterParam); + } + ); + + this.splitView['listeners'].push(id); + return id; } + /** * Unregisters a split view listener * @memberof splitView * @param {string} id listener id + * @returns {boolean} * @since 0.6.0 * @example * splitViewHandle.removeEventListener(listenerId); */ - removeEventListener(id) { + removeEventListener(id: string): boolean { return helpers.removeEventListener(id); } @@ -138,9 +154,10 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.exists(); */ - exists() { - return this.splitView.exists; + exists(): boolean { + return this.splitView['exists']; } + /** * Reads the size of the split view * @memberof splitView @@ -149,9 +166,10 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.getSize(); */ - getSize() { - return this.splitView.size; + getSize(): number { + return this.splitView['size']; } + /** * Reads the collapse status * @memberof splitView @@ -160,9 +178,10 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.isCollapsed(); */ - isCollapsed() { - return this.splitView.collapsed; + isCollapsed(): boolean { + return this.splitView['collapsed']; } + /** * Reads the expand status * @memberof splitView @@ -171,7 +190,14 @@ export class splitViewHandle extends LuigiClientBase { * @example * splitViewHandle.isExpanded(); */ - isExpanded() { - return !this.splitView.collapsed; + isExpanded(): boolean { + return !this.splitView['collapsed']; + } + + private sendSplitViewEvent(action: string, data?: any): void { + helpers.sendPostMessageToLuigiCore({ + data: data || null, + msg: `luigi.navigation.splitview.${action}` + }); } } diff --git a/client/src/storageManager.js b/client/src/storageManager.ts similarity index 73% rename from client/src/storageManager.js rename to client/src/storageManager.ts index 2a7a8a1385..ab3456c037 100644 --- a/client/src/storageManager.js +++ b/client/src/storageManager.ts @@ -1,7 +1,8 @@ import { LuigiClientBase } from './baseClass'; import { helpers } from './helpers'; -const pendingOperation = new Map(); +const pendingOperation: Map = new Map(); +const syncOperation: Map = new Map(); /** * StorageManager allows you to use browser local storage of key/values. Every storage operation is sent to be managed by Luigi Core. @@ -10,11 +11,16 @@ const pendingOperation = new Map(); * @name storageManager */ class StorageManager extends LuigiClientBase { + private storageEventProcessor: StorageEventProcessor; + /** @private */ constructor() { super(); + this.storageEventProcessor = new StorageEventProcessor(); - helpers.addEventListener('storage', (evt, listenerId) => this.storageEventProcessor.processEvent(evt, listenerId)); + helpers.addEventListener('storage', (event: any) => + this.storageEventProcessor.processEvent(event) + ); } /** @@ -27,7 +33,7 @@ class StorageManager extends LuigiClientBase { * LuigiClient.storageManager().setItem('keyExample','valueExample').then(() => console.log('Value stored')) * @since 1.6.0 */ - setItem(key, value) { + setItem(key: string, value: Record): Promise { return new Promise((resolve, reject) => { this.storageEventProcessor.execute(resolve, reject, 'setItem', { key, @@ -45,7 +51,7 @@ class StorageManager extends LuigiClientBase { * LuigiClient.storageManager().getItem('keyExample').then((value) => console.log); * @since 1.6.0 */ - getItem(key) { + getItem(key: string): Promise> { return new Promise((resolve, reject) => { this.storageEventProcessor.execute(resolve, reject, 'getItem', { key }); }); @@ -60,7 +66,7 @@ class StorageManager extends LuigiClientBase { * LuigiClient.storageManager().removeItem('keyExample').then((value) => console.log(value + ' just removed') * @since 1.6.0 */ - removeItem(key) { + removeItem(key: string): Promise> { return new Promise((resolve, reject) => { this.storageEventProcessor.execute(resolve, reject, 'removeItem', { key @@ -76,7 +82,7 @@ class StorageManager extends LuigiClientBase { * LuigiClient.storageManager().clear().then(() => console.log('storage cleared')) * @since 1.6.0 */ - clear() { + clear(): Promise { return new Promise((resolve, reject) => { this.storageEventProcessor.execute(resolve, reject, 'clear', {}); }); @@ -91,7 +97,7 @@ class StorageManager extends LuigiClientBase { * LuigiClient.storageManager().has(key).then((present) => console.log('item is present '+present)) * @since 1.6.0 */ - has(key) { + has(key: string): Promise { return new Promise((resolve, reject) => { this.storageEventProcessor.execute(resolve, reject, 'has', { key }); }); @@ -105,7 +111,7 @@ class StorageManager extends LuigiClientBase { * LuigiClient.storageManager().getAllKeys().then((keys) => console.log('keys are '+keys)) * @since 1.6.0 */ - getAllKeys() { + getAllKeys(): Promise { return new Promise((resolve, reject) => { this.storageEventProcessor.execute(resolve, reject, 'getAllKeys', {}); }); @@ -113,51 +119,73 @@ class StorageManager extends LuigiClientBase { } class StorageEventProcessor { - processEvent(evt, listenerId) { + processEvent(event: any) { try { - const data = evt.data.data; - if (!pendingOperation.has(data.id)) { - console.log('Impossible to find Promise method for message ' + data.id); + const data: Record = event.data.data; + + if (!pendingOperation.has(data['id'])) { + console.log( + 'Impossible to find Promise method for message ' + data['id'] + ); return; } - const promiseOperations = pendingOperation.get(data.id); - if (data.status === 'ERROR') { - promiseOperations.reject(data.result); + + const promiseOperations: any = pendingOperation.get(data['id']); + + if (data['status'] === 'ERROR') { + promiseOperations.reject(data['result']); } else { - promiseOperations.resolve(data.result); + promiseOperations.resolve(data['result']); } - pendingOperation.delete(data.id); - } catch (e) { - console.error(e); + + pendingOperation.delete(data['id']); + } catch (error) { + console.error(error); } } - waitForSyncResult(id) { - let start = new Date().getTime(); + waitForSyncResult(id: number): any { + const start: number = new Date().getTime(); + while (!syncOperation.has(id)) { - let exec = new Date().getTime() - start; + const exec: number = new Date().getTime() - start; + if (exec > 10000) { - throw 'Storage operation is taking more than 1 second...Some problem with Luigi Core communication'; + throw 'Storage operation is taking more than 1 second... Some problem with Luigi Core communication'; } } - const result = syncOperation.get(id); + + const result: any = syncOperation.get(id); + pendingOperation.delete(id); + return result; } - execute(resolve, reject, operation, params) { - let id = helpers.getRandomId(); + execute( + resolve: (value?: any) => void, + reject: () => void, + operation: any, + params: any + ): void { + let id: number = helpers.getRandomId(); + this.createPendingOperation(id, resolve, reject); this.sendMessage(id, operation, params); } - createPendingOperation(id, resolve, reject) { + createPendingOperation( + id: number, + resolve: (value?: any) => void, + reject: () => void + ): void { pendingOperation.set(id, { resolve, reject }); } - sendMessage(id, operation, params) { + + sendMessage(id: number, operation: any, params: any): void { helpers.sendPostMessageToLuigiCore({ msg: 'storage', data: { diff --git a/client/src/uxManager.js b/client/src/uxManager.ts similarity index 74% rename from client/src/uxManager.js rename to client/src/uxManager.ts index 187743eaed..77e72c803d 100644 --- a/client/src/uxManager.js +++ b/client/src/uxManager.ts @@ -1,6 +1,7 @@ +import { AlertSettings, ConfirmationModalSettings } from '../luigi-client'; import { LuigiClientBase } from './baseClass'; -import { lifecycleManager } from './lifecycleManager'; import { helpers } from './helpers'; +import { lifecycleManager } from './lifecycleManager'; /** * Use the UX Manager to manage the appearance features in Luigi. @@ -10,9 +11,15 @@ class UxManager extends LuigiClientBase { /** @private */ constructor() { super(); - helpers.addEventListener('luigi.current-locale-changed', e => { - if (e.data.currentLocale && lifecycleManager.currentContext?.internal) { - lifecycleManager.currentContext.internal.currentLocale = e.data.currentLocale; + + helpers.addEventListener('luigi.current-locale-changed', (event: any) => { + if ( + event.data.currentLocale && + lifecycleManager.currentContext?.internal + ) { + (lifecycleManager.currentContext.internal as Record)[ + 'currentLocale' + ] = event.data.currentLocale; lifecycleManager._notifyUpdate(); } }); @@ -22,7 +29,7 @@ class UxManager extends LuigiClientBase { * Adds a backdrop with a loading indicator for the micro frontend frame. This overrides the {@link navigation-parameters-reference.md#node-parameters loadingIndicator.enabled} setting. * @memberof uxManager */ - showLoadingIndicator() { + showLoadingIndicator(): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.show-loading-indicator' }); } @@ -30,7 +37,7 @@ class UxManager extends LuigiClientBase { * Removes the loading indicator. Use it after calling {@link #showLoadingIndicator showLoadingIndicator()} or to hide the indicator when you use the {@link navigation-parameters-reference.md#node-parameters loadingIndicator.hideAutomatically: false} node configuration. * @memberof uxManager */ - hideLoadingIndicator() { + hideLoadingIndicator(): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.hide-loading-indicator' }); } @@ -38,7 +45,7 @@ class UxManager extends LuigiClientBase { * Closes the currently opened micro frontend modal. * @memberof uxManager */ - closeCurrentModal() { + closeCurrentModal(): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.close-modal' }); } @@ -46,27 +53,30 @@ class UxManager extends LuigiClientBase { * Adds a backdrop to block the top and side navigation. It is based on the Fundamental UI Modal, which you can use in your micro frontend to achieve the same behavior. * @memberof uxManager */ - addBackdrop() { + addBackdrop(): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.add-backdrop' }); } + /** * Removes the backdrop. * @memberof uxManager */ - removeBackdrop() { + removeBackdrop(): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.remove-backdrop' }); } + /** * This method informs the main application that there are unsaved changes in the current view in the iframe. It can be used to prevent navigation away from the current view, for example with form fields which were edited but not submitted. However, this functionality is not restricted to forms. If you use `withoutSync()` together with `setDirtyStatus()`, this is a special case in which the dirty state logic needs to be handled by the micro frontend. For example, if the user navigates with an Angular router, which would trigger `withoutSync()`, Angular needs to take care about dirty state, prevent the navigation and ask for permission to navigate away, through `uxManager().showConfirmationModal(settings)`. * @param {boolean} isDirty indicates if there are any unsaved changes on the current page or in the component * @memberof uxManager */ - setDirtyStatus(isDirty) { + setDirtyStatus(isDirty: boolean): void { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.set-page-dirty', dirty: isDirty }); } + /** * Shows a confirmation modal. * @memberof uxManager @@ -93,23 +103,28 @@ class UxManager extends LuigiClientBase { * // Logic to execute when the confirmation modal is dismissed * }); */ - showConfirmationModal(settings) { - helpers.addEventListener('luigi.ux.confirmationModal.hide', (e, listenerId) => { - this.hideConfirmationModal(e.data.data); - helpers.removeEventListener(listenerId); - }); + showConfirmationModal(settings: ConfirmationModalSettings): Promise { + helpers.addEventListener( + 'luigi.ux.confirmationModal.hide', + (event, listenerId) => { + this.hideConfirmationModal(event.data.data); + helpers.removeEventListener(listenerId); + } + ); helpers.sendPostMessageToLuigiCore({ msg: 'luigi.ux.confirmationModal.show', data: { settings } }); - const confirmationModalPromise = {}; - confirmationModalPromise.promise = new Promise((resolve, reject) => { - confirmationModalPromise.resolveFn = resolve; - confirmationModalPromise.rejectFn = reject; + const confirmationModalPromise: Record = {}; + + confirmationModalPromise['promise'] = new Promise((resolve, reject) => { + confirmationModalPromise['resolveFn'] = resolve; + confirmationModalPromise['rejectFn'] = reject; }); this.setPromise('confirmationModal', confirmationModalPromise); - return confirmationModalPromise.promise; + + return confirmationModalPromise['promise']; } /** @@ -117,10 +132,11 @@ class UxManager extends LuigiClientBase { * @memberof uxManager * @param {Object} modal confirmed boolean value if ok or cancel has been pressed */ - hideConfirmationModal(modal) { + hideConfirmationModal(modal: Record): void { const promise = this.getPromise('confirmationModal'); + if (promise) { - modal.confirmed ? promise.resolveFn() : promise.rejectFn(); + modal['confirmed'] ? promise.resolveFn() : promise.rejectFn(); this.setPromise('confirmationModal', undefined); } } @@ -158,34 +174,44 @@ class UxManager extends LuigiClientBase { * // Logic to execute when the alert is dismissed * }); */ - showAlert(settings) { - //generate random ID - settings.id = helpers.getRandomId(); + showAlert(config: AlertSettings): Promise { + const settings: AlertSettings & { id: string } = { + ...config + } as AlertSettings & { id: string }; + + // generate random ID + settings.id = helpers.getRandomId().toString(); - helpers.addEventListener('luigi.ux.alert.hide', (e, listenerId) => { - if (e.data.id === settings.id) { - this.hideAlert(e.data); + helpers.addEventListener('luigi.ux.alert.hide', (event, listenerId) => { + if (event.data.id === settings.id) { + this.hideAlert(event.data); helpers.removeEventListener(listenerId); } }); - if (settings?.closeAfter < 100) { - console.warn(`Message with id='${settings.id}' has too small 'closeAfter' value. It needs to be at least 100ms.`); + if (settings?.closeAfter && settings?.closeAfter < 100) { + console.warn( + `Message with id='${settings.id}' has too small 'closeAfter' value. It needs to be at least 100ms.` + ); settings.closeAfter = undefined; } + helpers.sendPostMessageToLuigiCore({ msg: 'luigi.ux.alert.show', data: { settings } }); const alertPromises = this.getPromise('alerts') || {}; + alertPromises[settings.id] = {}; alertPromises[settings.id].promise = new Promise(resolve => { alertPromises[settings.id].resolveFn = resolve; }); this.setPromise('alerts', alertPromises); + return alertPromises[settings.id].promise; } + /** * @private * @memberof uxManager @@ -193,8 +219,9 @@ class UxManager extends LuigiClientBase { * @param {string} alertObj.id alert id * @param {string} alertObj.dismissKey key of the link */ - hideAlert({ id, dismissKey }) { + hideAlert({ id, dismissKey }: Record): void { const alerts = this.getPromise('alerts'); + if (id && alerts[id]) { alerts[id].resolveFn(dismissKey ? dismissKey : id); delete alerts[id]; @@ -207,8 +234,10 @@ class UxManager extends LuigiClientBase { * @returns {string} current locale * @memberof uxManager */ - getCurrentLocale() { - return lifecycleManager.currentContext?.internal?.currentLocale; + getCurrentLocale(): string { + return (lifecycleManager.currentContext?.internal as Record)[ + 'currentLocale' + ]; } /** @@ -219,7 +248,7 @@ class UxManager extends LuigiClientBase { * @param {string} locale locale to be set as the current locale * @memberof uxManager */ - setCurrentLocale(locale) { + setCurrentLocale(locale: string): void { if (locale) { helpers.sendPostMessageToLuigiCore({ msg: 'luigi.ux.set-current-locale', @@ -236,8 +265,10 @@ class UxManager extends LuigiClientBase { * @memberof uxManager * @since 0.6.0 */ - isSplitView() { - return lifecycleManager.currentContext?.internal?.splitView; + isSplitView(): boolean { + return (lifecycleManager.currentContext?.internal as Record)[ + 'splitView' + ]; } /** @@ -246,8 +277,10 @@ class UxManager extends LuigiClientBase { * @memberof uxManager * @since 0.6.0 */ - isModal() { - return lifecycleManager.currentContext?.internal?.modal; + isModal(): boolean { + return (lifecycleManager.currentContext?.internal as Record)[ + 'modal' + ]; } /** @@ -256,8 +289,10 @@ class UxManager extends LuigiClientBase { * @memberof uxManager * @since 1.26.0 */ - isDrawer() { - return lifecycleManager.currentContext?.internal?.drawer; + isDrawer(): boolean { + return (lifecycleManager.currentContext?.internal as Record)[ + 'drawer' + ]; } /** @@ -265,8 +300,10 @@ class UxManager extends LuigiClientBase { * @returns {*} current themeObj * @memberof uxManager */ - getCurrentTheme() { - return lifecycleManager.currentContext?.internal?.currentTheme; + getCurrentTheme(): any { + return (lifecycleManager.currentContext?.internal as Record)[ + 'currentTheme' + ]; } /** @@ -276,8 +313,12 @@ class UxManager extends LuigiClientBase { * @since 2.3.0 * @example LuigiClient.uxManager().getCSSVariables(); */ - getCSSVariables() { - return lifecycleManager.currentContext?.internal?.cssVariables || {}; + getCSSVariables(): Object { + return ( + (lifecycleManager.currentContext?.internal as Record)[ + 'cssVariables' + ] || {} + ); } /** @@ -286,23 +327,36 @@ class UxManager extends LuigiClientBase { * @since 2.3.0 * @example LuigiClient.uxManager().applyCSS(); */ - applyCSS() { - document.querySelectorAll('head style[luigi-injected]').forEach(luigiInjectedStyleTag => { - luigiInjectedStyleTag.remove(); - }); - const vars = lifecycleManager.currentContext?.internal?.cssVariables; + applyCSS(): void { + document + .querySelectorAll('head style[luigi-injected]') + .forEach(luigiInjectedStyleTag => { + luigiInjectedStyleTag.remove(); + }); + + const vars = (lifecycleManager.currentContext?.internal as Record< + string, + any + >)['cssVariables']; + if (vars) { let cssString = ':root {\n'; + Object.keys(vars).forEach(key => { const val = vars[key]; - cssString += (key.startsWith('--') ? '' : '--') + key + ':' + val + ';\n'; + + cssString += + (key.startsWith('--') ? '' : '--') + key + ':' + val + ';\n'; }); cssString += '}'; - const themeStyle = document.createElement('style'); - themeStyle.setAttribute('luigi-injected', true); + + const themeStyle: HTMLStyleElement = document.createElement('style'); + + themeStyle.setAttribute('luigi-injected', 'true'); themeStyle.innerHTML = cssString; document.head.appendChild(themeStyle); } } } + export const uxManager = new UxManager(); diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000000..7d4b92f682 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "declaration": false, + "esModuleInterop": true, + "experimentalDecorators": true, + "importHelpers": true, + "lib": ["ES2022", "dom"], + "module": "ES2022", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "outDir": "./public/", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "strictNullChecks": true, + "target": "ES2022", + "useDefineForClassFields": false + } +} diff --git a/client/webpack.config.js b/client/webpack.config.js index 7c160fdf06..d48c7f24ad 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -1,30 +1,29 @@ const path = require('path'); -const { readFileSync } = require('fs'); const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { entry: { - luigiClient: './src/luigi-client.js' + 'luigi-client': './src/luigi-client.ts', + 'luigi-element': './src/luigi-element.ts' }, - output: { - filename: 'luigi-client.js', + filename: '[name].js', libraryExport: 'default', - library: 'LuigiClient', libraryTarget: 'umd', path: path.join(path.resolve(__dirname), 'public') }, - module: { rules: [ { - test: /\.js$/, + test: /\.ts$/, exclude: /node_modules/, - loader: 'babel-loader' + loader: 'ts-loader' } ] }, - + resolve: { + extensions: ['.ts', '.js'] + }, plugins: [ new CopyWebpackPlugin([ { @@ -34,10 +33,6 @@ module.exports = { { from: 'luigi-element.d.ts', to: '.' - }, - { - from: 'src/luigi-element.js', - to: '.' } ]) ], diff --git a/container/package.json b/container/package.json index 31c0a3ae58..bd5a6670bd 100644 --- a/container/package.json +++ b/container/package.json @@ -18,16 +18,16 @@ "bundle": "npm run build", "dev": "rollup -c -w", "copyBundle": "cp public/bundle.js public/bundle.js.map test-app/ && cp public/bundle.js public/bundle.js.map examples/ || COPY public\\* test-app\\", - "copyLuigiElement": "cp ../client/src/luigi-element.js test-app/compound", + "copyLuigiElement": "cp ../client/public/luigi-element.js test-app/compound", "serve": "npm run build && npm run copyLuigiElement && npm run copyBundle && sirv -D -c test-app --no-clear", "bundle:watch": "chokidar \"src/**/*.*\" -c \"npm run build && npm run copyBundle\"", "start": "concurrently -k \"npm run serve\" \"npm run bundle:watch\"", - "start-examples":"npm run copyBundle && sirv -D -c examples --no-clear", - "start-examples-test":"npm run copyBundle && sirv -D -c examples --no-clear --port 2222", + "start-examples": "npm run copyBundle && sirv -D -c examples --no-clear", + "start-examples-test": "npm run copyBundle && sirv -D -c examples --no-clear --port 2222", "cypress-headless": "cypress run -c video=false", "cypress-browser": "cypress open --e2e --browser chrome -c video=false", "prepare-release": "node prepareNextRelease.js", - "sync-event-typings":"cp src/constants/communication.ts typings/constants/events.d.ts" + "sync-event-typings": "cp src/constants/communication.ts typings/constants/events.d.ts" }, "devDependencies": { "@babel/node": "7.22.10", @@ -62,4 +62,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/docs/luigi-client-api.md b/docs/luigi-client-api.md index 0919cfb06d..25921db690 100644 --- a/docs/luigi-client-api.md +++ b/docs/luigi-client-api.md @@ -28,1319 +28,3 @@ This document outlines the features provided by the Luigi Client API. It covers ## API Reference - -### Lifecycle - -Use the functions and parameters to define the Lifecycle of listeners, navigation nodes, and Event data. - -#### isLuigiClientInitialized - -Check if LuigiClient is initialized - -##### Examples - -```javascript -const init = LuigiClient.isLuigiClientInitialized() -``` - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** client initialized state - -**Meta** - -- **since**: 1.12.0 - -#### luigiClientInit - -Starts the handshake with Luigi Core and thereafter results in initialization of Luigi Client. It is always ran by default when importing the Luigi Client package in your micro frontend. Note that when using `defer-luigi-init` to defer default initialization, you will need to initialize the handshake using this function manually wherever needed. - -##### Examples - -```javascript -LuigiClient.luigiClientInit() -``` - -**Meta** - -- **since**: 1.12.0 - -#### addInitListener - -Registers a listener called with the context object and the Luigi Core domain as soon as Luigi is instantiated. Defer your application bootstrap if you depend on authentication data coming from Luigi. - -##### Parameters - -- `initFn` **[Lifecycle~initListenerCallback](#lifecycleinitlistenercallback)** the function that is called once Luigi is initialized, receives current context and origin as parameters - -##### Examples - -```javascript -const initListenerId = LuigiClient.addInitListener((context) => storeContextToMF(context)) -``` - -#### removeInitListener - -Removes an init listener. - -##### Parameters - -- `id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the id that was returned by the `addInitListener` function. - -##### Examples - -```javascript -LuigiClient.removeInitListener(initListenerId) -``` - -#### addContextUpdateListener - -Registers a listener called with the context object when the URL is changed. For example, you can use this when changing environments in a context switcher in order for the micro frontend to do an API call to the environment picked. - -##### Parameters - -- `contextUpdatedFn` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** the listener function called each time Luigi context changes - -##### Examples - -```javascript -const updateListenerId = LuigiClient.addContextUpdateListener((context) => storeContextToMF(context)) -``` - -#### removeContextUpdateListener - -Removes a context update listener. - -##### Parameters - -- `id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the id that was returned by the `addContextUpdateListener` function - -##### Examples - -```javascript -LuigiClient.removeContextUpdateListener(updateListenerId) -``` - -#### addInactiveListener - -Registers a listener called upon micro frontend inactivity. This happens when a new micro frontend gets shown while keeping the old one cached. -Gets called when: - -- navigating with **preserveView** -- navigating from or to a **viewGroup** - -Does not get called when navigating normally, or when `openAsModal` or `openAsSplitView` are used. -Once the micro frontend turns back into active state, the `addContextUpdateListener` receives an updated context. - -##### Parameters - -- `inactiveFn` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** the listener function called each time a micro frontend turns into an inactive state - -##### Examples - -```javascript -LuigiClient.addInactiveListener(() => mfIsInactive = true) -const inactiveListenerId = LuigiClient.addInactiveListener(() => mfIsInactive = true) -``` - -#### removeInactiveListener - -Removes a listener for inactive micro frontends. - -##### Parameters - -- `id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the id that was returned by the `addInactiveListener` function - -##### Examples - -```javascript -LuigiClient.removeInactiveListener(inactiveListenerId) -``` - -#### addCustomMessageListener - -Registers a listener called when the micro frontend receives a custom message. - -##### Parameters - -- `customMessageId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the custom message id -- `customMessageListener` **[Lifecycle~customMessageListenerCallback](#lifecyclecustommessagelistenercallback)** the function that is called when the micro frontend receives the corresponding event - -##### Examples - -```javascript -const customMsgId = LuigiClient.addCustomMessageListener('myapp.project-updated', (data) => doSomething(data)) -``` - -**Meta** - -- **since**: 0.6.2 - -#### removeCustomMessageListener - -Removes a custom message listener. - -##### Parameters - -- `id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the id that was returned by the `addInitListener` function - -**Meta** - -- **since**: 0.6.2 - LuigiClient.removeCustomMessageListener(customMsgId) - -#### getToken - -Returns the currently valid access token. - -##### Examples - -```javascript -const accessToken = LuigiClient.getToken() -``` - -Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** current access token - -#### getContext - -Returns the context object. Typically it is not required as the [addContextUpdateListener()](#addContextUpdateListener) receives the same values. - -##### Examples - -```javascript -const context = LuigiClient.getContext() -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** current context data - -#### getEventData - -Returns the context object. It is an alias function for getContext(). - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** current context data - -**Meta** - -- **deprecated**: This is deprecated. - - -#### getActiveFeatureToggles - -Returns a list of active feature toggles - -##### Examples - -```javascript -const activeFeatureToggleList = LuigiClient.getActiveFeatureToggles() -``` - -Returns **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** a list of feature toggle names - -**Meta** - -- **since**: 1.4.0 - -#### addNodeParams - -Sets node parameters in Luigi Core. The parameters will be added to the URL. - -##### Parameters - -- `params` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** -- `keepBrowserHistory` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`) - -##### Examples - -```javascript -LuigiClient.addNodeParams({luigi:'rocks'}, true); -``` - -#### getNodeParams - -Returns the node parameters of the active URL. -Node parameters are defined like URL query parameters but with a specific prefix allowing Luigi to pass them to the micro frontend view. The default prefix is **~** and you can use it in the following way: `https://my.luigi.app/home/products?~sort=asc&~page=3`. - - - -> **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in node parameters are HTML-encoded. - -##### Parameters - -- `shouldDesanitise` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** defines whether the specially encoded characters should be desanitised (optional, default `false`) - -##### Examples - -```javascript -const nodeParams = LuigiClient.getNodeParams() -const nodeParams = LuigiClient.getNodeParams(true) -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** node parameters, where the object property name is the node parameter name without the prefix, and its value is the value of the node parameter. For example `{sort: 'asc', page: 3}` - -#### getPathParams - -Returns the dynamic path parameters of the active URL. -Path parameters are defined by navigation nodes with a dynamic **pathSegment** value starting with **:**, such as **productId**. -All path parameters in the current navigation path (as defined by the active URL) are returned. - - - -> **NOTE:** some special characters (`<`, `>`, `"`, `'`, `/`) in path parameters are HTML-encoded. - -##### Examples - -```javascript -const pathParams = LuigiClient.getPathParams() -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** path parameters, where the object property name is the path parameter name without the prefix, and its value is the actual value of the path parameter. For example `{productId: 1234, ...}` - -#### getCoreSearchParams - -Read search query parameters which are sent from Luigi Core - -##### Examples - -```javascript -LuigiClient.getCoreSearchParams(); -``` - -Returns **any** Core search query parameters - -#### addCoreSearchParams - -Sends search query parameters to Luigi Core. The search parameters will be added to the URL if they are first allowed on a node level using [clientPermissions.urlParameters](navigation-parameters-reference.md#clientpermissionsurlparameters). - -##### Parameters - -- `searchParams` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** -- `keepBrowserHistory` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** (optional, default `true`) - -##### Examples - -```javascript -LuigiClient.addCoreSearchParams({luigi:'rocks'}, false); -``` - -#### getClientPermissions - -Returns the current client permissions as specified in the navigation node or an empty object. For details, see [Node parameters](navigation-parameters-reference.md). - -##### Examples - -```javascript -const permissions = LuigiClient.getClientPermissions() -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** client permissions as specified in the navigation node - -#### setTargetOrigin - -When the micro frontend is not embedded in the Luigi Core application and there is no init handshake you can set the target origin that is used in postMessage function calls by Luigi Client. Typically used only in custom micro-frontend frameworks that are compatible with LuigiClient API. - -##### Parameters - -- `origin` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** target origin - -##### Examples - -```javascript -LuigiClient.setTargetOrigin(window.location.origin) -``` - -**Meta** - -- **since**: 0.7.3 - -#### sendCustomMessage - -Sends a custom message to the Luigi Core application. - -##### Parameters - -- `message` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** an object containing data to be sent to the Luigi Core to process it further. This object is set as an input parameter of the custom message listener on the Luigi Core side - - `message.id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** a string containing the message id - - `message.MY_DATA_FIELD` **any** any other message data field - -##### Examples - -```javascript -LuigiClient.sendCustomMessage({id: 'environment.created', production: false}) -LuigiClient.sendCustomMessage({id: 'environment.created', data: environmentDataObj}) -``` - -**Meta** - -- **since**: 0.6.2 - -#### getUserSettings - -Returns the current user settings based on the selected node. - -##### Examples - -```javascript -const userSettings = LuigiClient.getUserSettings() -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** current user settings - -**Meta** - -- **since**: 1.7.1 - -#### getAnchor - -Returns the current anchor based on active URL. - -##### Examples - -```javascript -LuigiClient.getAnchor(); -``` - -Returns **any** anchor of URL - -**Meta** - -- **since**: 1.21.0 - -#### setAnchor - -Sends anchor to Luigi Core. The anchor will be added to the URL. - -##### Parameters - -- `anchor` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** - -##### Examples - -```javascript -LuigiClient.setAnchor('luigi'); -``` - -**Meta** - -- **since**: 1.21.0 - -#### setViewGroupData - -This function allows you to change node labels within the same [view group](navigation-advanced.md#view-groups), e.g. in your node config: `label: 'my Node {viewGroupData.vg1}'`. - -##### Parameters - -- `data` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** a data object containing the view group name and desired label - -##### Examples - -```javascript -LuigiClient.setViewGroupData({'vg1':' Luigi rocks!'}) -``` - -**Meta** - -- **since**: 2.2.0 - -### Lifecycle~initListenerCallback - -Callback of the addInitListener - -Type: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function) - -#### Parameters - -- `context` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** current context data -- `origin` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Luigi Core URL - -### Lifecycle~customMessageListenerCallback - -Callback of the customMessageListener - -Type: [Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function) - -#### Parameters - -- `customMessage` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** custom message object - - `customMessage.id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** message id - - `customMessage.MY_DATA_FIELD` **any** any other message data field -- `listenerId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** custom message listener id to be used for unsubscription - -### linkManager - -The Link Manager allows you to navigate to another route. Use it instead of an internal router to: - -- Provide routing inside micro frontends. -- Reflect the route. -- Keep the navigation state in Luigi. - -#### navigateToIntent - -Offers an alternative way of navigating with intents. This involves specifying a semanticSlug and an object containing -parameters. -This method internally generates a URL of the form `#?intent=-?=` through the given -input arguments. This then follows a call to the original `linkManager.navigate(...)` function. -Consequently, the following calls shall have the exact same effect: - -- linkManager().navigateToIntent('Sales-settings', {project: 'pr2', user: 'john'}) -- linkManager().navigate('/#?intent=Sales-settings?project=pr2&user=john') - -##### Parameters - -- `semanticSlug` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** concatenation of semantic object and action connected with a dash (-), i.e.: `-` -- `params` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** an object representing all the parameters passed, i.e.: `{param1: '1', param2: 2, param3: 'value3'}`. (optional, default `{}`) - -##### Examples - -```javascript -LuigiClient.linkManager().navigateToIntent('Sales-settings', {project: 'pr2', user: 'john'}) -LuigiClient.linkManager().navigateToIntent('Sales-settings') -``` - -#### withoutSync - -Disables the navigation handling for a single navigation request. -It prevents Luigi Core from handling the URL change after `navigate()`. -Used for auto-navigation. - -##### Examples - -```javascript -LuigiClient.linkManager().withoutSync().navigate('/projects/xy/foobar'); -LuigiClient.linkManager().withoutSync().fromClosestContext().navigate('settings'); -``` - -**Meta** - -- **since**: 0.7.7 - -#### newTab - -Enables navigating to a new tab. - -##### Examples - -```javascript -LuigiClient.linkManager().newTab().navigate('/projects/xy/foobar'); -``` - -**Meta** - -- **since**: 1.16.0 - -#### preserveQueryParams - -Keeps the URL's query parameters for a navigation request. - -##### Parameters - -- `preserve` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true`, the URL's query parameters will be kept after navigation. (optional, default `false`) - -##### Examples - -```javascript -LuigiClient.linkManager().preserveQueryParams(true).navigate('/projects/xy/foobar'); -LuigiClient.linkManager().preserveQueryParams(false).navigate('/projects/xy/foobar'); -``` - -**Meta** - -- **since**: 1.19.0 - -#### getCurrentRoute - -Gets the luigi route associated with the current micro frontend. - -##### Examples - -```javascript -LuigiClient.linkManager().getCurrentRoute(); -LuigiClient.linkManager().fromContext('project').getCurrentRoute(); -LuigiClient.linkManager().fromVirtualTreeRoot().getCurrentRoute(); -``` - -Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** a promise which resolves to a String value specifying the current luigi route - -**Meta** - -- **since**: 1.23.0 - -#### navigate - -Navigates to the given path in the application hosted by Luigi. It contains either a full absolute path or a relative path without a leading slash that uses the active route as a base. This is the standard navigation. - -##### Parameters - -- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** path to be navigated to -- `sessionId` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** current Luigi **sessionId** -- `preserveView` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** preserve a view by setting it to `true`. It keeps the current view opened in the background and opens the new route in a new frame. Use the [goBack()](#goBack) function to navigate back. You can use this feature across different levels. Preserved views are discarded as soon as you use the standard [navigate()](#navigate) function instead of [goBack()](#goBack) -- `modalSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** opens a view in a modal. Use these settings to configure the modal's title and size - - `modalSettings.title` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** modal title. By default, it is the node label. If there is no label, it is left empty - - `modalSettings.size` **(`"fullscreen"` \| `"l"` \| `"m"` \| `"s"`)** size of the modal (optional, default `"l"`) - - `modalSettings.width` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** updates the `width` of the modal. Allowed units are 'px', '%', 'rem', 'em', 'vh' and 'vw'. - - `modalSettings.height` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** updates the `height` of the modal. Allowed units are 'px', '%', 'rem', 'em', 'vh' and 'vw'. - - `modalSettings.keepPrevious` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Lets you open multiple modals. Keeps the previously opened modal and allows to open another modal on top of the previous one. By default the previous modals are discarded. - - `modalSettings.closebtn_data_testid` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** lets you specify a `data_testid` for the close button. Default value is `lui-modal-index-0`. If multiple modals are opened the index will be increased per modal. -- `splitViewSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** opens a view in a split view. Use these settings to configure the split view's behaviour - - `splitViewSettings.title` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** split view title. By default, it is the node label. If there is no label, it is left empty - - `splitViewSettings.size` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** height of the split view in percent (optional, default `40`) - - `splitViewSettings.collapsed` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** creates split view but leaves it closed initially (optional, default `false`) -- `drawerSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** opens a view in a drawer. Use these settings to configure if the drawer has a header, backdrop and size. - - `drawerSettings.header` **any** By default, the header is visible. The default title is the node label, but the header could also be an object with a `title` attribute allowing you to specify your own title. An 'x' icon is displayed to close the drawer view. - - `drawerSettings.backdrop` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true` the rest of the screen has a backdrop. - - `drawerSettings.size` **(`"l"` \| `"m"` \| `"s"` \| `"xs"`)** size of the drawer (optional, default `"s"`) - -##### Examples - -```javascript -LuigiClient.linkManager().navigate('/overview') -LuigiClient.linkManager().navigate('users/groups/stakeholders') -LuigiClient.linkManager().navigate('/settings', null, true) // preserve view -LuigiClient.linkManager().navigate('#?Intent=Sales-order?id=13') // intent navigation -``` - -#### updateModalPathInternalNavigation - -Updates path of the modalPathParam when internal navigation occurs. - -##### Parameters - -- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** -- `modalSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** opens a view in a modal. Use these settings to configure the modal's title and size (optional, default `{}`) -- `addHistoryEntry` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** adds an entry in the history (optional, default `false`) - -##### Examples - -```javascript -LuigiClient.linkManager().updateModalPathInternalNavigation('microfrontend') -``` - -**Meta** - -- **since**: 1.21.0 - -#### openAsModal - -Opens a view in a modal. You can specify the modal's title and size. If you don't specify the title, it is the node label. If there is no node label, the title remains empty. The default size of the modal is `l`, which means 80%. You can also use `m` (60%) and `s` (40%) to set the modal size. Optionally, use it in combination with any of the navigation functions. - -##### Parameters - -- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** navigation path -- `modalSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** opens a view in a modal. Use these settings to configure the modal's title and size (optional, default `{}`) - - `modalSettings.title` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** modal title. By default, it is the node label. If there is no label, it is left empty - - `modalSettings.size` **(`"fullscreen"` \| `"l"` \| `"m"` \| `"s"`)** size of the modal (optional, default `"l"`) - - `modalSettings.width` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** updates the `width` of the modal. Allowed units are 'px', '%', 'rem', 'em', 'vh' and 'vw'. - - `modalSettings.height` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** updates the `height` of the modal. Allowed units are 'px', '%', 'rem', 'em', 'vh' and 'vw'. - - `modalSettings.keepPrevious` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Lets you open multiple modals. Keeps the previously opened modal and allows to open another modal on top of the previous one. By default the previous modals are discarded. - - `modalSettings.closebtn_data_testid` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** lets you specify a `data_testid` for the close button. Default value is `lui-modal-index-0`. If multiple modals are opened the index will be increased per modal. - -##### Examples - -```javascript -LuigiClient.linkManager().openAsModal('projects/pr1/users', {title:'Users', size:'m'}).then((res) => { - // Logic to execute when the modal will be closed - console.log(res.data) //=> {foo: 'bar'} - }); -``` - -Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** which is resolved when closing the modal. By using LuigiClient.linkManager().goBack({ foo: 'bar' }) to close the modal you have access to the `goBackContext` when the promise will be resolved. - -#### updateModalSettings - -Updates the current title and size of a modal. If `routing.showModalPathInUrl` is set to `true`, the URL will be updated with the modal settings data. -In addition, you can specify if a new history entry will be created with the updated URL. - -##### Parameters - -- `updatedModalSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** possibility to update the active modal. (optional, default `{}`) - - `updatedModalSettings.title` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** update the `title` of the active modal. - - `updatedModalSettings.size` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** update the `size` of the active modal. - - `updatedModalSettings.width` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** updates the `width` of the modal. Allowed units are 'px', '%', 'rem', 'em', 'vh' and 'vw'. - - `updatedModalSettings.height` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** updates the `height` of the modal. Allowed units are 'px', '%', 'rem', 'em', 'vh' and 'vw'. -- `addHistoryEntry` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** adds an entry in the history, by default it's `false`. (optional, default `false`) - -##### Examples - -```javascript -LuigiClient.linkManager().updateModalSettings({title:'LuigiModal', size:'l'}); -``` - -#### openAsSplitView - -- **See: [splitView](#splitview) for further documentation about the returned instance** - -Opens a view in a split view. You can specify the split view's title and size. If you don't specify the title, it is the node label. If there is no node label, the title remains empty. The default size of the split view is `40`, which means 40% height of the split view. - -##### Parameters - -- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** navigation path -- `splitViewSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** opens a view in a split view. Use these settings to configure the split view's behaviour (optional, default `{}`) - - `splitViewSettings.title` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** split view title. By default, it is the node label. If there is no label, it is left empty - - `splitViewSettings.size` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** height of the split view in percent (optional, default `40`) - - `splitViewSettings.collapsed` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** opens split view in collapsed state (optional, default `false`) - -##### Examples - -```javascript -const splitViewHandle = LuigiClient.linkManager().openAsSplitView('projects/pr1/logs', {title: 'Logs', size: 40, collapsed: true}); -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** instance of the SplitView. It provides Event listeners and you can use the available functions to control its behavior. - -**Meta** - -- **since**: 0.6.0 - -#### openAsDrawer - -Opens a view in a drawer. You can specify the size of the drawer, whether the drawer has a header, and whether a backdrop is active in the background. By default, the header is shown. The backdrop is not visible and has to be activated. The size of the drawer is set to `s` by default, which means 25% of the micro frontend size. You can also use `l`(75%), `m`(50%) or `xs`(15.5%). Optionally, use it in combination with any of the navigation functions. - -##### Parameters - -- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** navigation path -- `drawerSettings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** opens a view in a drawer. Use these settings to configure if the drawer has a header, backdrop and size. (optional, default `{}`) - - `drawerSettings.header` **any** By default, the header is visible. The default title is the node label, but the header could also be an object with a `title` attribute allowing you to specify your own title. An 'x' icon is displayed to close the drawer view. - - `drawerSettings.backdrop` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true` the rest of the screen has a backdrop. - - `drawerSettings.size` **(`"l"` \| `"m"` \| `"s"` \| `"xs"`)** size of the drawer (optional, default `"s"`) - - `drawerSettings.overlap` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** enable resizing of main microfrontend iFrame after drawer open (optional, default `true`) - -##### Examples - -```javascript -LuigiClient.linkManager().openAsDrawer('projects/pr1/drawer', {header:true, backdrop:true, size:'s'}); -LuigiClient.linkManager().openAsDrawer('projects/pr1/drawer', {header:{title:'My drawer component'}, backdrop:true, size:'xs'}); -``` - -**Meta** - -- **since**: 1.6.0 - -#### fromContext - -Sets the current navigation context to that of a specific parent node which has the [navigationContext](navigation-configuration.md) field declared in the navigation configuration. This navigation context is then used by the `navigate` function. - -##### Parameters - -- `navigationContext` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** - -##### Examples - -```javascript -LuigiClient.linkManager().fromContext('project').navigate('/settings') -``` - -Returns **[linkManager](#linkmanager)** link manager instance - -#### fromClosestContext - -Sets the current navigation context which is then used by the `navigate` function. This has to be a parent navigation context, it is not possible to use the child navigation contexts. - -##### Examples - -```javascript -LuigiClient.linkManager().fromClosestContext().navigate('/users/groups/stakeholders') -``` - -Returns **[linkManager](#linkmanager)** link manager instance - -#### fromVirtualTreeRoot - -Sets the current navigation base to the parent node that is defined as virtualTree. This method works only when the currently active micro frontend is inside a virtualTree. - -##### Examples - -```javascript -LuigiClient.linkManager().fromVirtualTreeRoot().navigate('/users/groups/stakeholders') -``` - -Returns **[linkManager](#linkmanager)** link manager instance - -**Meta** - -- **since**: 1.0.1 - -#### fromParent - -Enables navigating to sibling nodes without knowing the absolute path. - -##### Examples - -```javascript -LuigiClient.linkManager().fromParent().navigate('/sibling') -``` - -Returns **[linkManager](#linkmanager)** link manager instance - -**Meta** - -- **since**: 1.0.1 - -#### withParams - -Sends node parameters to the route. The parameters are used by the `navigate` function. Use it optionally in combination with any of the navigation functions and receive it as part of the context object in Luigi Client. - -##### Parameters - -- `nodeParams` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** - -##### Examples - -```javascript -LuigiClient.linkManager().withParams({foo: "bar"}).navigate("path") - -// Can be chained with context setting functions such as: -LuigiClient.linkManager().fromContext("currentTeam").withParams({foo: "bar"}).navigate("path") -``` - -Returns **[linkManager](#linkmanager)** link manager instance - -#### withOptions - -Sets options to customise route changing behaviour. The parameters are used by the `navigate` function. Use it optionally in combination with any of the navigation functions and receive it as part of the context object in Luigi Client. - -##### Parameters - -- `options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** navigation options - - `options.preventHistoryEntry` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true`, there is no browser history being kept. - - `options.preventContextUpdate` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** By default, it is set to `false`. If it is set to `true`, there is no context update being triggered. - -##### Examples - -```javascript -LuigiClient.linkManager().withOptions( -{ preventContextUpdate:true, preventHistoryEntry: true } -).navigate('/overview') -``` - -Returns **[linkManager](#linkmanager)** link manager instance - -**Meta** - -- **since**: 1.25.0 - -#### pathExists - -Checks if the path you can navigate to exists in the main application. For example, you can use this helper method conditionally to display a DOM element like a button. - -##### Parameters - -- `path` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** path which existence you want to check - -##### Examples - -```javascript -let pathExists; - LuigiClient - .linkManager() - .pathExists('projects/pr2') - .then( - (pathExists) => { } - ); -``` - -Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** a promise which resolves to a Boolean variable specifying whether the path exists or not - -#### hasBack - -Checks if there is one or more preserved views. You can use it to show a **back** button. - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** indicating if there is a preserved view you can return to - -#### goBack - -Discards the active view and navigates back to the last visited view. Works with preserved views, and also acts as the substitute of the browser **back** button. **goBackContext** is only available when using preserved views. - -##### Parameters - -- `goBackValue` **any** data that is passed in the **goBackContext** field to the last visited view when using preserved views - -##### Examples - -```javascript -LuigiClient.linkManager().goBack({ foo: 'bar' }); -LuigiClient.linkManager().goBack(true); -``` - -### splitView - -Split view -Allows to open a micro frontend in a split screen in the lower part of the content area. Open it by calling `const splitViewHandle = LuigiClient.linkManager().openAsSplitView`. -At a given time, you can open only one split view. It closes automatically when you navigate to a different route. -When you call `handle.collapse()`, the split view gets destroyed. It recreates when you use `handle.expand()`. -`openAsSplitView` returns an instance of the split view handle. The functions, actions, and event handlers listed below allow you to control and manage the split view. - -**Meta** - -- **since**: 0.6.0 - -#### collapse - -Collapses the split view - -##### Examples - -```javascript -splitViewHandle.collapse(); -``` - -**Meta** - -- **since**: 0.6.0 - -#### expand - -Expands the split view - -##### Examples - -```javascript -splitViewHandle.expand(); -``` - -**Meta** - -- **since**: 0.6.0 - -#### close - -Closes and destroys the split view - -##### Examples - -```javascript -splitViewHandle.close(); -``` - -**Meta** - -- **since**: 0.6.0 - -#### setSize - -Sets the height of the split view - -##### Parameters - -- `value` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** lower height in percent - -##### Examples - -```javascript -splitViewHandle.setSize(60); -``` - -**Meta** - -- **since**: 0.6.0 - -#### on - -Registers a listener for split view events - -##### Parameters - -- `name` **(`"expand"` \| `"collapse"` \| `"resize"` \| `"close"`)** event name -- `callback` **[function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** gets called when this event gets triggered by Luigi - -##### Examples - -```javascript -const listenerId = splitViewHandle.on('expand', () => {}); -const listenerId = splitViewHandle.on('collapse', () => {}); -const listenerId = splitViewHandle.on('resize', () => {}); -const listenerId = splitViewHandle.on('close', () => {}); -* -``` - -Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** listener id - -**Meta** - -- **since**: 0.6.0 - -#### removeEventListener - -Unregisters a split view listener - -##### Parameters - -- `id` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** listener id - -##### Examples - -```javascript -splitViewHandle.removeEventListener(listenerId); -``` - -**Meta** - -- **since**: 0.6.0 - -#### exists - -Gets the split view status - -##### Examples - -```javascript -splitViewHandle.exists(); -``` - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if a split view is loaded - -**Meta** - -- **since**: 0.6.0 - -#### getSize - -Reads the size of the split view - -##### Examples - -```javascript -splitViewHandle.getSize(); -``` - -Returns **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** height in percent - -**Meta** - -- **since**: 0.6.0 - -#### isCollapsed - -Reads the collapse status - -##### Examples - -```javascript -splitViewHandle.isCollapsed(); -``` - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if the split view is currently collapsed - -**Meta** - -- **since**: 0.6.0 - -#### isExpanded - -Reads the expand status - -##### Examples - -```javascript -splitViewHandle.isExpanded(); -``` - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** true if the split view is currently expanded - -**Meta** - -- **since**: 0.6.0 - -### uxManager - -Use the UX Manager to manage the appearance features in Luigi. - -#### showLoadingIndicator - -Adds a backdrop with a loading indicator for the micro frontend frame. This overrides the [loadingIndicator.enabled](navigation-parameters-reference.md#node-parameters) setting. - -#### hideLoadingIndicator - -Removes the loading indicator. Use it after calling [showLoadingIndicator()](#showLoadingIndicator) or to hide the indicator when you use the [loadingIndicator.hideAutomatically: false](navigation-parameters-reference.md#node-parameters) node configuration. - -#### closeCurrentModal - -Closes the currently opened micro frontend modal. - -#### addBackdrop - -Adds a backdrop to block the top and side navigation. It is based on the Fundamental UI Modal, which you can use in your micro frontend to achieve the same behavior. - -#### removeBackdrop - -Removes the backdrop. - -#### setDirtyStatus - -This method informs the main application that there are unsaved changes in the current view in the iframe. It can be used to prevent navigation away from the current view, for example with form fields which were edited but not submitted. However, this functionality is not restricted to forms. If you use `withoutSync()` together with `setDirtyStatus()`, this is a special case in which the dirty state logic needs to be handled by the micro frontend. For example, if the user navigates with an Angular router, which would trigger `withoutSync()`, Angular needs to take care about dirty state, prevent the navigation and ask for permission to navigate away, through `uxManager().showConfirmationModal(settings)`. - -##### Parameters - -- `isDirty` **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** indicates if there are any unsaved changes on the current page or in the component - -#### showConfirmationModal - -Shows a confirmation modal. - -##### Parameters - -- `settings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** the settings of the confirmation modal. If you don't provide any value for any of the fields, a default value is used - - `settings.type` **(`"confirmation"` \| `"success"` \| `"warning"` \| `"error"` \| `"information"`)** the content of the modal type. (Optional) - - `settings.header` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the content of the modal header (optional, default `"Confirmation"`) - - `settings.body` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the content of the modal body. It supports HTML formatting elements such as `
`, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``. (optional, default `"Are you sure you want to do this?"`) - - `settings.buttonConfirm` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| `false`)** the label for the modal confirmation button. If set to `false`, the button will not be shown. (optional, default `"Yes"`) - - `settings.buttonDismiss` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the label for the modal dismiss button (optional, default `"No"`) - -##### Examples - -```javascript -import LuigiClient from '@luigi-project/client'; -const settings = { - type: "confirmation", - header: "Confirmation", - body: "Are you sure you want to do this?", - buttonConfirm: "Yes", - buttonDismiss: "No" -} -LuigiClient - .uxManager() - .showConfirmationModal(settings) - .then(() => { - // Logic to execute when the confirmation modal is dismissed - }); -``` - -Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** which is resolved when accepting the confirmation modal and rejected when dismissing it - -#### showAlert - -Shows an alert. - -##### Parameters - -- `settings` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** the settings for the alert - - `settings.text` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the content of the alert. To add a link to the content, you have to set up the link in the `links` object. The key(s) in the `links` object must be used in the text to reference the links, wrapped in curly brackets with no spaces. If you don't specify any text, the alert is not displayed - - `settings.type` **(`"info"` \| `"success"` \| `"warning"` \| `"error"`)** sets the type of alert - - `settings.links` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** provides links data - - `settings.links.LINK_KEY` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** object containing the data for a particular link. To properly render the link in the alert message refer to the description of the **settings.text** parameter - - `settings.links.LINK_KEY.text` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** text which replaces the link identifier in the alert content - - `settings.links.LINK_KEY.url` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** URL to navigate when you click the link. Currently, only internal links are supported in the form of relative or absolute paths - - `settings.links.LINK_KEY.dismissKey` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** dismissKey which represents the key of the link. - - `settings.closeAfter` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** (optional) time in milliseconds that tells Luigi when to close the Alert automatically. If not provided, the Alert will stay on until closed manually. It has to be greater than `100` - -##### Examples - -```javascript -import LuigiClient from '@luigi-project/client'; -const settings = { - text: "Ut enim ad minim veniam, {goToHome} quis nostrud exercitation ullamco {relativePath}. Duis aute irure dolor {goToOtherProject}", - type: 'info', - links: { - goToHome: { text: 'homepage', url: '/overview' }, - goToOtherProject: { text: 'other project', url: '/projects/pr2' }, - relativePath: { text: 'relative hide side nav', url: 'hideSideNav' }, - neverShowItAgain: { text: 'Never show it again', dismissKey: 'neverShowItAgain' } - }, - closeAfter: 3000 -} -LuigiClient - .uxManager() - .showAlert(settings) - .then(() => { - // Logic to execute when the alert is dismissed - }); -``` - -Returns **[promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** which is resolved when the alert is dismissed - -#### getCurrentLocale - -Gets the current locale. - -Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** current locale - -#### setCurrentLocale - -Sets current locale to the specified one. - -**NOTE:** this must be explicitly allowed on the navigation node level by setting `clientPermissions.changeCurrentLocale` to `true`. (See [Node parameters](navigation-parameters-reference.md).) - -##### Parameters - -- `locale` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** locale to be set as the current locale - -#### isSplitView - -Checks if the current micro frontend is displayed inside a split view - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** indicating if it is loaded inside a split view - -**Meta** - -- **since**: 0.6.0 - -#### isModal - -Checks if the current micro frontend is displayed inside a modal - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** indicating if it is loaded inside a modal - -**Meta** - -- **since**: 0.6.0 - -#### isDrawer - -Checks if the current micro frontend is displayed inside a drawer - -Returns **[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** indicating if it is loaded inside a drawer - -**Meta** - -- **since**: 1.26.0 - -#### getCurrentTheme - -Gets the current theme. - -Returns **any** current themeObj - -#### getCSSVariables - -Gets the CSS variables from Luigi Core with their key and value. - -##### Examples - -```javascript -LuigiClient.uxManager().getCSSVariables(); -``` - -Returns **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** CSS variables with their key and value. - -**Meta** - -- **since**: 2.3.0 - -#### applyCSS - -Adds the CSS variables from Luigi Core in a