diff --git a/app/client/public/index.html b/app/client/public/index.html
index a33d992710..fd226bd343 100755
--- a/app/client/public/index.html
+++ b/app/client/public/index.html
@@ -37,7 +37,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
-
React App
+ Appsmith | Editor
You need to enable JavaScript to run this app.
diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx
index 34b6872f51..45bb80c526 100644
--- a/app/client/src/constants/DefaultTheme.tsx
+++ b/app/client/src/constants/DefaultTheme.tsx
@@ -23,6 +23,15 @@ export type Theme = {
borders: { thickness: string; style: "dashed" | "solid"; color: Color }[];
};
+export const getColorWithOpacity = (color: Color, opacity: number) => {
+ color = color.slice(1);
+ const val = parseInt(color, 16);
+ const r = (val >> 16) & 255;
+ const g = (val >> 8) & 255;
+ const b = val & 255;
+ return `rgba(${r},${g},${b},${opacity})`;
+};
+
export const theme: Theme = {
radii: [0, 4, 8, 10, 20, 50],
fontSizes: [0, 10, 12, 14, 16, 18, 24, 28, 32, 48, 64],
diff --git a/app/client/src/editorComponents/DragLayerComponent.tsx b/app/client/src/editorComponents/DragLayerComponent.tsx
index 454d68b40a..727b604862 100644
--- a/app/client/src/editorComponents/DragLayerComponent.tsx
+++ b/app/client/src/editorComponents/DragLayerComponent.tsx
@@ -9,7 +9,6 @@ import DropTargetMask from "./DropTargetMask";
const WrappedDragLayer = styled.div`
position: absolute;
pointer-events: none;
- z-index: 10;
left: 0;
top: 0;
width: 100%;
@@ -26,6 +25,8 @@ type DragLayerProps = {
occupiedSpaces: OccupiedSpace[] | null;
onBoundsUpdate: Function;
isOver: boolean;
+ parentRows?: number;
+ parentCols?: number;
};
const DragLayerComponent = (props: DragLayerProps) => {
@@ -41,6 +42,8 @@ const DragLayerComponent = (props: DragLayerProps) => {
monitor.getItem(),
props.dropTargetOffset,
props.occupiedSpaces,
+ props.parentRows,
+ props.parentCols,
),
}),
);
diff --git a/app/client/src/editorComponents/DraggableComponent.tsx b/app/client/src/editorComponents/DraggableComponent.tsx
index 11892cfbfb..689efd4f8e 100644
--- a/app/client/src/editorComponents/DraggableComponent.tsx
+++ b/app/client/src/editorComponents/DraggableComponent.tsx
@@ -18,6 +18,8 @@ const DraggableWrapper = styled.div<{ show: boolean }>`
display: ${props => (props.show ? "block" : "none")};
}
display: block;
+ position: relative;
+ z-index: 1;
`;
const DragHandle = styled.div`
@@ -27,7 +29,6 @@ const DragHandle = styled.div`
cursor: move;
display: none;
cursor: grab;
- z-index: 11;
`;
const DeleteControl = styled.div`
@@ -36,7 +37,6 @@ const DeleteControl = styled.div`
top: -${props => props.theme.fontSizes[CONTROL_THEME_FONTSIZE_INDEX] / 2}px;
display: none;
cursor: pointer;
- z-index: 11;
`;
const moveControlIcon = ControlIcons.MOVE_CONTROL({
@@ -100,13 +100,13 @@ const DraggableComponent = (props: DraggableComponentProps) => {
props.style.componentHeight + (props.style.heightUnit || "px"),
}}
>
+ {props.children}
{moveControlIcon}
{deleteControlIcon}
- {props.children}
);
diff --git a/app/client/src/editorComponents/DropTargetComponent.tsx b/app/client/src/editorComponents/DropTargetComponent.tsx
index c3d96b7881..d0123e7977 100644
--- a/app/client/src/editorComponents/DropTargetComponent.tsx
+++ b/app/client/src/editorComponents/DropTargetComponent.tsx
@@ -1,6 +1,6 @@
import React, { useState, useContext } from "react";
import { WidgetProps } from "../widgets/BaseWidget";
-import { OccupiedSpace } from "../widgets/ContainerWidget";
+import { OccupiedSpaceContext } from "../widgets/ContainerWidget";
import { WidgetConfigProps } from "../reducers/entityReducers/widgetConfigReducer";
import { useDrop, XYCoord } from "react-dnd";
import { ContainerProps } from "./ContainerComponent";
@@ -15,7 +15,6 @@ type DropTargetComponentProps = ContainerProps & {
snapRows?: number;
snapColumnSpace: number;
snapRowSpace: number;
- occupiedSpaces: OccupiedSpace[] | null;
};
type DropTargetBounds = {
@@ -29,6 +28,7 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
// Hook to keep the offset of the drop target container in state
const [dropTargetOffset, setDropTargetOffset] = useState({ x: 0, y: 0 });
const { updateWidget } = useContext(WidgetFunctionsContext);
+ const occupiedSpaces = useContext(OccupiedSpaceContext);
// Make this component a drop target
const [{ isOver, isExactlyOver }, drop] = useDrop({
accept: Object.values(WidgetFactory.getWidgetTypes()),
@@ -69,7 +69,9 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
props.snapRowSpace,
widget,
dropTargetOffset,
- props.occupiedSpaces,
+ occupiedSpaces,
+ props.snapRows,
+ props.snapColumns,
);
}
return false;
@@ -88,7 +90,6 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
return (
{
? props.style.componentWidth + (props.style.widthUnit || "px")
: "100%",
top: 0,
- background: "white",
}}
>
{
visible={isOver}
isOver={isExactlyOver}
dropTargetOffset={dropTargetOffset}
- occupiedSpaces={props.occupiedSpaces}
+ occupiedSpaces={occupiedSpaces}
onBoundsUpdate={handleBoundsUpdate}
+ parentRows={props.snapRows}
+ parentCols={props.snapColumns}
/>
{props.children}
diff --git a/app/client/src/editorComponents/DropTargetMask.tsx b/app/client/src/editorComponents/DropTargetMask.tsx
index fe0b07c986..5e6c3ee84e 100644
--- a/app/client/src/editorComponents/DropTargetMask.tsx
+++ b/app/client/src/editorComponents/DropTargetMask.tsx
@@ -16,7 +16,6 @@ export const DropTargetMaskWrapper = styled.div`
right: 0;
width: 100%;
height: 100%;
- background: white;
${props =>
props.showGrid &&
css`
diff --git a/app/client/src/editorComponents/Dropzone.tsx b/app/client/src/editorComponents/Dropzone.tsx
index 8a49b268b8..313e44d1d5 100644
--- a/app/client/src/editorComponents/Dropzone.tsx
+++ b/app/client/src/editorComponents/Dropzone.tsx
@@ -6,10 +6,9 @@ import { theme } from "../constants/DefaultTheme";
const DropZoneWrapper = styled.div`
position: absolute;
- z-index: 10;
background: ${props => props.theme.colors.hover};
border: 1px dashed ${props => props.theme.colors.textAnchor};
- opacity: 0.7;
+ opacity: 0.6;
`;
type DropZoneProps = {
diff --git a/app/client/src/editorComponents/ResizableComponent.tsx b/app/client/src/editorComponents/ResizableComponent.tsx
index 02484f584e..1589eb170e 100644
--- a/app/client/src/editorComponents/ResizableComponent.tsx
+++ b/app/client/src/editorComponents/ResizableComponent.tsx
@@ -1,11 +1,15 @@
-import React, { useContext, CSSProperties } from "react";
+import React, { useContext, CSSProperties, useState } from "react";
import styled from "styled-components";
import { Rnd } from "react-rnd";
import { XYCoord } from "react-dnd";
import { WidgetProps, WidgetOperations } from "../widgets/BaseWidget";
+import { OccupiedSpaceContext } from "../widgets/ContainerWidget";
import { ContainerProps, ParentBoundsContext } from "./ContainerComponent";
+import { isDropZoneOccupied } from "../utils/WidgetPropsUtils";
import { ResizingContext } from "./DraggableComponent";
+import { FocusContext } from "../pages/Editor/Canvas";
import { WidgetFunctionsContext } from "../pages/Editor";
+import { theme, getColorWithOpacity } from "../constants/DefaultTheme";
export type ResizableComponentProps = WidgetProps & ContainerProps;
@@ -18,28 +22,23 @@ const handleStyles: {
top: {
height: "30px",
top: "-15px",
- zIndex: 11,
},
bottom: {
height: "30px",
bottom: "-15px",
- zIndex: 11,
},
left: {
width: "30px",
left: "-15px",
- zIndex: 11,
},
right: {
width: "30px",
right: "-15px",
- zIndex: 11,
},
};
const ResizableContainer = styled(Rnd)`
position: relative;
- z-index: 10;
border: ${props => {
return Object.values(props.theme.borders[0]).join(" ");
}};
@@ -50,7 +49,6 @@ const ResizableContainer = styled(Rnd)`
width: ${props => props.theme.spaces[2]}px;
height: ${props => props.theme.spaces[2]}px;
border-radius: ${props => props.theme.radii[5]}%;
- z-index: 9;
background: ${props => props.theme.colors.containerBorder};
}
&:after {
@@ -68,10 +66,49 @@ export const ResizableComponent = (props: ResizableComponentProps) => {
const { setIsResizing } = useContext(ResizingContext);
const { boundingParent } = useContext(ParentBoundsContext);
const { updateWidget } = useContext(WidgetFunctionsContext);
+ const { setFocus } = useContext(FocusContext);
+ const occupiedSpaces = useContext(OccupiedSpaceContext);
+ const [isColliding, setIsColliding] = useState(false);
+
let bounds = "body";
if (boundingParent && boundingParent.current) {
bounds = "." + boundingParent.current.className.split(" ")[1];
}
+
+ const checkForCollision = (
+ e: Event,
+ dir: any,
+ ref: any,
+ delta: { width: number; height: number },
+ position: XYCoord,
+ ) => {
+ const left = props.leftColumn + position.x / props.parentColumnSpace;
+ const top = props.topRow + position.y / props.parentRowSpace;
+
+ const right =
+ props.rightColumn + (delta.width + position.x) / props.parentColumnSpace;
+ const bottom =
+ props.bottomRow + (delta.height + position.y) / props.parentRowSpace;
+
+ if (
+ isDropZoneOccupied(
+ {
+ left,
+ top,
+ bottom,
+ right,
+ },
+ props.widgetId,
+ occupiedSpaces,
+ )
+ ) {
+ setIsColliding(true);
+ } else {
+ if (!!isColliding) {
+ setIsColliding(false);
+ }
+ }
+ };
const updateSize = (
e: Event,
dir: any,
@@ -80,6 +117,7 @@ export const ResizableComponent = (props: ResizableComponentProps) => {
position: XYCoord,
) => {
setIsResizing && setIsResizing(false);
+ setFocus && setFocus(props.widgetId);
const leftColumn = props.leftColumn + position.x / props.parentColumnSpace;
const topRow = props.topRow + position.y / props.parentRowSpace;
@@ -88,13 +126,16 @@ export const ResizableComponent = (props: ResizableComponentProps) => {
const bottomRow =
props.bottomRow + (delta.height + position.y) / props.parentRowSpace;
- updateWidget &&
- updateWidget(WidgetOperations.RESIZE, props.widgetId, {
- leftColumn,
- rightColumn,
- topRow,
- bottomRow,
- });
+ if (!isColliding) {
+ updateWidget &&
+ updateWidget(WidgetOperations.RESIZE, props.widgetId, {
+ leftColumn,
+ rightColumn,
+ topRow,
+ bottomRow,
+ });
+ }
+ setIsColliding(false);
};
return (
{
disableDragging
minWidth={props.parentColumnSpace}
minHeight={props.parentRowSpace}
- style={{ ...props.style }}
+ style={{
+ ...props.style,
+ background: isColliding
+ ? getColorWithOpacity(theme.colors.error, 0.6)
+ : props.style.backgroundColor,
+ }}
onResizeStop={updateSize}
+ onResize={checkForCollision}
onResizeStart={() => {
setIsResizing && setIsResizing(true);
}}
diff --git a/app/client/src/pages/Editor/Canvas.tsx b/app/client/src/pages/Editor/Canvas.tsx
index a5e9350d9a..a16b54aac3 100644
--- a/app/client/src/pages/Editor/Canvas.tsx
+++ b/app/client/src/pages/Editor/Canvas.tsx
@@ -16,6 +16,7 @@ const ArtBoard = styled.div`
height: 100%;
position: relative;
overflow: auto;
+ background: white;
`;
interface CanvasProps {
diff --git a/app/client/src/pages/Editor/index.tsx b/app/client/src/pages/Editor/index.tsx
index 086ad989b5..02591f4716 100644
--- a/app/client/src/pages/Editor/index.tsx
+++ b/app/client/src/pages/Editor/index.tsx
@@ -33,7 +33,6 @@ const CanvasContainer = styled.section`
right: 0;
bottom: 0;
left: 0;
- z-index: 11;
pointer-events: none;
}
`;
diff --git a/app/client/src/utils/WidgetPropsUtils.tsx b/app/client/src/utils/WidgetPropsUtils.tsx
index 3e974980f8..9a84e3d1f6 100644
--- a/app/client/src/utils/WidgetPropsUtils.tsx
+++ b/app/client/src/utils/WidgetPropsUtils.tsx
@@ -66,14 +66,13 @@ const areIntersecting = (r1: Rect, r2: Rect) => {
export const isDropZoneOccupied = (
offset: Rect,
- widget: WidgetProps,
+ widgetId: string,
occupied: OccupiedSpace[] | null,
) => {
if (occupied) {
occupied = occupied.filter(widgetDetails => {
return (
- widgetDetails.id !== widget.widgetId &&
- widgetDetails.parentId !== widget.widgetId
+ widgetDetails.id !== widgetId && widgetDetails.parentId !== widgetId
);
});
for (let i = 0; i < occupied.length; i++) {
@@ -86,6 +85,16 @@ export const isDropZoneOccupied = (
return false;
};
+export const isWidgetOverflowingParentBounds = (
+ parentRowCols: { rows?: number; cols?: number },
+ offset: Rect,
+) => {
+ return (
+ (parentRowCols.cols || GridDefaults.DEFAULT_GRID_COLUMNS) < offset.right ||
+ (parentRowCols.rows || GridDefaults.DEFAULT_GRID_ROWS) < offset.bottom
+ );
+};
+
export const noCollision = (
clientOffset: XYCoord,
colWidth: number,
@@ -93,6 +102,8 @@ export const noCollision = (
widget: WidgetProps & Partial,
dropTargetOffset: XYCoord,
occupiedSpaces: OccupiedSpace[] | null,
+ rows?: number,
+ cols?: number,
): boolean => {
if (clientOffset && dropTargetOffset && widget) {
const [left, top] = getDropZoneOffsets(
@@ -113,7 +124,10 @@ export const noCollision = (
top,
bottom: top + widgetHeight,
};
- return !isDropZoneOccupied(currentOffset, widget, occupiedSpaces);
+ return (
+ !isDropZoneOccupied(currentOffset, widget.widgetId, occupiedSpaces) &&
+ !isWidgetOverflowingParentBounds({ rows, cols }, currentOffset)
+ );
}
return false;
};
diff --git a/app/client/src/widgets/ContainerWidget.tsx b/app/client/src/widgets/ContainerWidget.tsx
index ec1ded6bb3..c4e0af788b 100644
--- a/app/client/src/widgets/ContainerWidget.tsx
+++ b/app/client/src/widgets/ContainerWidget.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { createContext, Context } from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import ContainerComponent from "../editorComponents/ContainerComponent";
import { ContainerOrientation, WidgetType } from "../constants/WidgetConstants";
@@ -11,6 +11,9 @@ import DraggableComponent from "../editorComponents/DraggableComponent";
import ResizableComponent from "../editorComponents/ResizableComponent";
const { DEFAULT_GRID_COLUMNS, DEFAULT_GRID_ROW_HEIGHT } = GridDefaults;
+export const OccupiedSpaceContext: Context<
+ OccupiedSpace[] | any
+> = createContext(null);
class ContainerWidget extends BaseWidget<
ContainerWidgetProps,
@@ -78,6 +81,7 @@ class ContainerWidget extends BaseWidget<
}))
: null;
}
+
getCanvasView() {
const style = this.getPositionStyle();
const occupiedSpaces = this.getOccupiedSpaces();
@@ -85,7 +89,6 @@ class ContainerWidget extends BaseWidget<
);
- return this.props.parentId ? renderDraggableComponent : renderComponent;
+ return (
+
+ {this.props.parentId ? renderDraggableComponent : renderComponent}
+
+ );
}
getWidgetType(): WidgetType {