PromucFlow_constructor/app/client/src/widgets/BaseWidget.tsx
2023-07-27 16:13:37 +05:30

932 lines
28 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 { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import type { BatchPropertyUpdatePayload } from "actions/controlActions";
import AutoHeightContainerWrapper from "components/autoHeight/AutoHeightContainerWrapper";
import AutoHeightOverlayContainer from "components/autoHeightOverlay";
import FlexComponent from "components/designSystems/appsmith/autoLayout/FlexComponent";
import PositionedContainer from "components/designSystems/appsmith/PositionedContainer";
import DraggableComponent from "components/editorComponents/DraggableComponent";
import type { EditorContextType } from "components/editorComponents/EditorContextProvider";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
import ErrorBoundary from "components/editorComponents/ErrorBoundry";
import ResizableComponent from "components/editorComponents/ResizableComponent";
import SnipeableComponent from "components/editorComponents/SnipeableComponent";
import WidgetNameComponent from "components/editorComponents/WidgetNameComponent";
import type { ExecuteTriggerPayload } from "constants/AppsmithActionConstants/ActionConstants";
import type { PropertyPaneConfig } from "constants/PropertyControlConstants";
import type {
CSSUnit,
PositionType,
RenderMode,
WidgetTags,
WidgetType,
} from "constants/WidgetConstants";
import { FLEXBOX_PADDING } from "constants/WidgetConstants";
import {
GridDefaults,
RenderModes,
WIDGET_PADDING,
} from "constants/WidgetConstants";
import { ENTITY_TYPE } from "entities/AppsmithConsole";
import type { Stylesheet } from "entities/AppTheming";
import { get, isFunction, memoize } from "lodash";
import type { Context, ReactNode, RefObject } from "react";
import React, { Component } from "react";
import type {
ModifyMetaWidgetPayload,
UpdateMetaWidgetPropertyPayload,
} from "reducers/entityReducers/metaWidgetsReducer";
import { AppPositioningTypes } from "reducers/entityReducers/pageListReducer";
import type { SelectionRequestType } from "sagas/WidgetSelectUtils";
import shallowequal from "shallowequal";
import type { CSSProperties } from "styled-components";
import AnalyticsUtil from "utils/AnalyticsUtil";
import AppsmithConsole from "utils/AppsmithConsole";
import { ResponsiveBehavior } from "utils/autoLayout/constants";
import {
FlexVerticalAlignment,
LayoutDirection,
} from "utils/autoLayout/constants";
import type {
DataTreeEvaluationProps,
EvaluationError,
WidgetDynamicPathListProps,
} from "utils/DynamicBindingUtils";
import { EVAL_ERROR_PATH } from "utils/DynamicBindingUtils";
import type { DerivedPropertiesMap } from "utils/WidgetFactory";
import type { CanvasWidgetStructure, FlattenedWidgetProps } from "./constants";
import Skeleton from "./Skeleton";
import {
getWidgetMaxAutoHeight,
getWidgetMinAutoHeight,
isAutoHeightEnabledForWidget,
isAutoHeightEnabledForWidgetWithLimits,
shouldUpdateWidgetHeightAutomatically,
} from "./WidgetUtils";
import AutoLayoutDimensionObserver from "components/designSystems/appsmith/autoLayout/AutoLayoutDimensionObeserver";
import WidgetFactory from "utils/WidgetFactory";
import type { WidgetEntity } from "entities/DataTree/dataTreeFactory";
import WidgetComponentBoundary from "components/editorComponents/WidgetComponentBoundary";
import type { AutocompletionDefinitions } from "./constants";
import { getWidgetMinMaxDimensionsInPixel } from "utils/autoLayout/flexWidgetUtils";
/***
* 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
*
*/
const REFERENCE_KEY = "$$refs$$";
abstract class BaseWidget<
T extends WidgetProps,
K extends WidgetState,
TCache = unknown,
> extends Component<T, K> {
static contextType = EditorContext;
context!: React.ContextType<Context<EditorContextType<TCache>>>;
static getPropertyPaneConfig(): PropertyPaneConfig[] {
return [];
}
static getPropertyPaneContentConfig(): PropertyPaneConfig[] {
return [];
}
static getPropertyPaneStyleConfig(): PropertyPaneConfig[] {
return [];
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {};
}
static getDefaultPropertiesMap(): Record<string, any> {
return {};
}
// TODO Find a way to enforce this, (dont let it be set)
static getMetaPropertiesMap(): Record<string, any> {
return {};
}
static getStylesheetConfig(): Stylesheet {
return {};
}
static getAutocompleteDefinitions(): AutocompletionDefinitions {
return {};
}
/**
* getLoadingProperties returns a list of regexp's used to specify bindingPaths,
* which can set the isLoading prop of the widget.
* When:
* 1. the path is bound to an action (API/Query)
* 2. the action is currently in-progress
*
* if undefined, all paths can set the isLoading state
* if empty array, no paths can set the isLoading state
*/
static getLoadingProperties(): Array<RegExp> | undefined {
return;
}
/**
* Widget abstraction to register the widget type
* ```javascript
* getWidgetType() {
* return "MY_AWESOME_WIDGET",
* }
* ```
*/
/**
* Widgets can execute actions using this `executeAction` method.
* Triggers may be specific to the widget
*/
executeAction(actionPayload: ExecuteTriggerPayload): void {
const { executeAction } = this.context;
executeAction &&
executeAction({
...actionPayload,
source: {
id: this.props.widgetId,
name: this.props.widgetName,
},
});
actionPayload.triggerPropertyName &&
AppsmithConsole.info({
text: `${actionPayload.triggerPropertyName} triggered`,
source: {
type: ENTITY_TYPE.WIDGET,
id: this.props.widgetId,
name: this.props.widgetName,
},
});
}
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,
shouldReplay = true,
): void {
const { batchUpdateWidgetProperty } = this.context;
const { widgetId } = this.props;
if (batchUpdateWidgetProperty && widgetId) {
batchUpdateWidgetProperty(widgetId, updates, shouldReplay);
}
}
updateWidgetProperty(propertyName: string, propertyValue: any): void {
this.batchUpdateWidgetProperty({
modify: { [propertyName]: propertyValue },
});
}
resetChildrenMetaProperty(widgetId: string) {
const { resetChildrenMetaProperty } = this.context;
if (resetChildrenMetaProperty) resetChildrenMetaProperty(widgetId);
}
/*
This method calls the action to update widget height
We're not using `updateWidgetProperty`, because, the workflow differs
We will be computing properties of all widgets which are effected by
this change.
@param height number: Height of the widget's contents in pixels
@return void
TODO (abhinav): Make sure that this isn't called for scenarios which do not require it
This is for performance. We don't want unnecessary code to run
*/
updateAutoHeight = (height: number): void => {
const paddedHeight =
Math.ceil(
Math.ceil(height + WIDGET_PADDING * 2) /
GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
) * GridDefaults.DEFAULT_GRID_ROW_HEIGHT;
const shouldUpdate = shouldUpdateWidgetHeightAutomatically(
paddedHeight,
this.props,
);
const { updateWidgetAutoHeight } = this.context;
if (updateWidgetAutoHeight) {
const { widgetId } = this.props;
shouldUpdate && updateWidgetAutoHeight(widgetId, paddedHeight);
}
};
selectWidgetRequest = (
selectionRequestType: SelectionRequestType,
payload?: string[],
) => {
const { selectWidgetRequest } = this.context;
if (selectWidgetRequest) {
selectWidgetRequest(selectionRequestType, payload);
}
};
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unused-vars */
componentDidUpdate(prevProps: T, prevState?: K) {
if (
!this.props.deferRender &&
this.props.deferRender !== prevProps.deferRender
) {
this.deferredComponentDidRender();
}
}
componentDidMount(): void {}
/*
* With lazy rendering, skeleton loaders are rendered for below fold widgets.
* This Appsmith widget life cycle method that gets called when the actual widget
* component renders instead of the skeleton loader.
*/
deferredComponentDidRender(): void {}
/* eslint-enable @typescript-eslint/no-empty-function */
modifyMetaWidgets = (modifications: ModifyMetaWidgetPayload) => {
this.context.modifyMetaWidgets?.({
...modifications,
creatorId: this.props.widgetId,
});
};
deleteMetaWidgets = () => {
this.context?.deleteMetaWidgets?.({
creatorIds: [this.props.widgetId],
});
};
setWidgetCache = (data: TCache) => {
const key = this.getWidgetCacheKey();
if (key) {
this.context?.setWidgetCache?.(key, data);
}
};
updateMetaWidgetProperty = (payload: UpdateMetaWidgetPropertyPayload) => {
const { widgetId } = this.props;
this.context.updateMetaWidgetProperty?.({
...payload,
creatorId: widgetId,
});
};
getWidgetCache = () => {
const key = this.getWidgetCacheKey();
if (key) {
return this.context?.getWidgetCache?.(key);
}
};
getWidgetCacheKey = () => {
return this.props.metaWidgetId || this.props.widgetId;
};
setWidgetReferenceCache = <TRefCache,>(data: TRefCache) => {
const key = this.getWidgetCacheReferenceKey();
this.context?.setWidgetCache?.(`${key}.${REFERENCE_KEY}`, data);
};
getWidgetReferenceCache = <TRefCache,>() => {
const key = this.getWidgetCacheReferenceKey();
return this.context?.getWidgetCache?.<TRefCache>(`${key}.${REFERENCE_KEY}`);
};
getWidgetCacheReferenceKey = () => {
return this.props.referencedWidgetId || this.props.widgetId;
};
getComponentDimensions = () => {
return this.calculateWidgetBounds(
this.props.rightColumn,
this.props.leftColumn,
this.props.topRow,
this.props.bottomRow,
this.props.parentColumnSpace,
this.props.parentRowSpace,
this.props.mobileLeftColumn,
this.props.mobileRightColumn,
this.props.mobileTopRow,
this.props.mobileBottomRow,
this.props.isMobile,
this.props.isFlexChild,
);
};
calculateWidgetBounds(
rightColumn: number,
leftColumn: number,
topRow: number,
bottomRow: number,
parentColumnSpace: number,
parentRowSpace: number,
mobileLeftColumn?: number,
mobileRightColumn?: number,
mobileTopRow?: number,
mobileBottomRow?: number,
isMobile?: boolean,
isFlexChild?: boolean,
): {
componentWidth: number;
componentHeight: number;
} {
let left = leftColumn;
let right = rightColumn;
let top = topRow;
let bottom = bottomRow;
if (isFlexChild && isMobile) {
if (mobileLeftColumn !== undefined && parentColumnSpace !== 1) {
left = mobileLeftColumn;
}
if (mobileRightColumn !== undefined && parentColumnSpace !== 1) {
right = mobileRightColumn;
}
if (mobileTopRow !== undefined && parentRowSpace !== 1) {
top = mobileTopRow;
}
if (mobileBottomRow !== undefined && parentRowSpace !== 1) {
bottom = mobileBottomRow;
}
}
return {
componentWidth: (right - left) * parentColumnSpace,
componentHeight: (bottom - top) * parentRowSpace,
};
}
getLabelWidth = () => {
return (Number(this.props.labelWidth) || 0) * this.props.parentColumnSpace;
};
getErrorCount = memoize((evalErrors: Record<string, EvaluationError[]>) => {
return Object.values(evalErrors).reduce(
(prev, curr) => curr.length + prev,
0,
);
}, JSON.stringify);
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={WIDGET_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) {
const { componentWidth } = this.getComponentDimensions();
return !this.props.disablePropertyPane ? (
<>
<WidgetNameComponent
errorCount={this.getErrorCount(get(this.props, EVAL_ERROR_PATH, {}))}
parentId={this.props.parentId}
showControls={showControls}
topRow={this.props.detachFromLayout ? 4 : this.props.topRow}
type={this.props.type}
widgetId={this.props.widgetId}
widgetName={this.props.widgetName}
widgetWidth={componentWidth}
/>
{content}
</>
) : (
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>;
}
/**
* wraps the widget in a draggable component.
* Note: widget drag can be disabled by setting `dragDisabled` prop to true
*
* @param content
*/
makeSnipeable(content: ReactNode) {
return <SnipeableComponent {...this.props}>{content}</SnipeableComponent>;
}
makePositioned(content: ReactNode) {
const { componentHeight, componentWidth } = this.getComponentDimensions();
return (
<PositionedContainer
componentHeight={componentHeight}
componentWidth={componentWidth}
focused={this.props.focused}
isDisabled={this.props.isDisabled}
isVisible={this.props.isVisible}
leftColumn={this.props.leftColumn}
noContainerOffset={this.props.noContainerOffset}
parentColumnSpace={this.props.parentColumnSpace}
parentId={this.props.parentId}
parentRowSpace={this.props.parentRowSpace}
ref={this.props.wrapperRef}
resizeDisabled={this.props.resizeDisabled}
selected={this.props.selected}
topRow={this.props.topRow}
widgetId={this.props.widgetId}
widgetName={this.props.widgetName}
widgetType={this.props.type}
>
{content}
</PositionedContainer>
);
}
get isAutoLayoutMode() {
return this.props.appPositioningType === AppPositioningTypes.AUTO;
}
addErrorBoundary(content: ReactNode) {
return <ErrorBoundary>{content}</ErrorBoundary>;
}
addAutoHeightOverlay(content: ReactNode, style?: CSSProperties) {
// required when the limits have to be updated
// simultaneosuly when they move together
// to maintain the undo/redo stack
const onBatchUpdate = ({
maxHeight,
minHeight,
}: {
maxHeight?: number;
minHeight?: number;
}) => {
const modifyObj: Record<string, unknown> = {};
if (maxHeight !== undefined) {
modifyObj["maxDynamicHeight"] = Math.floor(
maxHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
}
if (minHeight !== undefined) {
modifyObj["minDynamicHeight"] = Math.floor(
minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
);
}
this.batchUpdateWidgetProperty({
modify: modifyObj,
postUpdateAction: ReduxActionTypes.CHECK_CONTAINERS_FOR_AUTO_HEIGHT,
});
AnalyticsUtil.logEvent("AUTO_HEIGHT_OVERLAY_HANDLES_UPDATE", modifyObj);
};
const onMaxHeightSet = (maxHeight: number) => onBatchUpdate({ maxHeight });
const onMinHeightSet = (minHeight: number) => onBatchUpdate({ minHeight });
return (
<>
<AutoHeightOverlayContainer
{...this.props}
batchUpdate={onBatchUpdate}
maxDynamicHeight={getWidgetMaxAutoHeight(this.props)}
minDynamicHeight={getWidgetMinAutoHeight(this.props)}
onMaxHeightSet={onMaxHeightSet}
onMinHeightSet={onMinHeightSet}
style={style}
/>
{content}
</>
);
}
makeFlex(content: ReactNode) {
const { componentHeight, componentWidth } = this.getComponentDimensions();
return (
<FlexComponent
alignment={this.props.alignment}
componentHeight={componentHeight}
componentWidth={componentWidth}
direction={this.props.direction || LayoutDirection.Horizontal}
flexVerticalAlignment={
this.props.flexVerticalAlignment || FlexVerticalAlignment.Bottom
}
focused={this.props.focused}
isMobile={this.props.isMobile || false}
isResizeDisabled={this.props.resizeDisabled}
parentColumnSpace={this.props.parentColumnSpace}
parentId={this.props.parentId}
renderMode={this.props.renderMode}
responsiveBehavior={this.props.responsiveBehavior}
selected={this.props.selected}
widgetId={this.props.widgetId}
widgetName={this.props.widgetName}
widgetType={this.props.type}
>
{content}
</FlexComponent>
);
}
addWidgetComponentBoundary = (
content: ReactNode,
widgetProps: WidgetProps,
) => (
<WidgetComponentBoundary widgetType={widgetProps.type}>
{content}
</WidgetComponentBoundary>
);
getWidgetComponent = () => {
const { renderMode, type } = this.props;
/**
* The widget mount calls the withWidgetProps with the widgetId and type to fetch the
* widget props. During the computation of the props (in withWidgetProps) if the evaluated
* values are not present (which will not be during mount), the widget type is changed to
* SKELETON_WIDGET.
*
* Note:- This is done to retain the old rendering flow without any breaking changes.
* This could be refactored into not changing the widget type but to have a boolean flag.
*/
if (type === "SKELETON_WIDGET" || this.props.deferRender) {
return <Skeleton />;
}
let content =
renderMode === RenderModes.CANVAS
? this.getCanvasView()
: this.getPageView();
// This `if` code is responsible for the unmount of the widgets
// while toggling the dynamicHeight property
// Adding a check for the Modal Widget early
// to avoid deselect Modal in its unmount effect.
if (
!this.props.isFlexChild &&
isAutoHeightEnabledForWidget(this.props) &&
!this.props.isAutoGeneratedWidget && // To skip list widget's auto generated widgets
!this.props.detachFromLayout // To skip Modal widget issue #18697
) {
return (
<AutoHeightContainerWrapper
onUpdateDynamicHeight={(height) => this.updateAutoHeight(height)}
widgetProps={this.props}
>
{content}
</AutoHeightContainerWrapper>
);
}
if (this.props.isFlexChild && !this.props.detachFromLayout) {
const autoDimensionConfig = WidgetFactory.getWidgetAutoLayoutConfig(
this.props.type,
).autoDimension;
const shouldObserveWidth = isFunction(autoDimensionConfig)
? autoDimensionConfig(this.props).width
: autoDimensionConfig?.width;
const shouldObserveHeight = isFunction(autoDimensionConfig)
? autoDimensionConfig(this.props).height
: autoDimensionConfig?.height;
if (!shouldObserveHeight && !shouldObserveWidth) return content;
const { componentHeight, componentWidth } = this.getComponentDimensions();
const { minHeight, minWidth } = getWidgetMinMaxDimensionsInPixel(
this.props,
this.props.mainCanvasWidth || 0,
);
return (
<AutoLayoutDimensionObserver
height={componentHeight}
isFillWidget={
this.props.responsiveBehavior === ResponsiveBehavior.Fill
}
minHeight={minHeight ?? 0}
minWidth={minWidth ?? 0}
onDimensionUpdate={this.updateWidgetDimensions}
shouldObserveHeight={shouldObserveHeight || false}
shouldObserveWidth={shouldObserveWidth || false}
type={this.props.type}
width={componentWidth}
>
{content}
</AutoLayoutDimensionObserver>
);
}
content = this.addWidgetComponentBoundary(content, this.props);
return this.addErrorBoundary(content);
};
private updateWidgetDimensions = (width: number, height: number) => {
const { updateWidgetDimension } = this.context;
if (!updateWidgetDimension) return;
updateWidgetDimension(
this.props.widgetId,
width + 2 * FLEXBOX_PADDING,
height + 2 * FLEXBOX_PADDING,
);
};
private getWidgetView(): ReactNode {
let content: ReactNode;
switch (this.props.renderMode) {
case RenderModes.CANVAS:
content = this.getWidgetComponent();
if (!this.props.detachFromLayout) {
if (
!this.props.resizeDisabled &&
this.props.type !== "SKELETON_WIDGET"
)
content = this.makeResizable(content);
content = this.showWidgetName(content);
content = this.makeDraggable(content);
content = this.makeSnipeable(content);
// NOTE: In sniping mode we are not blocking onClick events from PositionWrapper.
if (this.props.isFlexChild) {
content = this.makeFlex(content);
} else {
content = this.makePositioned(content);
}
if (isAutoHeightEnabledForWidgetWithLimits(this.props)) {
content = this.addAutoHeightOverlay(content);
}
}
return content;
// return this.getCanvasView();
case RenderModes.PAGE:
content = this.getWidgetComponent();
if (!this.props.detachFromLayout) {
if (this.props.isFlexChild) content = this.makeFlex(content);
else content = this.makePositioned(content);
}
return content;
default:
throw Error("RenderMode not defined");
}
}
updateOneClickBindingOptionsVisibility(visibility: boolean) {
const { updateOneClickBindingOptionsVisibility } = this.context;
updateOneClickBindingOptionsVisibility?.(visibility);
}
abstract getPageView(): ReactNode;
getCanvasView(): ReactNode {
return this.getPageView();
}
// 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)
);
}
// 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,
isFlexChild: false,
isMobile: 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 CanvasWidgetStructure,
S extends WidgetState,
> {
buildWidget(widgetProps: T): JSX.Element;
}
export interface WidgetBaseProps {
widgetId: string;
metaWidgetId?: string;
type: WidgetType;
widgetName: string;
parentId?: string;
renderMode: RenderMode;
version: number;
childWidgets?: WidgetEntity[];
flattenedChildCanvasWidgets?: Record<string, FlattenedWidgetProps>;
metaWidgetChildrenStructure?: CanvasWidgetStructure[];
referencedWidgetId?: string;
requiresFlatWidgetChildren?: boolean;
hasMetaWidgets?: boolean;
creatorId?: string;
isMetaWidget?: boolean;
suppressAutoComplete?: boolean;
suppressDebuggerError?: boolean;
disallowCopy?: boolean;
/**
* The keys of the props mentioned here would always be picked from the canvas widget
* rather than the evaluated values in withWidgetProps HOC.
* */
additionalStaticProps?: string[];
mainCanvasWidth?: number;
isMobile?: boolean;
}
export type WidgetRowCols = {
leftColumn: number;
rightColumn: number;
topRow: number;
bottomRow: number;
minHeight?: number; // Required to reduce the size of CanvasWidgets.
mobileLeftColumn?: number;
mobileRightColumn?: number;
mobileTopRow?: number;
mobileBottomRow?: number;
height?: number;
};
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
isFlexChild?: boolean;
direction?: LayoutDirection;
responsiveBehavior?: ResponsiveBehavior;
minWidth?: number; // Required to avoid squishing of widgets on mobile viewport.
isMobile?: boolean;
flexVerticalAlignment?: FlexVerticalAlignment;
appPositioningType?: AppPositioningTypes;
widthInPercentage?: number; // Stores the widget's width set by the user
mobileWidthInPercentage?: number;
}
export const WIDGET_DISPLAY_PROPS = {
isVisible: true,
isLoading: true,
isDisabled: true,
backgroundColor: true,
};
export interface WidgetError extends Error {
type: "property" | "configuration" | "other";
path?: string;
}
export interface WidgetErrorProps {
errors?: WidgetError[];
}
export interface WidgetDisplayProps {
//TODO(abhinav): Some of these props are mandatory
isVisible?: boolean;
isLoading: boolean;
isDisabled?: boolean;
backgroundColor?: string;
animateLoading?: boolean;
deferRender?: boolean;
wrapperRef?: RefObject<HTMLDivElement>;
selectedWidgetAncestry?: string[];
}
export interface WidgetDataProps
extends WidgetBaseProps,
WidgetErrorProps,
WidgetPositionProps,
WidgetDisplayProps {}
export interface WidgetProps
extends WidgetDataProps,
WidgetDynamicPathListProps,
DataTreeEvaluationProps {
key?: string;
isDefaultClickDisabled?: boolean;
[key: string]: any;
}
export interface WidgetCardProps {
rows: number;
columns: number;
type: WidgetType;
key?: string;
displayName: string;
icon: string;
isBeta?: boolean;
tags?: WidgetTags[];
}
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;