diff --git a/package-lock.json b/package-lock.json index 8414161..b844ac5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@ant-design/icons": "^5.4.0", "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/utilities": "^3.2.2", "@reduxjs/toolkit": "^2.2.7", "@testing-library/jest-dom": "^5.17.0", @@ -2499,6 +2500,19 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@dnd-kit/modifiers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-7.0.0.tgz", + "integrity": "sha512-BG/ETy3eBjFap7+zIti53f0PCLGDzNXyTmn6fSdrudORf+OH04MxrW4p5+mPu4mgMk9kM41iYONjc3DOUWTcfg==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.1.0", + "react": ">=16.8.0" + } + }, "node_modules/@dnd-kit/utilities": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", diff --git a/package.json b/package.json index 14d702e..c9bce63 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "dependencies": { "@ant-design/icons": "^5.4.0", "@dnd-kit/core": "^6.1.0", + "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/utilities": "^3.2.2", "@reduxjs/toolkit": "^2.2.7", "@testing-library/jest-dom": "^5.17.0", diff --git a/src/App.js b/src/App.js index 6f32af5..6882d15 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useRef, useState } from 'react' import { LayoutFilled, ProductFilled, CloudUploadOutlined } from "@ant-design/icons" @@ -7,14 +7,23 @@ import WidgetsContainer from './sidebar/widgetsContainer' import UploadsContainer from './sidebar/uploadsContainer' import Canvas from './canvas/canvas' import Header from './components/header' -import { DndContext, useSensors, useSensor, PointerSensor, closestCorners, DragOverlay } from '@dnd-kit/core' +import { DndContext, useSensors, useSensor, PointerSensor, closestCorners, DragOverlay, rectIntersection } from '@dnd-kit/core' import { DraggableWidgetCard } from './components/cards' +import Widget from './canvas/widgets/base' +import { snapCenterToCursor } from '@dnd-kit/modifiers' function App() { + /** + * @type {Canvas | null>} + */ + const canvasRef = useRef() + const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files) + const [dropAnimation, setDropAnimation] = useState(null) + const [sidebarWidgets, setSidebarWidgets] = useState([]) const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas @@ -44,7 +53,7 @@ function App() { ] const handleDragStart = (event) => { - console.log("Dragging", event.active) + console.log("Drag start: ", event) const draggedItem = sidebarWidgets.find((item) => item.name === event.active.id) setActiveSidebarWidget(draggedItem) } @@ -54,37 +63,87 @@ function App() { const handleDragEnd = (event) => { // add items to canvas from sidebar - const widgetItem = event.active.data.current?.title - if (event.over?.id !== "cart-droppable" || !widgetItem) return - // const temp = [...widgets] - // temp.push(widgetItem) - // setCanvasWidgets(temp) + const {active, over, delta, activatorEvent} = event + + const widgetItem = active.data.current?.title + const activeItemElement = document.getElementById(`${active.id}`) + + // console.log("ended: ", activatorEvent, "delta", delta, "drag ended: ", event, "active: ", active, "over: ", over) + console.log("over: ", active, over, activeItemElement) + if (over?.id !== "canvas-droppable" || !widgetItem) { + setDropAnimation({ duration: 250, easing: "ease" }) + return + } + setDropAnimation(null) + + // Calculate the dragged item's bounding rectangle + // const itemRect = activeItemElement.getBoundingClientRect(); + // const itemCenterX = itemRect.left + itemRect.width / 2 + // const itemCenterY = itemRect.top + itemRect.height / 2 + + // // Calculate cursor position relative to the canvas + // const cursorX = activatorEvent.clientX + // const cursorY = activatorEvent.clientY + + // // Calculate the offset from the center of the item to the cursor + // const offsetX = cursorX - itemCenterX + // const offsetY = cursorY - itemCenterY + + const canvasContainerRect = canvasRef.current.getCanvasContainerBoundingRect() + const canvasTranslate = canvasRef.current.getCanvasTranslation() + const zoom = canvasRef.current.getZoom() + + let finalPosition = { + x: (delta.x - canvasContainerRect.x - canvasTranslate.x) / zoom, + y: (delta.y - canvasContainerRect.y - canvasTranslate.y) / zoom, + } + + // find the center of the active widget then set the final position + + // finalPosition = { + // finalPosition + // } + + // console.log("drop position: ", "delta: ", delta, "activator", canvasContainerRect, canvasTranslate,) + + canvasRef.current.addWidget(Widget, ({id, widgetRef}) => { + widgetRef.current.setPos(finalPosition.x, finalPosition.y) + // widgetRef.current.setPos(10, 10) + }) + setActiveSidebarWidget(null) } + const handleWidgetAddedToCanvas = (widgets) => { + console.log("canvas ref: ", canvasRef) + setCanvasWidgets(widgets) + } return (
-
- +
{/* dragOverlay (dnd-kit) helps move items from one container to another */} - + {activeSidebarWidget ? ( ): null} diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index 7671500..207232b 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -2,7 +2,7 @@ import React from "react" import {DndContext} from '@dnd-kit/core' -import { FullscreenOutlined, ReloadOutlined } from "@ant-design/icons" +import { CloseOutlined, FullscreenOutlined, ReloadOutlined } from "@ant-design/icons" import { Button, Tooltip } from "antd" import Droppable from "../components/utils/droppable" @@ -22,6 +22,8 @@ class Canvas extends React.Component { constructor(props) { super(props) + + const { canvasWidgets, onWidgetListUpdated } = props this.canvasRef = React.createRef() this.canvasContainerRef = React.createRef() @@ -40,7 +42,7 @@ class Canvas extends React.Component { } this.state = { - widgets: [], // don't store the widget directly here, instead store it in widgetRef, else the changes in the widget will re-render the whole canvas + widgets: [], // don't store the refs directly here, instead store it in widgetRef, store the widget type here zoom: 1, isPanning: false, currentTranslate: { x: 0, y: 0 }, @@ -49,6 +51,8 @@ class Canvas extends React.Component { this.selectedWidgets = [] + this._onWidgetListUpdated = onWidgetListUpdated // a function callback when the widget is added to the canvas + this.resetTransforms = this.resetTransforms.bind(this) this.renderWidget = this.renderWidget.bind(this) @@ -62,7 +66,7 @@ class Canvas extends React.Component { this.getCanvasObjectsBoundingBox = this.getCanvasObjectsBoundingBox.bind(this) this.fitCanvasToBoundingBox = this.fitCanvasToBoundingBox.bind(this) - + this.getCanvasBoundingRect = this.getCanvasContainerBoundingRect.bind(this) this.clearSelections = this.clearSelections.bind(this) this.clearCanvas = this.clearCanvas.bind(this) @@ -222,6 +226,30 @@ class Canvas extends React.Component { this.setZoom(zoom, {x: event.offsetX, y: event.offsetY}) } + getCanvasContainerBoundingRect(){ + return this.canvasContainerRef.current.getBoundingClientRect() + } + + getCanvasBoundingRect(){ + return this.canvasRef.current.getBoundingClientRect() + } + + getCanvasTranslation(){ + return this.state.currentTranslate + } + + /** + * Given a position relative to canvas container, + * returns the position relative to the canvas + */ + getRelativePositionToCanvas(x, y){ + + const canvasRect = this.canvasRef.current.getBoundingClientRect() + let zoom = this.state.zoom + + return {x: (canvasRect.left - x ), y: (canvasRect.top - y)} + } + /** * fits the canvas size to fit the widgets bounding box */ @@ -272,6 +300,10 @@ class Canvas extends React.Component { // this.canvasRef.current.style.height = `${100/zoom}%` } + + getZoom(){ + return this.state.zoom + } resetTransforms() { this.setState({ @@ -315,7 +347,7 @@ class Canvas extends React.Component { * * @param {Widget} widgetComponentType - don't pass instead pass Widget object */ - addWidget(widgetComponentType){ + addWidget(widgetComponentType, callback){ const widgetRef = React.createRef() const id = `${widgetComponentType.widgetType}_${UID()}` @@ -323,10 +355,22 @@ class Canvas extends React.Component { // Store the ref in the instance variable this.widgetRefs[id] = widgetRef // console.log("widget ref: ", this.widgetRefs) + + const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType }] // don't add the widget refs in the state // Update the state to include the new widget's type and ID - this.setState((prevState) => ({ - widgets: [...prevState.widgets, { id, type: widgetComponentType }] - })) + this.setState({ + widgets: widgets + }, () => { + if (callback) + callback({id, widgetRef}) + + if (this._onWidgetListUpdated) + this._onWidgetListUpdated(widgets) + }) + + + + return {id, widgetRef} } /** @@ -335,14 +379,17 @@ class Canvas extends React.Component { clearCanvas(){ for (let [key, value] of Object.entries(this.widgetRefs)){ - console.log("removed: ", key, value) + console.log("removed: ", value, value.current?.getElement()) + value.current?.remove() } this.widgetRefs = {} this.setState(() => ({ widgets: [] - })) + }), () => { + + }) } removeWidget(widgetId){ @@ -356,10 +403,11 @@ class Canvas extends React.Component { } renderWidget(widget){ - const { id, type: ComponentType } = widget + const { id, widgetType: ComponentType } = widget // console.log("widet: ", this.widgetRefs, id) - return + return } render() { @@ -372,6 +420,9 @@ class Canvas extends React.Component {
@@ -382,7 +433,7 @@ class Canvas extends React.Component { > {/* Canvas */}
{ diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 8e0e9f3..40c6efd 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -20,7 +20,7 @@ class Widget extends React.Component{ super(props) const {id, widgetName, canvasRef} = props - console.log("Id: ", id) + // console.log("Id: ", id) // this id has to be unique inside the canvas, it will be set automatically and should never be changed this.__id = id this._zIndex = 0 @@ -31,6 +31,9 @@ class Widget extends React.Component{ this._disableResize = false this._disableSelection = false + this._parent = "" // id of the parent widget, default empty string + this._children = [] // id's of all the child widgets + this.minSize = {width: 50, height: 50} // disable resizing below this number this.maxSize = {width: 500, height: 500} // disable resizing above this number @@ -133,6 +136,13 @@ class Widget extends React.Component{ return toSnakeCase(this.state.widgetName) } + /** + * removes the element/widget + */ + remove(){ + this.elementRef.current.remove() + } + mousePress(event){ // event.preventDefault() if (!this._disableSelection){ @@ -176,6 +186,8 @@ class Widget extends React.Component{ this.setState({ pos: {x: x, y: y} }) + + console.log("POs: ", x, y) } getPos(){ @@ -314,6 +326,7 @@ class Widget extends React.Component{ left: `${this.state.pos.x}px`, width: `${this.state.size.width}px`, height: `${this.state.size.height}px`, + position: "absolute" // don't change this } let selectionStyle = { @@ -333,7 +346,7 @@ class Widget extends React.Component{ return ( -
diff --git a/src/components/utils/droppable.js b/src/components/utils/droppable.js index 00ee790..a732888 100644 --- a/src/components/utils/droppable.js +++ b/src/components/utils/droppable.js @@ -6,7 +6,7 @@ function Droppable(props) { id: props.id, }) const style = { - color: isOver ? 'green' : undefined, + backgroundColor: isOver ? 'green' : '', } diff --git a/src/sidebar/widgetsContainer.js b/src/sidebar/widgetsContainer.js index 09b5e11..3f28f02 100644 --- a/src/sidebar/widgetsContainer.js +++ b/src/sidebar/widgetsContainer.js @@ -74,7 +74,7 @@ function WidgetsContainer({onWidgetsUpdate}){ } return ( -
+