PromucFlow_constructor/app/client/src/widgets/BaseWidget.tsx

284 lines
7.8 KiB
TypeScript
Raw Normal View History

/**
* Widget are responsible for accepting the abstraction layer inputs, interpretting them into rederable props and
* spawing components based on those props
* Widgets are also responsible for dispatching actions and updating the state tree
*/
import {
WidgetType,
RenderMode,
2019-03-19 14:05:48 +00:00
RenderModes,
2019-09-09 09:08:54 +00:00
CSSUnits,
} from "../constants/WidgetConstants";
import React, { Component } from "react";
import { PositionType, CSSUnit } from "constants/WidgetConstants";
2019-09-09 09:08:54 +00:00
import _ from "lodash";
import DraggableComponent from "components/editorComponents/DraggableComponent";
import ResizableComponent from "components/editorComponents/ResizableComponent";
import { ActionPayload } from "constants/ActionConstants";
import PositionedContainer from "components/designSystems/appsmith/PositionedContainer";
import WidgetNameComponent from "components/designSystems/appsmith/WidgetNameComponent";
2019-11-04 14:22:50 +00:00
import shallowequal from "shallowequal";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
import { PositionTypes } from "constants/WidgetConstants";
2019-11-20 08:10:01 +00:00
2019-11-19 12:44:58 +00:00
import ErrorBoundary from "../components/editorComponents/ErrorBoundry";
import { WidgetPropertyValidationType } from "../utils/ValidationFactory";
/***
* BaseWidget
*
* The abstract class which is extended/implemented by all widgets.
* Widgets must adhere to the abstractions provided by BaseWidget.
*
* Do not:
* 1) Use the context directly in the widgets
* 2) Update or access the dsl in the widgets
* 3) Call actions in widgets or connect the widgets to the entity reducers
*
*/
abstract class BaseWidget<
T extends WidgetProps,
2019-09-09 09:08:54 +00:00
K extends WidgetState
> extends Component<T, K> {
constructor(props: T) {
2019-09-09 09:08:54 +00:00
super(props);
const initialState: WidgetState = {
componentHeight: 0,
componentWidth: 0,
2019-09-09 09:08:54 +00:00
};
initialState.componentHeight = 0;
initialState.componentWidth = 0;
2019-09-09 09:08:54 +00:00
this.state = initialState as K;
}
static contextType = EditorContext;
2019-11-19 12:44:58 +00:00
// Needed to send a default no validation option. In case a widget needs
// validation implement this in the widget class again
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {};
}
/**
* Widget abstraction to register the widget type
* ```javascript
* getWidgetType() {
* return "MY_AWESOME_WIDGET",
* }
* ```
*/
abstract getWidgetType(): WidgetType;
/**
* Widgets can execute actions using this `executeAction` method.
* Triggers may be specific to the widget
*/
executeAction(actionPayloads?: ActionPayload[]): void {
const { executeAction } = this.context;
2019-11-13 07:34:59 +00:00
executeAction && !_.isNil(actionPayloads) && executeAction(actionPayloads);
}
2019-11-07 11:17:53 +00:00
updateWidgetProperty(
widgetId: string,
propertyName: string,
propertyValue: any,
): void {
const { updateWidgetProperty } = this.context;
updateWidgetProperty &&
updateWidgetProperty(widgetId, propertyName, propertyValue);
}
componentDidMount(): void {
this.calculateWidgetBounds(
this.props.rightColumn,
this.props.leftColumn,
this.props.topRow,
this.props.bottomRow,
this.props.parentColumnSpace,
2019-09-09 09:08:54 +00:00
this.props.parentRowSpace,
);
}
2019-09-09 09:08:54 +00:00
//eslint-disable-next-line @typescript-eslint/no-unused-vars
componentDidUpdate(prevProps: T) {
this.calculateWidgetBounds(
this.props.rightColumn,
this.props.leftColumn,
this.props.topRow,
this.props.bottomRow,
this.props.parentColumnSpace,
2019-09-09 09:08:54 +00:00
this.props.parentRowSpace,
);
}
calculateWidgetBounds(
rightColumn: number,
leftColumn: number,
topRow: number,
bottomRow: number,
parentColumnSpace: number,
2019-09-09 09:08:54 +00:00
parentRowSpace: number,
) {
2019-09-09 09:08:54 +00:00
const widgetState: WidgetState = {
componentWidth: (rightColumn - leftColumn) * parentColumnSpace,
componentHeight: (bottomRow - topRow) * parentRowSpace,
2019-09-09 09:08:54 +00:00
};
if (
_.isNil(this.state) ||
widgetState.componentHeight !== this.state.componentHeight ||
widgetState.componentWidth !== this.state.componentWidth
) {
2019-09-09 09:08:54 +00:00
this.setState(widgetState);
}
}
render() {
2019-09-09 09:08:54 +00:00
return this.getWidgetView();
}
private getWidgetView(): JSX.Element {
switch (this.props.renderMode) {
case RenderModes.CANVAS:
const style = this.getPositionStyle();
if (this.props.parentId) {
return (
<PositionedContainer style={style}>
<DraggableComponent {...this.props} orientation={"VERTICAL"}>
<WidgetNameComponent
widgetName={this.props.widgetName}
widgetId={this.props.widgetId}
/>
<ResizableComponent
{...this.props}
paddingOffset={PositionedContainer.padding}
>
{this.getCanvasView()}
</ResizableComponent>
</DraggableComponent>
</PositionedContainer>
);
}
return (
<PositionedContainer style={style}>
{this.getCanvasView()}
</PositionedContainer>
);
case RenderModes.PAGE:
if (this.props.isVisible) {
return (
<PositionedContainer style={this.getPositionStyle()}>
{this.getPageView()}
</PositionedContainer>
);
}
return <React.Fragment />;
default:
throw Error("RenderMode not defined");
}
}
2019-09-09 09:08:54 +00:00
abstract getPageView(): JSX.Element;
getCanvasView(): JSX.Element {
2019-11-19 12:44:58 +00:00
return <ErrorBoundary>{this.getPageView()}</ErrorBoundary>;
}
2019-11-04 14:22:50 +00:00
shouldComponentUpdate(nextProps: WidgetProps, nextState: WidgetState) {
const isNotEqual =
!shallowequal(nextProps, this.props) ||
!shallowequal(nextState, this.state);
return isNotEqual;
}
private getPositionStyle(): BaseStyle {
2019-03-19 14:05:48 +00:00
return {
positionType: PositionTypes.ABSOLUTE,
componentHeight: this.state.componentHeight,
componentWidth: this.state.componentWidth,
2019-03-19 14:05:48 +00:00
yPosition: this.props.topRow * this.props.parentRowSpace,
xPosition: this.props.leftColumn * this.props.parentColumnSpace,
xPositionUnit: CSSUnits.PIXEL,
2019-09-09 09:08:54 +00:00
yPositionUnit: CSSUnits.PIXEL,
};
2019-03-19 14:05:48 +00:00
}
2019-09-09 09:08:54 +00:00
static defaultProps: Partial<WidgetProps> = {
parentRowSpace: 1,
parentColumnSpace: 1,
topRow: 0,
2019-09-09 09:08:54 +00:00
leftColumn: 0,
};
}
export interface BaseStyle {
componentHeight: number;
componentWidth: number;
positionType: PositionType;
xPosition: number;
yPosition: number;
xPositionUnit: CSSUnit;
yPositionUnit: CSSUnit;
heightUnit?: CSSUnit;
widthUnit?: CSSUnit;
}
2019-09-09 09:08:54 +00:00
export interface WidgetState {
componentHeight: number;
componentWidth: number;
}
2019-09-09 09:08:54 +00:00
export interface WidgetBuilder<T extends WidgetProps> {
buildWidget(widgetProps: T): JSX.Element;
}
export interface WidgetProps extends WidgetDataProps {
key?: string;
renderMode: RenderMode;
2019-11-19 12:44:58 +00:00
dynamicBindings?: Record<string, boolean>;
2019-11-20 08:10:01 +00:00
isLoading: boolean;
2019-11-19 12:44:58 +00:00
invalidProps?: Record<string, boolean>;
2019-11-06 06:35:15 +00:00
[key: string]: any;
}
export interface WidgetDataProps {
2019-09-12 08:11:25 +00:00
widgetId: string;
type: WidgetType;
2019-09-12 08:11:25 +00:00
widgetName: string;
2019-08-29 11:22:09 +00:00
topRow: number;
leftColumn: number;
bottomRow: number;
rightColumn: number;
parentColumnSpace: number;
parentRowSpace: number;
isVisible?: boolean;
2019-11-20 08:10:01 +00:00
isLoading: boolean;
isDisabled?: boolean;
parentId?: string;
backgroundColor?: string;
}
export type WidgetRowCols = {
leftColumn: number;
rightColumn: number;
topRow: number;
bottomRow: number;
};
export interface WidgetCardProps {
type: WidgetType;
2019-08-29 11:22:09 +00:00
key?: string;
2019-09-26 11:37:09 +00:00
widgetCardName: string;
2019-08-29 11:22:09 +00:00
icon: string;
2019-08-21 12:49:16 +00:00
}
export const WidgetOperations = {
MOVE: "MOVE",
RESIZE: "RESIZE",
ADD_CHILD: "ADD_CHILD",
REMOVE_CHILD: "REMOVE_CHILD",
UPDATE_PROPERTY: "UPDATE_PROPERTY",
DELETE: "DELETE",
};
export type WidgetOperation = (typeof WidgetOperations)[keyof typeof WidgetOperations];
2019-09-09 09:08:54 +00:00
export default BaseWidget;