* initial commit * props hoc * changes * removed ignores and withWidgetProps * added extra props to canvasStructure * widget props changes * list widget changes * reintroduced widget props hook and other refactors * remove warnings * added deepequal for childWidgets selector * fix global hotkeys and tabs widget jest test * fix main container test fix * fixed view mode width * fix form widget values * minor fix * fix skeleton * form widget validity fix * jest test fix * fixed tests: GlobalHotkeys, Tabs, CanvasSelectectionArena and fixed main container rendering * minor fix * minor comments * reverted commented code * simplified structure, selective redux state updates and other inconsistencies * fix junit test cases * stop form widget from force rendering children * fix test case * random commit to re run tests * update isFormValid prop only if it exists * detangling circular dependency * fixing cypress tests * cleaned up code * clean up man cnavas props and fix jest cases * fix rendering order of child widgets for canvas * fix dropdown reset spec * adding comments * cleaning up unwanted code * fix multiselect widget on deploy * adressing review comments * addressing minor review comment changes * destructuring modal widget child and fix test case * fix communityIssues cypress spec * rewrite isVisible logic to match previous behaviour * merging widget props with component props before checking isVisible * adressing review comments for modal widget's isVisible Co-authored-by: rahulramesha <rahul@appsmith.com>
304 lines
9.3 KiB
TypeScript
304 lines
9.3 KiB
TypeScript
import React, { useContext, memo, useMemo } from "react";
|
|
import {
|
|
WidgetOperations,
|
|
WidgetRowCols,
|
|
WidgetProps,
|
|
} from "widgets/BaseWidget";
|
|
import { EditorContext } from "components/editorComponents/EditorContextProvider";
|
|
import {
|
|
UIElementSize,
|
|
computeFinalRowCols,
|
|
computeRowCols,
|
|
} from "./ResizableUtils";
|
|
import {
|
|
useShowPropertyPane,
|
|
useShowTableFilterPane,
|
|
useWidgetDragResize,
|
|
} from "utils/hooks/dragResizeHooks";
|
|
import { useSelector } from "react-redux";
|
|
import { AppState } from "reducers";
|
|
import Resizable from "resizable/resizenreflow";
|
|
import { omit, get } from "lodash";
|
|
import { getSnapColumns } from "utils/WidgetPropsUtils";
|
|
import {
|
|
VisibilityContainer,
|
|
LeftHandleStyles,
|
|
RightHandleStyles,
|
|
TopHandleStyles,
|
|
BottomHandleStyles,
|
|
TopLeftHandleStyles,
|
|
TopRightHandleStyles,
|
|
BottomLeftHandleStyles,
|
|
BottomRightHandleStyles,
|
|
} from "./ResizeStyledComponents";
|
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
|
import {
|
|
previewModeSelector,
|
|
snipingModeSelector,
|
|
} from "selectors/editorSelectors";
|
|
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
|
import { focusWidget } from "actions/widgetActions";
|
|
import { GridDefaults } from "constants/WidgetConstants";
|
|
import { DropTargetContext } from "./DropTargetComponent";
|
|
import { XYCord } from "pages/common/CanvasArenas/hooks/useCanvasDragging";
|
|
import { getParentToOpenSelector } from "selectors/widgetSelectors";
|
|
|
|
export type ResizableComponentProps = WidgetProps & {
|
|
paddingOffset: number;
|
|
};
|
|
|
|
export const ResizableComponent = memo(function ResizableComponent(
|
|
props: ResizableComponentProps,
|
|
) {
|
|
// Fetch information from the context
|
|
const { updateWidget } = useContext(EditorContext);
|
|
|
|
const isSnipingMode = useSelector(snipingModeSelector);
|
|
const isPreviewMode = useSelector(previewModeSelector);
|
|
|
|
const showPropertyPane = useShowPropertyPane();
|
|
const showTableFilterPane = useShowTableFilterPane();
|
|
const { selectWidget } = useWidgetSelection();
|
|
const { setIsResizing } = useWidgetDragResize();
|
|
const selectedWidget = useSelector(
|
|
(state: AppState) => state.ui.widgetDragResize.lastSelectedWidget,
|
|
);
|
|
const selectedWidgets = useSelector(
|
|
(state: AppState) => state.ui.widgetDragResize.selectedWidgets,
|
|
);
|
|
const focusedWidget = useSelector(
|
|
(state: AppState) => state.ui.widgetDragResize.focusedWidget,
|
|
);
|
|
|
|
const isDragging = useSelector(
|
|
(state: AppState) => state.ui.widgetDragResize.isDragging,
|
|
);
|
|
const isResizing = useSelector(
|
|
(state: AppState) => state.ui.widgetDragResize.isResizing,
|
|
);
|
|
const parentWidgetToSelect = useSelector(
|
|
getParentToOpenSelector(props.widgetId),
|
|
);
|
|
|
|
const isWidgetFocused =
|
|
focusedWidget === props.widgetId ||
|
|
selectedWidget === props.widgetId ||
|
|
selectedWidgets.includes(props.widgetId);
|
|
|
|
// Calculate the dimensions of the widget,
|
|
// The ResizableContainer's size prop is controlled
|
|
const dimensions: UIElementSize = {
|
|
width:
|
|
(props.rightColumn - props.leftColumn) * props.parentColumnSpace -
|
|
2 * props.paddingOffset,
|
|
height:
|
|
(props.bottomRow - props.topRow) * props.parentRowSpace -
|
|
2 * props.paddingOffset,
|
|
};
|
|
|
|
// onResize handler
|
|
const getResizedPositions = (
|
|
newDimensions: UIElementSize,
|
|
position: XYCord,
|
|
) => {
|
|
const delta: UIElementSize = {
|
|
height: newDimensions.height - dimensions.height,
|
|
width: newDimensions.width - dimensions.width,
|
|
};
|
|
const newRowCols: WidgetRowCols = computeRowCols(delta, position, props);
|
|
let canResizeHorizontally = true,
|
|
canResizeVertically = true;
|
|
|
|
// this is required for list widget so that template have no collision
|
|
if (props.ignoreCollision)
|
|
return {
|
|
canResizeHorizontally,
|
|
canResizeVertically,
|
|
};
|
|
|
|
if (
|
|
newRowCols &&
|
|
(newRowCols.rightColumn > getSnapColumns() ||
|
|
newRowCols.leftColumn < 0 ||
|
|
newRowCols.rightColumn - newRowCols.leftColumn < 2)
|
|
) {
|
|
canResizeHorizontally = false;
|
|
}
|
|
|
|
if (
|
|
newRowCols &&
|
|
(newRowCols.topRow < 0 || newRowCols.bottomRow - newRowCols.topRow < 4)
|
|
) {
|
|
canResizeVertically = false;
|
|
}
|
|
|
|
const resizedPositions = {
|
|
id: props.widgetId,
|
|
left: newRowCols.leftColumn,
|
|
top: newRowCols.topRow,
|
|
bottom: newRowCols.bottomRow,
|
|
right: newRowCols.rightColumn,
|
|
};
|
|
|
|
// Check if new row cols are occupied by sibling widgets
|
|
return {
|
|
canResizeHorizontally,
|
|
canResizeVertically,
|
|
resizedPositions,
|
|
};
|
|
};
|
|
|
|
// onResizeStop handler
|
|
// when done resizing, check if;
|
|
// 1) There is no collision
|
|
// 2) There is a change in widget size
|
|
// Update widget, if both of the above are true.
|
|
const updateSize = (newDimensions: UIElementSize, position: XYCord) => {
|
|
// Get the difference in size of the widget, before and after resizing.
|
|
const delta: UIElementSize = {
|
|
height: newDimensions.height - dimensions.height,
|
|
width: newDimensions.width - dimensions.width,
|
|
};
|
|
|
|
// Get the updated Widget rows and columns props
|
|
// False, if there is collision
|
|
// False, if none of the rows and cols have changed.
|
|
const newRowCols: WidgetRowCols | false = computeFinalRowCols(
|
|
delta,
|
|
position,
|
|
props,
|
|
);
|
|
|
|
if (newRowCols) {
|
|
updateWidget &&
|
|
updateWidget(WidgetOperations.RESIZE, props.widgetId, {
|
|
...newRowCols,
|
|
parentId: props.parentId,
|
|
snapColumnSpace: props.parentColumnSpace,
|
|
snapRowSpace: props.parentRowSpace,
|
|
});
|
|
}
|
|
// Tell the Canvas that we've stopped resizing
|
|
// Put it later in the stack so that other updates like click, are not propagated to the parent container
|
|
setTimeout(() => {
|
|
setIsResizing && setIsResizing(false);
|
|
}, 0);
|
|
// Tell the Canvas to put the focus back to this widget
|
|
// By setting the focus, we enable the control buttons on the widget
|
|
selectWidget &&
|
|
selectedWidget !== props.widgetId &&
|
|
parentWidgetToSelect?.widgetId !== props.widgetId &&
|
|
selectWidget(props.widgetId);
|
|
|
|
if (parentWidgetToSelect) {
|
|
selectWidget &&
|
|
selectedWidget !== parentWidgetToSelect.widgetId &&
|
|
selectWidget(parentWidgetToSelect.widgetId);
|
|
focusWidget(parentWidgetToSelect.widgetId);
|
|
} else {
|
|
selectWidget &&
|
|
selectedWidget !== props.widgetId &&
|
|
selectWidget(props.widgetId);
|
|
}
|
|
// Property pane closes after a resize/drag
|
|
showPropertyPane && showPropertyPane();
|
|
AnalyticsUtil.logEvent("WIDGET_RESIZE_END", {
|
|
widgetName: props.widgetName,
|
|
widgetType: props.type,
|
|
startHeight: dimensions.height,
|
|
startWidth: dimensions.width,
|
|
endHeight: newDimensions.height,
|
|
endWidth: newDimensions.width,
|
|
});
|
|
};
|
|
|
|
const handleResizeStart = () => {
|
|
setIsResizing && !isResizing && setIsResizing(true);
|
|
selectWidget &&
|
|
selectedWidget !== props.widgetId &&
|
|
selectWidget(props.widgetId);
|
|
// Make sure that this tableFilterPane should close
|
|
showTableFilterPane && showTableFilterPane();
|
|
AnalyticsUtil.logEvent("WIDGET_RESIZE_START", {
|
|
widgetName: props.widgetName,
|
|
widgetType: props.type,
|
|
});
|
|
};
|
|
const handles = useMemo(() => {
|
|
const allHandles = {
|
|
left: LeftHandleStyles,
|
|
top: TopHandleStyles,
|
|
bottom: BottomHandleStyles,
|
|
right: RightHandleStyles,
|
|
bottomRight: BottomRightHandleStyles,
|
|
topLeft: TopLeftHandleStyles,
|
|
topRight: TopRightHandleStyles,
|
|
bottomLeft: BottomLeftHandleStyles,
|
|
};
|
|
|
|
return omit(allHandles, get(props, "disabledResizeHandles", []));
|
|
}, [props]);
|
|
|
|
const isEnabled =
|
|
!isDragging &&
|
|
isWidgetFocused &&
|
|
!props.resizeDisabled &&
|
|
!isSnipingMode &&
|
|
!isPreviewMode;
|
|
const isMultiSelectedWidget =
|
|
selectedWidgets &&
|
|
selectedWidgets.length > 1 &&
|
|
selectedWidgets.includes(props.widgetId);
|
|
const { updateDropTargetRows } = useContext(DropTargetContext);
|
|
|
|
const gridProps = {
|
|
parentColumnSpace: props.parentColumnSpace,
|
|
parentRowSpace: props.parentRowSpace,
|
|
paddingOffset: props.paddingOffset,
|
|
maxGridColumns: GridDefaults.DEFAULT_GRID_COLUMNS,
|
|
};
|
|
|
|
const originalPositions = {
|
|
id: props.widgetId,
|
|
left: props.leftColumn,
|
|
top: props.topRow,
|
|
bottom: props.bottomRow,
|
|
right: props.rightColumn,
|
|
};
|
|
const updateBottomRow = (bottom: number) => {
|
|
if (props.parentId) {
|
|
updateDropTargetRows && updateDropTargetRows([props.parentId], bottom);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Resizable
|
|
allowResize={!isMultiSelectedWidget}
|
|
componentHeight={dimensions.height}
|
|
componentWidth={dimensions.width}
|
|
enable={isEnabled}
|
|
getResizedPositions={getResizedPositions}
|
|
gridProps={gridProps}
|
|
handles={handles}
|
|
onStart={handleResizeStart}
|
|
onStop={updateSize}
|
|
originalPositions={originalPositions}
|
|
parentId={props.parentId}
|
|
snapGrid={{ x: props.parentColumnSpace, y: props.parentRowSpace }}
|
|
updateBottomRow={updateBottomRow}
|
|
widgetId={props.widgetId}
|
|
// Used only for performance tracking, can be removed after optimization.
|
|
zWidgetId={props.widgetId}
|
|
zWidgetType={props.type}
|
|
>
|
|
<VisibilityContainer
|
|
padding={props.paddingOffset}
|
|
visible={!!props.isVisible}
|
|
>
|
|
{props.children}
|
|
</VisibilityContainer>
|
|
</Resizable>
|
|
);
|
|
});
|
|
export default ResizableComponent;
|