PromucFlow_constructor/app/client/src/components/editorComponents/DropTargetComponent.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

305 lines
9.5 KiB
TypeScript

import React, {
useState,
useContext,
ReactNode,
Context,
createContext,
useEffect,
memo,
} from "react";
import styled from "styled-components";
import { useDrop, XYCoord, DropTargetMonitor } from "react-dnd";
import { WidgetProps } from "widgets/BaseWidget";
import { WidgetConfigProps } from "reducers/entityReducers/widgetConfigReducer";
import {
widgetOperationParams,
noCollision,
getCanvasSnapRows,
} from "utils/WidgetPropsUtils";
import { EditorContext } from "components/editorComponents/EditorContextProvider";
import {
MAIN_CONTAINER_WIDGET_ID,
GridDefaults,
WidgetTypes,
} from "constants/WidgetConstants";
import { calculateDropTargetRows } from "./DropTargetUtils";
import DragLayerComponent from "./DragLayerComponent";
import { AppState } from "reducers";
import { useSelector } from "react-redux";
import {
useShowPropertyPane,
useWidgetSelection,
useCanvasSnapRowsUpdateHook,
} from "utils/hooks/dragResizeHooks";
import { getOccupiedSpaces } from "selectors/editorSelectors";
type DropTargetComponentProps = WidgetProps & {
children?: ReactNode;
snapColumnSpace: number;
snapRowSpace: number;
minHeight: number;
noPad?: boolean;
};
const StyledDropTarget = styled.div`
transition: height 100ms ease-in;
width: 100%;
position: relative;
background: none;
user-select: none;
`;
const Onboarding = () => {
return (
<div style={{ position: "fixed", left: "50%", top: "50vh" }}>
<h2 style={{ color: "#ccc" }}>Drag and drop a widget here</h2>
</div>
);
};
/*
This context will provide the function which will help the draglayer and resizablecomponents trigger
an update of the main container's rows
*/
export const DropTargetContext: Context<{
updateDropTargetRows?: (widgetId: string, row: number) => boolean;
persistDropTargetRows?: (widgetId: string, row: number) => void;
}> = createContext({});
export const DropTargetComponent = (props: DropTargetComponentProps) => {
const canDropTargetExtend = props.canExtend;
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
const { updateWidget } = useContext(EditorContext);
const occupiedSpaces = useSelector(getOccupiedSpaces);
const selectedWidget = useSelector(
(state: AppState) => state.ui.widgetDragResize.selectedWidget,
);
const isResizing = useSelector(
(state: AppState) => state.ui.widgetDragResize.isResizing,
);
const isDragging = useSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
const spacesOccupiedBySiblingWidgets =
occupiedSpaces && occupiedSpaces[props.widgetId]
? occupiedSpaces[props.widgetId]
: undefined;
const childWidgets = useSelector(
(state: AppState) => state.entities.canvasWidgets[props.widgetId].children,
);
const occupiedSpacesByChildren =
occupiedSpaces && occupiedSpaces[props.widgetId];
const [dropTargetOffset, setDropTargetOffset] = useState({ x: 0, y: 0 });
const [rows, setRows] = useState(snapRows);
const showPropertyPane = useShowPropertyPane();
const { selectWidget, focusWidget } = useWidgetSelection();
const updateCanvasSnapRows = useCanvasSnapRowsUpdateHook();
useEffect(() => {
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
setRows(snapRows);
}, [props.bottomRow, props.canExtend]);
const persistDropTargetRows = (widgetId: string, widgetBottomRow: number) => {
const newRows = calculateDropTargetRows(
widgetId,
widgetBottomRow,
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
occupiedSpacesByChildren,
);
const rowsToPersist = Math.max(
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
newRows,
);
setRows(rowsToPersist);
if (canDropTargetExtend) {
updateCanvasSnapRows(props.widgetId, rowsToPersist);
}
};
/* Update the rows of the main container based on the current widget's (dragging/resizing) bottom row */
const updateDropTargetRows = (widgetId: string, widgetBottomRow: number) => {
if (canDropTargetExtend) {
const newRows = calculateDropTargetRows(
widgetId,
widgetBottomRow,
props.minHeight / GridDefaults.DEFAULT_GRID_ROW_HEIGHT - 1,
occupiedSpacesByChildren,
);
if (rows < newRows) {
setRows(newRows);
return true;
}
return false;
}
return false;
};
const isChildFocused =
!!childWidgets &&
!!selectedWidget &&
childWidgets.length > 0 &&
childWidgets.indexOf(selectedWidget) > -1;
const isChildResizing = !!isResizing && isChildFocused;
// Make this component a drop target
const [{ isExactlyOver, isOver }, drop] = useDrop({
accept: Object.values(WidgetTypes),
options: {
arePropsEqual: () => {
return true;
},
},
drop(widget: WidgetProps & Partial<WidgetConfigProps>, monitor) {
// Make sure we're dropping in this container.
if (isExactlyOver) {
const updateWidgetParams = widgetOperationParams(
widget,
monitor.getSourceClientOffset() as XYCoord,
dropTargetOffset,
props.snapColumnSpace,
props.snapRowSpace,
widget.detachFromLayout ? MAIN_CONTAINER_WIDGET_ID : props.widgetId,
);
// const widgetBottomRow = getWidgetBottomRow(widget, updateWidgetParams);
const widgetBottomRow =
updateWidgetParams.payload.topRow +
(updateWidgetParams.payload.rows || widget.bottomRow - widget.topRow);
// Only show propertypane if this is a new widget.
// If it is not a new widget, then let the DraggableComponent handle it.
// Give evaluations a second to complete.
setTimeout(() => {
if (showPropertyPane && updateWidgetParams.payload.newWidgetId) {
showPropertyPane(updateWidgetParams.payload.newWidgetId);
// toggleEditWidgetName(updateWidgetParams.payload.newWidgetId, true);
}
}, 100);
// Select the widget if it is a new widget
selectWidget && selectWidget(widget.widgetId);
persistDropTargetRows(widget.widgetId, widgetBottomRow);
/* Finally update the widget */
updateWidget &&
updateWidget(
updateWidgetParams.operation,
updateWidgetParams.widgetId,
updateWidgetParams.payload,
);
}
return undefined;
},
// Collect isOver for ui transforms when hovering over this component
collect: (monitor: DropTargetMonitor) => ({
isExactlyOver: monitor.isOver({ shallow: true }),
isOver: monitor.isOver(),
}),
// Only allow drop if the drag object is directly over this component
// As opposed to the drag object being over a child component, or outside the component bounds
// Also only if the dropzone does not overlap any existing children
canDrop: (widget, monitor) => {
// Check if the draggable is the same as the dropTarget
if (isExactlyOver) {
const hasCollision = !noCollision(
monitor.getSourceClientOffset() as XYCoord,
props.snapColumnSpace,
props.snapRowSpace,
widget,
dropTargetOffset,
spacesOccupiedBySiblingWidgets,
rows,
GridDefaults.DEFAULT_GRID_COLUMNS,
);
return !hasCollision;
}
return false;
},
});
const handleBoundsUpdate = (rect: DOMRect) => {
if (rect.x !== dropTargetOffset.x || rect.y !== dropTargetOffset.y) {
setDropTargetOffset({
x: rect.x,
y: rect.y,
});
}
};
const handleFocus = (e: any) => {
if (!isResizing && !isDragging) {
if (!props.parentId) {
selectWidget && selectWidget(props.widgetId);
focusWidget && focusWidget(props.widgetId);
showPropertyPane && showPropertyPane();
} else {
selectWidget && selectWidget(props.parentId);
focusWidget && focusWidget(props.parentId);
}
}
// commenting this out to allow propagation of click events
// e.stopPropagation();
e.preventDefault();
};
const height = canDropTargetExtend
? `${Math.max(rows * props.snapRowSpace, props.minHeight)}px`
: "100%";
const border =
(isResizing || isDragging) &&
isExactlyOver &&
props.widgetId === MAIN_CONTAINER_WIDGET_ID
? "1px solid #DDDDDD"
: "1px solid transparent";
const dropRef = !props.dropDisabled ? drop : undefined;
return (
<DropTargetContext.Provider
value={{ updateDropTargetRows, persistDropTargetRows }}
>
<StyledDropTarget
onClick={handleFocus}
ref={dropRef}
style={{
height,
border,
}}
className={"t--drop-target"}
>
{props.children}
{!(childWidgets && childWidgets.length) &&
!isDragging &&
!props.parentId && <Onboarding />}
<DragLayerComponent
parentWidgetId={props.widgetId}
canDropTargetExtend={canDropTargetExtend}
parentRowHeight={props.snapRowSpace}
parentColumnWidth={props.snapColumnSpace}
visible={isExactlyOver || isChildResizing}
isOver={isExactlyOver}
occupiedSpaces={spacesOccupiedBySiblingWidgets}
onBoundsUpdate={handleBoundsUpdate}
parentRows={rows}
parentCols={props.snapColumns}
isResizing={isChildResizing}
noPad={props.noPad || false}
force={isDragging && !isOver && !props.parentId}
/>
</StyledDropTarget>
</DropTargetContext.Provider>
);
};
const MemoizedDropTargetComponent = memo(DropTargetComponent);
export default MemoizedDropTargetComponent;