Fix editor stacking context
This commit is contained in:
parent
81272a0839
commit
74ee90d816
|
|
@ -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`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
<title>Appsmith | Editor</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
<DragHandle className="control" ref={drag}>
|
||||
{moveControlIcon}
|
||||
</DragHandle>
|
||||
<DeleteControl className="control" onClick={deleteWidget}>
|
||||
{deleteControlIcon}
|
||||
</DeleteControl>
|
||||
{props.children}
|
||||
</DraggableWrapper>
|
||||
</ResizingContext.Provider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div
|
||||
ref={drop}
|
||||
className="dropTarget"
|
||||
style={{
|
||||
position: "relative",
|
||||
left: 0,
|
||||
|
|
@ -99,7 +100,6 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
|
|||
? props.style.componentWidth + (props.style.widthUnit || "px")
|
||||
: "100%",
|
||||
top: 0,
|
||||
background: "white",
|
||||
}}
|
||||
>
|
||||
<DragLayerComponent
|
||||
|
|
@ -109,8 +109,10 @@ export const DropTargetComponent = (props: DropTargetComponentProps) => {
|
|||
visible={isOver}
|
||||
isOver={isExactlyOver}
|
||||
dropTargetOffset={dropTargetOffset}
|
||||
occupiedSpaces={props.occupiedSpaces}
|
||||
occupiedSpaces={occupiedSpaces}
|
||||
onBoundsUpdate={handleBoundsUpdate}
|
||||
parentRows={props.snapRows}
|
||||
parentCols={props.snapColumns}
|
||||
/>
|
||||
|
||||
{props.children}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ export const DropTargetMaskWrapper = styled.div<DropTargetMaskProps>`
|
|||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: white;
|
||||
${props =>
|
||||
props.showGrid &&
|
||||
css`
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<ResizableContainer
|
||||
|
|
@ -109,8 +150,14 @@ export const ResizableComponent = (props: ResizableComponentProps) => {
|
|||
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);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const ArtBoard = styled.div`
|
|||
height: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
`;
|
||||
|
||||
interface CanvasProps {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ const CanvasContainer = styled.section`
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 11;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -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<WidgetConfigProps>,
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<WidgetProps>,
|
||||
|
|
@ -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<
|
|||
<DropTargetComponent
|
||||
{...this.props}
|
||||
{...this.state}
|
||||
occupiedSpaces={occupiedSpaces}
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
|
|
@ -106,7 +109,11 @@ class ContainerWidget extends BaseWidget<
|
|||
</DraggableComponent>
|
||||
);
|
||||
|
||||
return this.props.parentId ? renderDraggableComponent : renderComponent;
|
||||
return (
|
||||
<OccupiedSpaceContext.Provider value={occupiedSpaces}>
|
||||
{this.props.parentId ? renderDraggableComponent : renderComponent}
|
||||
</OccupiedSpaceContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
getWidgetType(): WidgetType {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user