PromucFlow_constructor/app/client/src/utils/CustomWidgetBuilderService.ts
Pawan Kumar 14a16926da
chore: add custom widget to anvil (#37878)
/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  -->
2024-12-06 16:03:40 +05:30

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);
}
}
}