## Description 1. Add LayoutComponent functionality. 2. Create Basic LayoutComponents. 3. Create LayoutPresets needed for Container-like widgets. 4. Add highlight calculation logic for all basic Layout Components. 5. Create dragging sagas for Anvil. 6. Create DraggingArena associated functionality to handle DnD in Anvil. #### PR fixes following issue(s) Fixes #27004 #### Type of change > Please delete options that are not relevant. - New feature (non-breaking change which adds functionality) ## Testing #### How Has This Been Tested? - [ ] Manual - [ ] JUnit - [x] Jest - [ ] Cypress ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] PR is being merged under a feature flag #### QA activity: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed --------- Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> Co-authored-by: Aswath K <aswath.sana@gmail.com> Co-authored-by: rahulramesha <rahul@appsmith.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com>
392 lines
11 KiB
TypeScript
392 lines
11 KiB
TypeScript
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
|
import { ValidationTypes } from "constants/WidgetValidation";
|
|
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
|
|
import React from "react";
|
|
import type { WidgetState } from "widgets/BaseWidget";
|
|
import BaseWidget from "widgets/BaseWidget";
|
|
import IframeComponent from "../component";
|
|
import type { IframeWidgetProps } from "../constants";
|
|
import { generateTypeDef } from "utils/autocomplete/dataTreeTypeDefCreator";
|
|
import { DefaultAutocompleteDefinitions } from "widgets/WidgetUtils";
|
|
import type {
|
|
AnvilConfig,
|
|
AutocompletionDefinitions,
|
|
} from "WidgetProvider/constants";
|
|
import IconSVG from "../icon.svg";
|
|
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
|
import type {
|
|
SnipingModeProperty,
|
|
PropertyUpdates,
|
|
} from "WidgetProvider/constants";
|
|
import { WIDGET_TAGS } from "constants/WidgetConstants";
|
|
import {
|
|
FlexVerticalAlignment,
|
|
ResponsiveBehavior,
|
|
} from "layoutSystems/common/utils/constants";
|
|
|
|
const isAirgappedInstance = isAirgapped();
|
|
|
|
const DEFAULT_IFRAME_SOURCE = !isAirgappedInstance
|
|
? "https://www.example.com"
|
|
: "";
|
|
|
|
class IframeWidget extends BaseWidget<IframeWidgetProps, WidgetState> {
|
|
static type = "IFRAME_WIDGET";
|
|
|
|
static getConfig() {
|
|
return {
|
|
name: "Iframe",
|
|
iconSVG: IconSVG,
|
|
tags: [WIDGET_TAGS.DISPLAY],
|
|
needsMeta: true,
|
|
searchTags: ["embed"],
|
|
};
|
|
}
|
|
|
|
static getDefaults() {
|
|
return {
|
|
source: DEFAULT_IFRAME_SOURCE,
|
|
borderOpacity: 100,
|
|
borderWidth: 1,
|
|
rows: 32,
|
|
columns: 24,
|
|
widgetName: "Iframe",
|
|
version: 1,
|
|
animateLoading: true,
|
|
responsiveBehavior: ResponsiveBehavior.Fill,
|
|
flexVerticalAlignment: FlexVerticalAlignment.Top,
|
|
};
|
|
}
|
|
|
|
static getMethods() {
|
|
return {
|
|
getSnipingModeUpdates: (
|
|
propValueMap: SnipingModeProperty,
|
|
): PropertyUpdates[] => {
|
|
return [
|
|
{
|
|
propertyPath: "source",
|
|
propertyValue: propValueMap.data,
|
|
isDynamicPropertyPath: true,
|
|
},
|
|
];
|
|
},
|
|
};
|
|
}
|
|
|
|
static getAutoLayoutConfig() {
|
|
return {
|
|
widgetSize: [
|
|
{
|
|
viewportMinWidth: 0,
|
|
configuration: () => {
|
|
return {
|
|
minWidth: "280px",
|
|
minHeight: "300px",
|
|
};
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
static getAnvilConfig(): AnvilConfig | null {
|
|
return {
|
|
widgetSize: {
|
|
maxHeight: {},
|
|
maxWidth: {},
|
|
minHeight: { base: "300px" },
|
|
minWidth: { base: "280px" },
|
|
},
|
|
};
|
|
}
|
|
|
|
static getAutocompleteDefinitions(): AutocompletionDefinitions {
|
|
return (widget: IframeWidgetProps) => ({
|
|
"!doc": "Iframe widget is used to display iframes in your app.",
|
|
"!url": "https://docs.appsmith.com/widget-reference/iframe",
|
|
isVisible: DefaultAutocompleteDefinitions.isVisible,
|
|
source: "string",
|
|
title: "string",
|
|
message: generateTypeDef(widget.message),
|
|
messageMetadata: generateTypeDef(widget.messageMetadata),
|
|
});
|
|
}
|
|
|
|
static getSetterConfig(): SetterConfig {
|
|
return {
|
|
__setters: {
|
|
setVisibility: {
|
|
path: "isVisible",
|
|
type: "boolean",
|
|
},
|
|
setURL: {
|
|
path: "source",
|
|
type: "string",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
static getPropertyPaneContentConfig() {
|
|
return [
|
|
{
|
|
sectionName: "Data",
|
|
children: [
|
|
{
|
|
propertyName: "source",
|
|
helpText: "The URL of the page to embed",
|
|
label: "URL",
|
|
controlType: "INPUT_TEXT",
|
|
placeholderText: "https://docs.appsmith.com",
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: {
|
|
type: ValidationTypes.SAFE_URL,
|
|
params: {
|
|
default: "https://www.example.com",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
propertyName: "srcDoc",
|
|
helpText: "Inline HTML to embed, overriding the src attribute",
|
|
label: "srcDoc",
|
|
controlType: "INPUT_TEXT",
|
|
placeholderText: "<p>Inline HTML</p>",
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: {
|
|
type: ValidationTypes.TEXT,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
sectionName: "General",
|
|
children: [
|
|
{
|
|
propertyName: "title",
|
|
helpText: "Label the content of the page to embed",
|
|
label: "Title",
|
|
controlType: "INPUT_TEXT",
|
|
placeholderText: "Documentation",
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: { type: ValidationTypes.TEXT },
|
|
},
|
|
{
|
|
propertyName: "animateLoading",
|
|
label: "Animate loading",
|
|
controlType: "SWITCH",
|
|
helpText: "Controls the loading of the widget",
|
|
defaultValue: true,
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: { type: ValidationTypes.BOOLEAN },
|
|
},
|
|
],
|
|
},
|
|
{
|
|
sectionName: "Events",
|
|
children: [
|
|
{
|
|
helpText: "when the source URL is changed",
|
|
propertyName: "onURLChanged",
|
|
label: "onURLChanged",
|
|
controlType: "ACTION_SELECTOR",
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: true,
|
|
},
|
|
{
|
|
helpText: "when the srcDoc is changed",
|
|
propertyName: "onSrcDocChanged",
|
|
label: "onSrcDocChanged",
|
|
controlType: "ACTION_SELECTOR",
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: true,
|
|
},
|
|
{
|
|
helpText: "when a message event is received",
|
|
propertyName: "onMessageReceived",
|
|
label: "onMessageReceived",
|
|
controlType: "ACTION_SELECTOR",
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: true,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
static getPropertyPaneStyleConfig() {
|
|
return [
|
|
{
|
|
sectionName: "Color",
|
|
children: [
|
|
{
|
|
propertyName: "borderColor",
|
|
label: "Border color",
|
|
helpText: "Controls the color of the border",
|
|
controlType: "COLOR_PICKER",
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: { type: ValidationTypes.TEXT },
|
|
},
|
|
],
|
|
},
|
|
{
|
|
sectionName: "Border and shadow",
|
|
children: [
|
|
{
|
|
propertyName: "borderWidth",
|
|
label: "Border width (px)",
|
|
helpText: "Controls the size of the border in px",
|
|
controlType: "INPUT_TEXT",
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
inputType: "NUMBER",
|
|
validation: {
|
|
type: ValidationTypes.NUMBER,
|
|
params: { min: 0, default: 1 },
|
|
},
|
|
},
|
|
{
|
|
propertyName: "borderOpacity",
|
|
label: "Border opacity (%)",
|
|
helpText: "Controls the opacity of the border in percentage",
|
|
controlType: "INPUT_TEXT",
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
inputType: "NUMBER",
|
|
validation: {
|
|
type: ValidationTypes.NUMBER,
|
|
params: { min: 0, max: 100, default: 100 },
|
|
},
|
|
},
|
|
{
|
|
propertyName: "borderRadius",
|
|
label: "Border radius",
|
|
helpText:
|
|
"Rounds the corners of the icon button's outer border edge",
|
|
controlType: "BORDER_RADIUS_OPTIONS",
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: { type: ValidationTypes.TEXT },
|
|
},
|
|
{
|
|
propertyName: "boxShadow",
|
|
label: "Box shadow",
|
|
helpText:
|
|
"Enables you to cast a drop shadow from the frame of the widget",
|
|
controlType: "BOX_SHADOW_OPTIONS",
|
|
isJSConvertible: true,
|
|
isBindProperty: true,
|
|
isTriggerProperty: false,
|
|
validation: { type: ValidationTypes.TEXT },
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
static getMetaPropertiesMap(): Record<string, any> {
|
|
return {
|
|
message: undefined,
|
|
messageMetadata: undefined,
|
|
};
|
|
}
|
|
|
|
static getStylesheetConfig(): Stylesheet {
|
|
return {
|
|
borderRadius: "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
boxShadow: "{{appsmith.theme.boxShadow.appBoxShadow}}",
|
|
};
|
|
}
|
|
|
|
handleUrlChange = (url: string) => {
|
|
if (url && this.props.onURLChanged) {
|
|
super.executeAction({
|
|
triggerPropertyName: "onURLChanged",
|
|
dynamicString: this.props.onURLChanged,
|
|
event: {
|
|
type: EventType.ON_IFRAME_URL_CHANGED,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
handleSrcDocChange = (srcDoc?: string) => {
|
|
if (srcDoc && this.props.onSrcDocChanged) {
|
|
super.executeAction({
|
|
triggerPropertyName: "onSrcDocChanged",
|
|
dynamicString: this.props.onSrcDocChanged,
|
|
event: {
|
|
type: EventType.ON_IFRAME_SRC_DOC_CHANGED,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
handleMessageReceive = ({
|
|
data,
|
|
lastEventId,
|
|
origin,
|
|
ports,
|
|
}: MessageEvent) => {
|
|
this.props.updateWidgetMetaProperty("messageMetadata", {
|
|
lastEventId,
|
|
origin,
|
|
ports,
|
|
});
|
|
this.props.updateWidgetMetaProperty("message", data, {
|
|
triggerPropertyName: "onMessageReceived",
|
|
dynamicString: this.props.onMessageReceived,
|
|
event: {
|
|
type: EventType.ON_IFRAME_MESSAGE_RECEIVED,
|
|
},
|
|
});
|
|
};
|
|
|
|
getWidgetView() {
|
|
const {
|
|
borderColor,
|
|
borderOpacity,
|
|
borderWidth,
|
|
renderMode,
|
|
source,
|
|
srcDoc,
|
|
title,
|
|
widgetId,
|
|
widgetName,
|
|
} = this.props;
|
|
|
|
return (
|
|
<IframeComponent
|
|
borderColor={borderColor}
|
|
borderOpacity={borderOpacity}
|
|
borderRadius={this.props.borderRadius}
|
|
borderWidth={borderWidth}
|
|
boxShadow={this.props.boxShadow}
|
|
onMessageReceived={this.handleMessageReceive}
|
|
onSrcDocChanged={this.handleSrcDocChange}
|
|
onURLChanged={this.handleUrlChange}
|
|
renderMode={renderMode}
|
|
source={source}
|
|
srcDoc={srcDoc}
|
|
title={title}
|
|
widgetId={widgetId}
|
|
widgetName={widgetName}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default IframeWidget;
|