/ok-to-test tags="@tag.Widget" <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes - **New Features** - Introduced a custom widget with enhanced console logging and communication capabilities. - Added a configuration system for widget properties, including visibility, style settings, and autocomplete functionality. - Implemented a responsive design for the custom widget with dynamic loading events and error handling. - Expanded widget mapping to include the new custom widget type. - Added support for multiple code templates (React, Vue, Vanilla JS) for custom widget creation. - Introduced a custom hook for managing widget height based on component sizes and embedding status. - **Bug Fixes** - Resolved various issues related to event handling and message passing between the widget and parent context. - **Documentation** - Added comprehensive comments and structure to configuration files for better clarity and usability. - **Style** - Included a CSS reset stylesheet for consistent styling across browsers. - Introduced new CSS classes for improved widget styling. - Enhanced styling rules to manage pointer events during widget resizing. - **Tests** - Developed a test suite to ensure the reliability of the widget's functionality and event handling. <!-- end of auto-generated comment: release notes by coderabbit.ai --> <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/12194686716> > Commit: a757240165ea8d2730d6b6f2574b2c1c7335fada > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=12194686716&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.Widget` > Spec: > <hr>Fri, 06 Dec 2024 08:28:58 UTC <!-- end of auto-generated comment: Cypress test results -->
118 lines
2.9 KiB
TypeScript
118 lines
2.9 KiB
TypeScript
export class Builder {
|
|
builderWindow: Window | null;
|
|
|
|
onMessageMap: Map<string, ((data: unknown) => void)[]> = new Map();
|
|
|
|
handleMessageBound = this.handleMessage.bind(this);
|
|
|
|
constructor() {
|
|
// when we add new widget, we add a /add to the url , so before opening the builder, we need to remove it
|
|
const path = window.location.pathname.replace(/\/add$/, "");
|
|
|
|
this.builderWindow = window.open(`${path}/builder`, "_blank");
|
|
|
|
window?.addEventListener("message", this.handleMessageBound);
|
|
}
|
|
|
|
handleMessage(event: MessageEvent) {
|
|
if (event.source === this.builderWindow) {
|
|
const handlerList = this.onMessageMap.get(event.data.type);
|
|
|
|
if (handlerList) {
|
|
handlerList.forEach((fn) => fn?.(event.data));
|
|
}
|
|
}
|
|
}
|
|
|
|
onMessage(type: string, fn: (data: unknown) => void) {
|
|
let eventHandlerList = this.onMessageMap.get(type);
|
|
|
|
if (eventHandlerList && eventHandlerList instanceof Array) {
|
|
eventHandlerList.push(fn);
|
|
} else {
|
|
eventHandlerList = [fn];
|
|
this.onMessageMap.set(type, eventHandlerList);
|
|
}
|
|
|
|
return () => {
|
|
if (eventHandlerList) {
|
|
const index = eventHandlerList.indexOf(fn);
|
|
|
|
if (index > -1) {
|
|
eventHandlerList.splice(index, 1);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
postMessage(message: unknown) {
|
|
this.builderWindow?.postMessage(message, "*");
|
|
}
|
|
|
|
isConnected() {
|
|
return !this.builderWindow?.closed;
|
|
}
|
|
|
|
focus() {
|
|
this.builderWindow?.focus();
|
|
}
|
|
|
|
close(closeWindow: boolean) {
|
|
if (closeWindow) {
|
|
this.builderWindow?.close();
|
|
}
|
|
|
|
window?.removeEventListener("message", this.handleMessageBound);
|
|
}
|
|
}
|
|
|
|
export default class CustomWidgetBuilderService {
|
|
private static builderWindowConnections: Map<string, Builder> = new Map();
|
|
|
|
// For unit testing purposes
|
|
private static builderFactory = Builder;
|
|
|
|
// For unit testing purposes
|
|
static setBuilderFactory(builder: typeof Builder) {
|
|
this.builderFactory = builder;
|
|
}
|
|
|
|
static createBuilder(widgetId: string) {
|
|
const builder = new this.builderFactory();
|
|
|
|
this.builderWindowConnections.set(widgetId, builder);
|
|
|
|
return builder;
|
|
}
|
|
|
|
static isConnected(widgetId: string) {
|
|
const builder = this.builderWindowConnections.get(widgetId);
|
|
|
|
return builder?.isConnected();
|
|
}
|
|
|
|
static focus(widgetId: string) {
|
|
if (this.isConnected(widgetId)) {
|
|
this.builderWindowConnections.get(widgetId)?.focus();
|
|
}
|
|
}
|
|
|
|
static getBuilder(widgetId: string) {
|
|
if (this.isConnected(widgetId)) {
|
|
const builder = this.builderWindowConnections.get(widgetId) as Builder;
|
|
|
|
return builder;
|
|
}
|
|
}
|
|
|
|
static closeBuilder(widgetId: string, closeWindow: boolean) {
|
|
if (this.builderWindowConnections.has(widgetId)) {
|
|
const builder = this.builderWindowConnections.get(widgetId);
|
|
|
|
builder?.close(closeWindow);
|
|
|
|
this.builderWindowConnections.delete(widgetId);
|
|
}
|
|
}
|
|
}
|