Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improves typings in Luigi container #4030

Merged
merged 5 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion container/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@types/jest": "^29.5.12",
"@typescript-eslint/eslint-plugin": "^8.13.0",
"@typescript-eslint/parser": "^8.13.0",
"@typescript-eslint/typescript-estree": "^8.13.0",
"chokidar-cli": "^3.0.0",
"cli-color": "^2.0.4",
"concurrently": "^7.6.0",
Expand All @@ -65,7 +66,6 @@
"svelte-preprocess": "5.0.4",
"tslib": "2.6.1",
"typescript": "5.1.6",
"@typescript-eslint/typescript-estree": "^8.13.0",
"typescript-eslint": "^8.13.0"
},
"engines": {
Expand Down
19 changes: 12 additions & 7 deletions container/src/LuigiCompoundContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@

<script lang="ts">
import { onMount } from 'svelte';
import { Events } from './constants/communication';
import type { ContainerElement } from './constants/container.model';
import { ContainerService } from './services/container.service';
import { WebComponentService } from './services/webcomponents.service';
import { Events } from './constants/communication';
import { GenericHelperFunctions } from './utilities/helpers';

/* eslint-disable */
export let activeFeatureToggleList: string[];
export let anchor: string;
export let clientPermissions: any;
Expand All @@ -86,10 +88,11 @@
export let userSettings: any;
export let viewurl: string;
export let webcomponent: any;
/* eslint-enable */

let containerInitialized = false;
let mainComponent: HTMLElement;
let eventBusElement: HTMLElement;
let mainComponent: ContainerElement;
let eventBusElement: ContainerElement;

const containerService = new ContainerService();
const webcomponentService = new WebComponentService();
Expand All @@ -114,11 +117,12 @@
);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const initialize = (thisComponent: any) => {
if (!compoundConfig || containerInitialized) {
return;
}
thisComponent.updateContext = (contextObj: any, internal?: any) => {
thisComponent.updateContext = (contextObj: object, internal?: object) => {
const rootElement = thisComponent.getNoShadow() ? thisComponent : mainComponent;
rootElement._luigi_mfe_webcomponent.context = contextObj;

Expand Down Expand Up @@ -149,14 +153,14 @@
}
webcomponentService
.renderWebComponentCompound(node, thisComponent.getNoShadow() ? thisComponent : mainComponent, ctx)
.then((compCnt) => {
eventBusElement = compCnt as HTMLElement;
.then((compCnt: ContainerElement) => {
eventBusElement = compCnt;
if (skipInitCheck || !node.viewUrl) {
thisComponent.initialized = true;
setTimeout(() => {
webcomponentService.dispatchLuigiEvent(Events.INITIALIZED, {});
});
} else if ((eventBusElement as any).LuigiClient && !(eventBusElement as any).deferLuigiClientWCInit) {
} else if (eventBusElement.LuigiClient && !eventBusElement.deferLuigiClientWCInit) {
thisComponent.initialized = true;
webcomponentService.dispatchLuigiEvent(Events.INITIALIZED, {});
}
Expand All @@ -166,6 +170,7 @@
};

onMount(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const thisComponent: any =
mainComponent.getRootNode() === document
? mainComponent.parentNode
Expand Down
24 changes: 14 additions & 10 deletions container/src/LuigiContainer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,15 @@

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { containerService } from './services/container.service';
import { WebComponentService } from './services/webcomponents.service';
import { ContainerAPI } from './api/container-api';
import { Events } from './constants/communication';
import { GenericHelperFunctions } from './utilities/helpers';
import type { IframeHandle, ContainerElement } from './constants/container.model';
import { containerService } from './services/container.service';
import { getAllowRules } from './services/iframe-helpers';
import { WebComponentService } from './services/webcomponents.service';
import { GenericHelperFunctions } from './utilities/helpers';

/* eslint-disable */
export let activeFeatureToggleList: string[];
export let allowRules: string[];
export let anchor: string;
Expand All @@ -88,9 +90,10 @@
export let userSettings: any;
export let viewurl: string;
export let webcomponent: any;
/* eslint-enable */

const iframeHandle: { iframe: HTMLIFrameElement } | any = {};
let mainComponent: HTMLElement;
const iframeHandle: IframeHandle = {};
let mainComponent: ContainerElement;
let containerInitialized = false;

const webcomponentService = new WebComponentService();
Expand Down Expand Up @@ -119,9 +122,10 @@
);
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const initialize = (thisComponent: any) => {
if (!containerInitialized) {
thisComponent.sendCustomMessage = (id: string, data?: any) => {
thisComponent.sendCustomMessage = (id: string, data?: object) => {
ContainerAPI.sendCustomMessage(
id,
thisComponent.getNoShadow() ? thisComponent : mainComponent,
Expand All @@ -131,15 +135,15 @@
);
};

thisComponent.updateContext = (contextObj: any, internal?: any) => {
thisComponent.updateContext = (contextObj: object, internal?: object) => {
if (webcomponent) {
(thisComponent.getNoShadow() ? thisComponent : mainComponent)._luigi_mfe_webcomponent.context = contextObj;
} else {
ContainerAPI.updateContext(contextObj, internal, iframeHandle);
}
};

thisComponent.closeAlert = (id: any, dismissKey: any) => {
thisComponent.closeAlert = (id: string, dismissKey: string) => {
ContainerAPI.closeAlert(id, dismissKey, iframeHandle);
};

Expand Down Expand Up @@ -179,8 +183,7 @@
} else if (webcomponent) {
(thisComponent.getNoShadow() ? thisComponent : mainComponent).addEventListener('wc_ready', () => {
if (
!(thisComponent.getNoShadow() ? thisComponent : (mainComponent as any))._luigi_mfe_webcomponent
?.deferLuigiClientWCInit
!(thisComponent.getNoShadow() ? thisComponent : mainComponent)._luigi_mfe_webcomponent?.deferLuigiClientWCInit
) {
thisComponent.initialized = true;
webcomponentService.dispatchLuigiEvent(Events.INITIALIZED, {});
Expand All @@ -193,6 +196,7 @@
};

onMount(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const thisComponent: any = mainComponent.parentNode;
thisComponent.iframeHandle = iframeHandle;
thisComponent.init = () => {
Expand Down
23 changes: 15 additions & 8 deletions container/src/api/container-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { IframeHandle, ContainerElement } from '../constants/container.model';
import { LuigiInternalMessageID } from '../constants/internal-communication';
import { containerService } from '../services/container.service';

Expand All @@ -8,7 +9,7 @@ export class ContainerAPIFunctions {
* @param internal internal luigi legacy data
* @param iframeHandle a reference to the iframe that is needed to send a message to it internally
*/
updateContext = (contextObj: any, internal?: any, iframeHandle?: any) => {
updateContext = (contextObj: object, internal?: object, iframeHandle?: IframeHandle) => {
if (iframeHandle) {
const internalParameter = internal || {};
containerService.sendCustomMessageToIframe(
Expand All @@ -31,7 +32,7 @@ export class ContainerAPIFunctions {
* @param iframeHandle a reference to the iframe that is needed to send a message to it internally
* @param authData the authData object being sent to the microfrontend
*/
updateAuthData = (iframeHandle: any, authData: any) => {
updateAuthData = (iframeHandle: IframeHandle, authData: object) => {
if (iframeHandle && authData) {
containerService.sendCustomMessageToIframe(iframeHandle, { authData }, LuigiInternalMessageID.AUTH_SET_TOKEN);
} else {
Expand All @@ -47,15 +48,21 @@ export class ContainerAPIFunctions {
* @param iframeHandle a reference to the iframe to be affected
* @param data data to be sent alongside the custom message
*/
sendCustomMessage = (id: string, mainComponent: any, isWebcomponent: boolean, iframeHandle: any, data?: any) => {
if (isWebcomponent && (mainComponent as any)._luigi_mfe_webcomponent) {
containerService.dispatch(id, (mainComponent as any)._luigi_mfe_webcomponent, data);
sendCustomMessage = (
id: string,
mainComponent: ContainerElement,
isWebcomponent: boolean,
iframeHandle: IframeHandle,
data?: object
) => {
if (isWebcomponent && mainComponent._luigi_mfe_webcomponent) {
containerService.dispatch(id, mainComponent._luigi_mfe_webcomponent, data);
} else {
const msg = { ...data };
if (msg.id) {
if (msg['id']) {
console.warn('Property "id" is reserved and can not be used in custom message data');
}
msg.id = id;
msg['id'] = id;
containerService.sendCustomMessageToIframe(iframeHandle, msg);
}
};
Expand All @@ -66,7 +73,7 @@ export class ContainerAPIFunctions {
* @param dismissKey the dismiss key being sent if any
* @param iframeHandle the handle of the iframe to send the message to
*/
closeAlert(id: any, dismissKey: any, iframeHandle: any) {
closeAlert(id: string, dismissKey: string, iframeHandle: IframeHandle) {
containerService.sendCustomMessageToIframe(iframeHandle, { id, dismissKey }, LuigiInternalMessageID.ALERT_CLOSED);
}
}
Expand Down
58 changes: 58 additions & 0 deletions container/src/constants/container.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export interface IframeHandle {
data?: string;
iframe: HTMLIFrameElement;
}

export interface ContainerElement extends HTMLElement {
iframeHandle?: IframeHandle;
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

export interface LayoutConfig {
column?: string;
row?: string;
slot?: string;
}

export interface RendererLayout {
columns?: string;
gap?: number;
maxWidth?: number;
minHeight?: number;
minWidth?: number;
rows?: string;
}

export interface RendererConfig extends RendererLayout {
layouts?: RendererLayout[];
}

export interface RendererUseProps {
attachCompoundItem?: (compoundCnt, compoundItemCnt, renderer) => void;
createCompoundContainer?: (config, renderer) => HTMLDivElement;
createCompoundItemContainer?: (layoutConfig, config?, renderer?) => HTMLDivElement;
extends?: string;
}

export interface RendererObject {
config?: RendererConfig;
use?: RendererUseProps | string;
}

export interface WebComponentNode {
compound?: {
renderer?: RendererObject;
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};
webcomponent?: {
selfRegistered?: boolean;
tagName?: string;
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
};
viewUrl?: string;
}

export interface WebComponentRenderer {
createCompoundContainer?: () => HTMLDivElement;
viewUrl?: string;
}
10 changes: 7 additions & 3 deletions container/src/constants/event-type.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
// TODO: Add and extend event to inclide custom typings/interface to make it easier to use on the listener parameter
export interface ParamsEvent extends Event {}
/**
* ParamsEvent interface is used to make the handling of listener parameter easier
*/
export interface ParamsEvent extends Event {
[key: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
}

/**
* PathExistsEvent interface is used to make it easier to use the linkManager().pathExists() promise based function
* on the core application side.
* It enforces the use of the callback function, since the latter is hardcoded to be 'callback'.
* This allows to send back the boolean value if the path exists or not.
* Example Usage:
* addEventListener('my-event-id' event: PathExistsEvent => {
* addEventListener('my-event-id', event: PathExistsEvent => {
* event.callback(true);
* }
* };
Expand Down
5 changes: 3 additions & 2 deletions container/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ComponentType } from 'svelte';
import LuigiContainer from './LuigiContainer.svelte';
import LuigiCompoundContainer from './LuigiCompoundContainer.svelte';
import { Events } from './constants/communication';
Expand All @@ -8,9 +9,9 @@ export type { PathExistsEvent } from './constants/event-type';
export default Events;

if (!customElements.get('luigi-container')) {
customElements.define('luigi-container', (LuigiContainer as any).element);
customElements.define('luigi-container', (LuigiContainer as ComponentType).element);
}

if (!customElements.get('luigi-compound-container')) {
customElements.define('luigi-compound-container', (LuigiCompoundContainer as any).element);
customElements.define('luigi-compound-container', (LuigiCompoundContainer as ComponentType).element);
}
21 changes: 14 additions & 7 deletions container/src/services/container.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Events } from '../constants/communication';
import type { IframeHandle, ContainerElement } from '../constants/container.model';
import { LuigiInternalMessageID } from '../constants/internal-communication';
import { GenericHelperFunctions } from '../utilities/helpers';

Expand All @@ -20,7 +21,7 @@ export class ContainerService {
* @param msg the message to be sent
* @param msgName the optional message name
*/
sendCustomMessageToIframe(iframeHandle: any, msg: any, msgName?: string) {
sendCustomMessageToIframe(iframeHandle: IframeHandle, msg: object, msgName?: string) {
const messageName = msgName || 'custom';

if (iframeHandle.iframe.contentWindow) {
Expand All @@ -38,16 +39,22 @@ export class ContainerService {
/**
* Dispatch an event to the given target container
* @param {string} msg the event message
* @param {HTMLElement} targetCnt the targeted HTML element onto which the event is dispatched
* @param {any} data custom data added to the event to be dispatched
* @param {ContainerElement} targetCnt the targeted HTML element onto which the event is dispatched
* @param {Object} data custom data added to the event to be dispatched
* @param {Function} callback
* @param {string} callbackName
*/
dispatch(msg: string, targetCnt: HTMLElement, data: any, callback?: (any) => void, callbackName?: string): void {
dispatch(
msg: string,
targetCnt: ContainerElement,
data: object,
callback?: (arg?) => void,
callbackName?: string
): void {
const customEvent = new CustomEvent(msg, { detail: data });

if (callback && GenericHelperFunctions.isFunction(callback) && callbackName) {
(customEvent as any)[callbackName] = (data) => {
customEvent[callbackName] = (data) => {
callback(data);
};
}
Expand All @@ -59,9 +66,9 @@ export class ContainerService {
* Retrieves the target container based on the event source.
*
* @param event The event object representing the source of the container.
* @returns {Object| undefined} The target container object or undefined if not found.
* @returns {ContainerElement | undefined} The target container object or undefined if not found.
*/
getTargetContainer(event) {
getTargetContainer(event): ContainerElement | undefined {
let cnt;

globalThis.__luigi_container_manager.container.forEach((element) => {
Expand Down
2 changes: 1 addition & 1 deletion container/src/services/iframe-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const getAllowRules = (allowRules: string[]) => {
const rules = allowRules;
rules.forEach((rule, index) => {
rules[index] = rule + (rule.indexOf(';') != -1 ? '' : ';');
rules[index] = (allowRules[index] as any).replaceAll('"', "'");
rules[index] = allowRules[index].replaceAll('"', "'");
});
return rules.join(' ');
};
Loading