PromucFlow_constructor/app/client/src/widgets/BaseWidget.tsx
Pawan Kumar 1717b0e392
[Feature] Grid Widget (#2389)
* Updated test

* updated assertions

* Resizing image to take full width of table cell

* updated assertion

* Stop updating dynamicBindingPathList directly from widget

* Fix selectedRow and selectedRows computations

* Fix primaryColumns computations

* Updated test for derived column

* Added tests for computed value

* Added check clear data

* Reordering of test

* updated common method

* Made image size as 100% of table cell size

* add templating logic

* Updated flow and dsl

* Clear old primary columns

* Updated testname

* updated assertion

* use evaluated values for children

* Fix primary columns update on component mount and component update

* add isArray check

* remove property pane enhancement reducer

* add property pane enhancement reducer

* disable items other than template + fix running property enchancment on drop of list widget

* disbled drag, resize, settingsControl, drag for items other than template

* add grid options

* uncomment the widget operation for add child for grid children

* handle delete scenario for child widget in list widget

* WIP: Use the new delete and update property features

* add listdsl.json for testcases

* add test cases for correct no. of items being rendered

* add test cases currentItem binding in list widget

* change dragEnabled to dragDisabled

* change resizeEnabled to resizeDisabled

* change settingsControlEnabled to settingsControlDisabled

* change dropEnabled to dropDisabled

* update settingsControlDisabled default value

* Use deleteProperties in propertyControls

* Fix unsetting of array indices when deleting widget properties

* remove old TableWidget.tsx file

* Fix derived column property update on primary column property update

* Handle undefined primary columns

* Fix filepicker immutable prop issue

* Fix object.freeze issue when adding ids to the property pane configuration

* fix widget issue in grid

* Fix column actions dynamicBindingPathList inclusion issue

* remove consoles + fix typo around batch update

* Remove redundant tests

* js binding test for date picker

* hydate enhancement map on copy list widget

* check for dynamicleaf

* fixes

* improve check

* fix getNextWidgetName

* update template in list widget when copying

* updating template copy logic when copying widget

* update dynamicBindingPathList in copied widget

* Add path parameter to hidden functions in property pane configs

* fix copy bug when copying list widget

* add computed list property control

* Remove time column type

Fix editor prompt for currentRow

Fix undefined derivedColumns scenario

Remove validations for primaryColums and derivedColumns

Fix section toggle for video, image and button column types

* Fix table widget actions and custom column migrations

* Add logs for cyclical dependency map ♻️

* Process array differences

* add property control for list widget

* Fix onClick migrations

* Property pane config parity

* binding and trigger paths from the property pane config (#2920)

* try react virtualized library

* Fix unit test

* Fix unit test 

* Fix minor issues in table widget

* Add default meta props to binding paths to ensure eval and validation

* Dummy commit 🎉

* Remove unnecessary datepicker test

Fix chart data as string issue

* Achieve table column sorting and resizing parity with release

* handle scenario where last column isn't available to access

* Fix for panel config path not existing in the widget

* Fix bindings in currentRow (default)

Add dummy property pane config for canvas widget

* Update canvas widgets with dynamicPathLists on delete of property paths

* Add all diffs to change paths and trim later

* Add back default properties 🚶🏻‍♂️

* Use object based paths instead of arrays for primaryColumns and derivedColumns

* Fix issue in reordered columns

* Fix inccorect update order

* add virtualized list

* Fix failing property pane tests

* minor change

* minor list widget change

* Remove .vscode from git

* Rename ads to alloy

Fix isVisible in list widget

* move grid component to widget folder

* fix import in widget registry

* add sticky row in virtualized list

* add sticky container

* Fix Height of grid widget items container

* fix dragging of items in children other than template children

* update list widget

* update list widget

* Fix padding in list widget

* hide scrollbar in list widget list

* fix copy bug in list widget

* regenrate enhancement map on undo delete widget

* Use enhancementmap for autocomplete in list widget

Basic styles for list widget scrollbar

* add custom control in widget config

* minor commit

* update scrollbar styles

* remove unused variable

* fix typo in custom control

* comment out test cases

* remove unused imports

* remove unused imports

* add JSON stringify in interweave

* add noPad styling in dragLayer for noPad prop

* implement grid gap

* add list item background color prop

* add white color in color picker control

* fix gap in last list item

* remove onBeforeParse in textcomponent

* remove virtualization in grid widget

* allow overflow-y

* add onListItemClick action

* add beta label

* add pagination

* fix actions in pagination in list widget

* add list widget icon

* add list background color default value

* remove extra div

* fix pagination issue

* fix list widget crashing on perpage change

* extract child operation function to widgetblueprint saga

* refactor enhancements

* add enhancement hook

* refactor propertyUpdate hook enhancment

* remove enhacement map

* revert renaming ads to alloy

* add autopagination

* Cleanup unused vars

Re-write loop using map

Fix binding with external input widget

* update default background color

* remove unnessary scrol + fix pagination per page

* remove console.log

* use grid gap in pixel instead of snap

* fix list widget tests for binding

* add tests for on click action and pagination

* remove unnecessary imports

* remove overflow hidden in list component

* Add feature to enable template actions

* update property pane help text for list widget

* disable pagination in editor view

* update property pane options

* add test case for action

* uncomment tests

* fix grid gap validation

* update test cases

* fix property pane opening issue for list tempalte

* Disable form widgets in list widget

* fix template issue for actions

* add validation tests for list data

* update starting template

* add selectedRow + enable pagination in edit mode

* remove extra padding in list widget + popper fix on settingDisabled

* add stop propagation for button click

* fix click event in edit mode

* disallow filepicker widget for list widget

* add test for list widget entity definition for selectItem

* remove unused imports

* fix test

* remove evaluated value for list child widgets

* add comment

* remove log

* fix copying bug in list widget

* add check for not allowing template to copy

* fix test

* add test for property pane actions

* remove unused import

* add draglayercomponent test

* add test for draggable component

* add test for evaluatedvalue popup

* add test for messages.ts

* add test for widgeticons

* add test for property pane selector

* add test for widget config response

* start testing widget configresponse

* add test for enhancements in widget config

* add test for codeeditor

* add test for base widget + list widget

* add test for executeWidgetBlueprintChildOperations

* remove unused import

* add test for widget operation utils

* remove unused import

* add test for handleSpecificCasesWhilePasting

* remove unused function

* remove unused import

* add empty list styling

* resolve all review comments

* fix message test

* add test for widget operation utils

* fix merge conflicts

* move validations in property config

Co-authored-by: Abhinav Jha <abhinav@appsmith.com>
Co-authored-by: nandan.anantharamu <nandan.anantharamu@thoughtspot.com>
Co-authored-by: vicky-primathon.in <vicky.bansal@primathon.in>
Co-authored-by: Pawan Kumar <pawankumar@Pawans-MacBook-Pro.local>
Co-authored-by: Piyush <piyush@codeitout.com>
Co-authored-by: hetunandu <hetu@appsmith.com>
Co-authored-by: Hetu Nandu <hetunandu@gmail.com>
Co-authored-by: root <root@DESKTOP-9GENCK0.localdomain>
2021-04-23 11:13:13 +05:30

426 lines
12 KiB
TypeScript

/**
* 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,
RenderModes,
CSSUnits,
} from "constants/WidgetConstants";
import React, { Component, ReactNode } from "react";
import {
PositionType,
CSSUnit,
CONTAINER_GRID_PADDING,
} from "constants/WidgetConstants";
import DraggableComponent from "components/editorComponents/DraggableComponent";
import ResizableComponent from "components/editorComponents/ResizableComponent";
import { ExecuteActionPayload } from "constants/AppsmithActionConstants/ActionConstants";
import PositionedContainer from "components/designSystems/appsmith/PositionedContainer";
import WidgetNameComponent from "components/editorComponents/WidgetNameComponent";
import shallowequal from "shallowequal";
import { PositionTypes } from "constants/WidgetConstants";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
import ErrorBoundary from "components/editorComponents/ErrorBoundry";
import { DerivedPropertiesMap } from "utils/WidgetFactory";
import {
WidgetDynamicPathListProps,
WidgetEvaluatedProps,
} from "../utils/DynamicBindingUtils";
import { PropertyPaneConfig } from "constants/PropertyControlConstants";
import { BatchPropertyUpdatePayload } from "actions/controlActions";
/***
* 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,
K extends WidgetState
> extends Component<T, K> {
static contextType = EditorContext;
static getPropertyPaneConfig(): PropertyPaneConfig[] {
return [];
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {};
}
// TODO Find a way to enforce this, (dont let it be set)
static getMetaPropertiesMap(): Record<string, any> {
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(actionPayload: ExecuteActionPayload): void {
const { executeAction } = this.context;
executeAction && executeAction(actionPayload);
}
disableDrag(disable: boolean) {
const { disableDrag } = this.context;
disableDrag && disable !== undefined && disableDrag(disable);
}
updateWidget(
operationName: string,
widgetId: string,
widgetProperties: any,
): void {
const { updateWidget } = this.context;
updateWidget && updateWidget(operationName, widgetId, widgetProperties);
}
deleteWidgetProperty(propertyPaths: string[]): void {
const { deleteWidgetProperty } = this.context;
const { widgetId } = this.props;
if (deleteWidgetProperty && widgetId) {
deleteWidgetProperty(widgetId, propertyPaths);
}
}
batchUpdateWidgetProperty(updates: BatchPropertyUpdatePayload): void {
const { batchUpdateWidgetProperty } = this.context;
const { widgetId } = this.props;
if (batchUpdateWidgetProperty && widgetId) {
batchUpdateWidgetProperty(widgetId, updates);
}
}
updateWidgetProperty(propertyName: string, propertyValue: any): void {
this.batchUpdateWidgetProperty({
modify: { [propertyName]: propertyValue },
});
}
resetChildrenMetaProperty(widgetId: string) {
const { resetChildrenMetaProperty } = this.context;
resetChildrenMetaProperty(widgetId);
}
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
componentDidUpdate(prevProps: T) {}
componentDidMount(): void {}
/* eslint-enable @typescript-eslint/no-empty-function */
getComponentDimensions = () => {
return this.calculateWidgetBounds(
this.props.rightColumn,
this.props.leftColumn,
this.props.topRow,
this.props.bottomRow,
this.props.parentColumnSpace,
this.props.parentRowSpace,
);
};
calculateWidgetBounds(
rightColumn: number,
leftColumn: number,
topRow: number,
bottomRow: number,
parentColumnSpace: number,
parentRowSpace: number,
): {
componentWidth: number;
componentHeight: number;
} {
return {
componentWidth: (rightColumn - leftColumn) * parentColumnSpace,
componentHeight: (bottomRow - topRow) * parentRowSpace,
};
}
render() {
return this.getWidgetView();
}
/**
* this function is responsive for making the widget resizable.
* A widget can be made by non-resizable by passing resizeDisabled prop.
*
* @param content
*/
makeResizable(content: ReactNode) {
return (
<ResizableComponent
{...this.props}
paddingOffset={PositionedContainer.padding}
>
{content}
</ResizableComponent>
);
}
/**
* this functions wraps the widget in a component that shows a setting control at the top right
* which gets shown on hover. A widget can enable/disable this by setting `disablePropertyPane` prop
*
* @param content
* @param showControls
*/
showWidgetName(content: ReactNode, showControls = false) {
return (
<>
{!this.props.disablePropertyPane && (
<WidgetNameComponent
widgetName={this.props.widgetName}
widgetId={this.props.widgetId}
parentId={this.props.parentId}
type={this.props.type}
showControls={showControls}
/>
)}
{content}
</>
);
}
/**
* wraps the widget in a draggable component.
* Note: widget drag can be disabled by setting `dragDisabled` prop to true
*
* @param content
*/
makeDraggable(content: ReactNode) {
return <DraggableComponent {...this.props}>{content}</DraggableComponent>;
}
makePositioned(content: ReactNode) {
const style = this.getPositionStyle();
return (
<PositionedContainer
widgetId={this.props.widgetId}
widgetType={this.props.type}
style={style}
>
{content}
</PositionedContainer>
);
}
addErrorBoundary(content: ReactNode) {
return <ErrorBoundary>{content}</ErrorBoundary>;
}
private getWidgetView(): ReactNode {
let content: ReactNode;
switch (this.props.renderMode) {
case RenderModes.CANVAS:
content = this.getCanvasView();
if (!this.props.detachFromLayout) {
if (!this.props.resizeDisabled) content = this.makeResizable(content);
content = this.showWidgetName(content);
content = this.makeDraggable(content);
content = this.makePositioned(content);
}
return content;
// return this.getCanvasView();
case RenderModes.PAGE:
content = this.getPageView();
if (this.props.isVisible) {
content = this.addErrorBoundary(content);
if (!this.props.detachFromLayout) {
content = this.makePositioned(content);
}
return content;
}
return <React.Fragment />;
default:
throw Error("RenderMode not defined");
}
}
abstract getPageView(): ReactNode;
getCanvasView(): ReactNode {
const content = this.getPageView();
return this.addErrorBoundary(content);
}
// TODO(abhinav): Maybe make this a pure component to bailout from updating altogether.
// This would involve making all widgets which have "states" to not have states,
// as they're extending this one.
shouldComponentUpdate(nextProps: WidgetProps, nextState: WidgetState) {
return (
!shallowequal(nextProps, this.props) ||
!shallowequal(nextState, this.state)
);
}
/**
* generates styles that positions the widget
*/
private getPositionStyle(): BaseStyle {
const { componentHeight, componentWidth } = this.getComponentDimensions();
return {
positionType: PositionTypes.ABSOLUTE,
componentHeight,
componentWidth,
yPosition:
this.props.topRow * this.props.parentRowSpace +
(this.props.noContainerOffset ? 0 : CONTAINER_GRID_PADDING),
xPosition:
this.props.leftColumn * this.props.parentColumnSpace +
(this.props.noContainerOffset ? 0 : CONTAINER_GRID_PADDING),
xPositionUnit: CSSUnits.PIXEL,
yPositionUnit: CSSUnits.PIXEL,
};
}
// TODO(abhinav): These defaultProps seem unneccessary. Check it out.
static defaultProps: Partial<WidgetProps> | undefined = {
parentRowSpace: 1,
parentColumnSpace: 1,
topRow: 0,
leftColumn: 0,
isLoading: false,
renderMode: RenderModes.CANVAS,
dragDisabled: false,
dropDisabled: false,
isDeletable: true,
resizeDisabled: false,
disablePropertyPane: false,
};
}
export interface BaseStyle {
componentHeight: number;
componentWidth: number;
positionType: PositionType;
xPosition: number;
yPosition: number;
xPositionUnit: CSSUnit;
yPositionUnit: CSSUnit;
heightUnit?: CSSUnit;
widthUnit?: CSSUnit;
}
export type WidgetState = Record<string, unknown>;
export interface WidgetBuilder<T extends WidgetProps, S extends WidgetState> {
buildWidget(widgetProps: T): JSX.Element;
}
export interface WidgetBaseProps {
widgetId: string;
type: WidgetType;
widgetName: string;
parentId?: string;
renderMode: RenderMode;
version: number;
}
export type WidgetRowCols = {
leftColumn: number;
rightColumn: number;
topRow: number;
bottomRow: number;
minHeight?: number; // Required to reduce the size of CanvasWidgets.
};
export interface WidgetPositionProps extends WidgetRowCols {
parentColumnSpace: number;
parentRowSpace: number;
// The detachFromLayout flag tells use about the following properties when enabled
// 1) Widget does not drag/resize
// 2) Widget CAN (but not neccessarily) be a dropTarget
// Examples: MainContainer is detached from layout,
// MODAL_WIDGET is also detached from layout.
detachFromLayout?: boolean;
noContainerOffset?: boolean; // This won't offset the child in parent
}
export const WIDGET_STATIC_PROPS = {
leftColumn: true,
rightColumn: true,
topRow: true,
bottomRow: true,
minHeight: true,
parentColumnSpace: true,
parentRowSpace: true,
children: true,
type: true,
widgetId: true,
widgetName: true,
parentId: true,
renderMode: true,
detachFromLayout: true,
noContainerOffset: false,
};
export interface WidgetDisplayProps {
//TODO(abhinav): Some of these props are mandatory
isVisible?: boolean;
isLoading: boolean;
isDisabled?: boolean;
backgroundColor?: string;
}
export interface WidgetDataProps
extends WidgetBaseProps,
WidgetPositionProps,
WidgetDisplayProps {}
export interface WidgetProps
extends WidgetDataProps,
WidgetDynamicPathListProps,
WidgetEvaluatedProps {
key?: string;
isDefaultClickDisabled?: boolean;
[key: string]: any;
}
export interface WidgetCardProps {
type: WidgetType;
key?: string;
widgetCardName: string;
isBeta?: boolean;
}
export const WidgetOperations = {
MOVE: "MOVE",
RESIZE: "RESIZE",
ADD_CHILD: "ADD_CHILD",
UPDATE_PROPERTY: "UPDATE_PROPERTY",
DELETE: "DELETE",
ADD_CHILDREN: "ADD_CHILDREN",
};
export type WidgetOperation = typeof WidgetOperations[keyof typeof WidgetOperations];
export default BaseWidget;