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 {
} onClick={this.resetTransforms} />
+
+ } onClick={this.clearCanvas} />
+
@@ -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 (
-