working on dropping the widget from sidebar
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
83
src/App.js
83
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 (
|
||||
<div className="tw-w-full tw-h-[100vh] tw-flex tw-flex-col tw-bg-primaryBg">
|
||||
<Header className="tw-h-[6vh]"/>
|
||||
|
||||
<DndContext sensors={sensors} collisionDetection={closestCorners}
|
||||
<DndContext sensors={sensors}
|
||||
modifiers={[snapCenterToCursor]}
|
||||
collisionDetection={rectIntersection}
|
||||
onDragStart={handleDragStart}
|
||||
onDragMove={handleDragMove}
|
||||
onDragEnd={handleDragEnd}
|
||||
|
||||
>
|
||||
<div className="tw-w-full tw-h-[94vh] tw-flex">
|
||||
<Sidebar tabs={sidebarTabs}/>
|
||||
<Canvas widgets={canvasWidgets}/>
|
||||
<Canvas ref={canvasRef} widgets={canvasWidgets} onWidgetAdded={handleWidgetAddedToCanvas}/>
|
||||
</div>
|
||||
|
||||
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
|
||||
<DragOverlay>
|
||||
<DragOverlay dropAnimation={dropAnimation} >
|
||||
{activeSidebarWidget ? (
|
||||
<DraggableWidgetCard name={activeSidebarWidget.name}
|
||||
img={activeSidebarWidget.img}
|
||||
url={activeSidebarWidget.url}
|
||||
url={activeSidebarWidget.link}
|
||||
/>
|
||||
):
|
||||
null}
|
||||
|
||||
@@ -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 <Widget /> 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 <ComponentType key={id} id={id} ref={this.widgetRefs[id]} canvasRef={this.canvasRef} />
|
||||
return <ComponentType key={id} id={id} ref={this.widgetRefs[id]}
|
||||
canvasRef={this.canvasRef} />
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -372,6 +420,9 @@ class Canvas extends React.Component {
|
||||
<Tooltip title="Reset viewport">
|
||||
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
|
||||
</Tooltip>
|
||||
<Tooltip title="Reset viewport">
|
||||
<Button icon={<CloseOutlined />} onClick={this.clearCanvas} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Droppable id="canvas-droppable" className="tw-w-full tw-h-full">
|
||||
@@ -382,7 +433,7 @@ class Canvas extends React.Component {
|
||||
>
|
||||
{/* Canvas */}
|
||||
<div data-canvas className="tw-w-full tw-h-full tw-absolute tw-top-0 tw-select-none
|
||||
t tw-bg-green-300 tw-overflow-hidden"
|
||||
t tw-bg-green-300"
|
||||
ref={this.canvasRef}>
|
||||
<div className="tw-relative tw-w-full tw-h-full">
|
||||
{
|
||||
|
||||
@@ -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 (
|
||||
|
||||
<div data-id={this.__id} ref={this.elementRef} className="tw-relative tw-w-fit tw-h-fit"
|
||||
<div data-id={this.__id} ref={this.elementRef} className="tw-absolute tw-w-fit tw-h-fit"
|
||||
style={style}
|
||||
>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ function Droppable(props) {
|
||||
id: props.id,
|
||||
})
|
||||
const style = {
|
||||
color: isOver ? 'green' : undefined,
|
||||
backgroundColor: isOver ? 'green' : '',
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ function WidgetsContainer({onWidgetsUpdate}){
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col">
|
||||
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
|
||||
|
||||
<div className="tw-flex tw-gap-2 input tw-place-items-center">
|
||||
<SearchOutlined />
|
||||
|
||||
Reference in New Issue
Block a user