working on DND kit

This commit is contained in:
paul
2024-09-11 19:06:04 +05:30
parent 7947bd599f
commit 5ee95ae2ee
9 changed files with 552 additions and 151 deletions

1
package-lock.json generated
View File

@@ -10,6 +10,7 @@
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",

View File

@@ -5,6 +5,7 @@
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/utilities": "^3.2.2",
"@reduxjs/toolkit": "^2.2.7",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",

View File

@@ -7,17 +7,28 @@ 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 { DraggableWidgetCard } from './components/cards'
function App() {
const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
const [sidebarWidgets, setSidebarWidgets] = useState([])
const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas
const [activeSidebarWidget, setActiveSidebarWidget] = useState(null) // helps with the dnd overlay
const tabs = [
const sensors = useSensors(
useSensor(PointerSensor)
)
const sidebarTabs = [
{
name: "Widgets",
icon: <LayoutFilled />,
content: <WidgetsContainer />
content: <WidgetsContainer onWidgetsUpdate={(widgets) => setSidebarWidgets(widgets)}/>
},
{
name: "Extensions",
@@ -32,15 +43,56 @@ function App() {
}
]
const handleDragStart = (event) => {
console.log("Dragging", event.active)
const draggedItem = sidebarWidgets.find((item) => item.name === event.active.id)
setActiveSidebarWidget(draggedItem)
}
const handleDragMove = (event) => {
}
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)
setActiveSidebarWidget(null)
}
return (
<div className="tw-w-full tw-h-[100vh] tw-flex tw-flex-col tw-bg-primaryBg">
<Header className="tw-h-[6vh]"/>
<div className="tw-w-full tw-h-[94vh] tw-flex">
<Sidebar tabs={tabs}/>
<Canvas />
</div>
<DndContext sensors={sensors} collisionDetection={closestCorners}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
>
<div className="tw-w-full tw-h-[94vh] tw-flex">
<Sidebar tabs={sidebarTabs}/>
<Canvas widgets={canvasWidgets}/>
</div>
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
<DragOverlay>
{activeSidebarWidget ? (
<DraggableWidgetCard name={activeSidebarWidget.name}
img={activeSidebarWidget.img}
url={activeSidebarWidget.url}
/>
):
null}
</DragOverlay>
</DndContext>
</div>
);
)
}
export default App;

View File

@@ -1,8 +1,11 @@
import React from "react"
import * as fabric from 'fabric'
import {DndContext} from '@dnd-kit/core'
import { FullscreenOutlined, ReloadOutlined } from "@ant-design/icons"
import { Button, Tooltip } from "antd"
import Droppable from "../components/utils/droppable"
import Widget from "./widgets/base"
import Cursor from "./constants/cursor"
import { UID } from "../utils/uid"
@@ -60,10 +63,6 @@ class Canvas extends React.Component {
this.getCanvasObjectsBoundingBox = this.getCanvasObjectsBoundingBox.bind(this)
this.fitCanvasToBoundingBox = this.fitCanvasToBoundingBox.bind(this)
this.updateWidgetPosition = this.updateWidgetPosition.bind(this)
this.checkAndExpandCanvas = this.checkAndExpandCanvas.bind(this)
this.expandCanvas = this.expandCanvas.bind(this)
this.clearSelections = this.clearSelections.bind(this)
this.clearCanvas = this.clearCanvas.bind(this)
@@ -195,7 +194,7 @@ class Canvas extends React.Component {
const newPosY = y + (deltaY/this.state.zoom) // account for the zoom, since the widget is relative to canvas
widget.setPos(newPosX, newPosY)
this.checkAndExpandCanvas(newPosX, newPosY, widget.getSize().width, widget.getSize().height)
// this.checkAndExpandCanvas(newPosX, newPosY, widget)
})
// this.fitCanvasToBoundingBox(10)
@@ -219,123 +218,10 @@ class Canvas extends React.Component {
wheelZoom(event){
let delta = event.deltaY
let zoom = this.state.zoom * 0.999 ** delta
this.setZoom(zoom, {x: event.offsetX, y: event.offsetY})
}
checkAndExpandCanvas(widgetX, widgetY, widgetWidth, widgetHeight) {
const canvasWidth = this.canvasRef.current.offsetWidth
const canvasHeight = this.canvasRef.current.offsetHeight
const canvasRect = this.canvasRef.current.getBoundingClientRect()
// Get the zoom level
const zoom = this.state.zoom
// Calculate effective canvas boundaries considering zoom
const effectiveCanvasRight = canvasWidth
const effectiveCanvasBottom = canvasHeight
// Calculate widget boundaries
const widgetRight = widgetX + widgetWidth
const widgetBottom = widgetY + widgetHeight
// Determine if expansion is needed
const expandRight = widgetRight > effectiveCanvasRight
const expandDown = widgetBottom > effectiveCanvasBottom
const expandLeft = widgetX < canvasRect.left * this.state.zoom
const expandUp = widgetY < canvasRect.top
if (expandRight || expandLeft || expandDown || expandUp) {
this.expandCanvas(expandRight, expandLeft, expandDown, expandUp, widgetX, widgetY, widgetWidth, widgetHeight)
}
}
// Expand the canvas method
/**
*
* @param {boolean} expandRight
* @param {boolean} expandLeft
* @param {boolean} expandDown
* @param {boolean} expandUp
* @param {number} widgetX
* @param {number} widgetY
* @param {number} widgetRight
* @param {number} widgetBottom
*/
expandCanvas(expandRight, expandLeft, expandDown, expandUp, widgetX, widgetY, widgetWidth, widgetHeight) {
const currentWidth = this.canvasRef.current.offsetWidth
const currentHeight = this.canvasRef.current.offsetHeight
console.log("current: ", expandRight, expandDown, expandLeft, expandUp)
let newWidth = currentWidth
let newHeight = currentHeight
let newTranslateX = this.state.currentTranslate.x
let newTranslateY = this.state.currentTranslate.y
if (expandRight) {
// const requiredWidth = widgetRight - newTranslateX // Add padding
// newWidth = Math.max(requiredWidth, currentWidth)
newWidth = currentWidth + 50
}
if (expandLeft) {
// const leftOffset = widgetX + newTranslateX // Position of the widget relative to the left edge
// const requiredLeftExpansion = -leftOffset + 50 // Add padding
newWidth = currentWidth + widgetWidth
newTranslateX -= widgetWidth // Adjust translation to move the canvas to the left
}
if (expandDown) {
newHeight = currentHeight + 50
// const requiredHeight = widgetBottom - newTranslateY // Add padding
// newHeight = Math.max(requiredHeight, currentHeight)
}
if (expandUp) {
newHeight = currentHeight + widgetHeight
newTranslateY -= widgetHeight
// const topOffset = widgetY + newTranslateY // Position of the widget relative to the top edge
// const requiredTopExpansion = -topOffset + 50 // Add padding
// newHeight = currentHeight + requiredTopExpansion
// newTranslateY -= requiredTopExpansion // Adjust translation to move the canvas upwards
}
// Apply new dimensions and translation
this.canvasRef.current.style.width = `${newWidth}px`
this.canvasRef.current.style.height = `${newHeight}px`
console.log("translate: ", this.canvasRef.current.offsetWidth, )
// Now, to keep the widget in the same relative position:
const updatedWidgetX = widgetX - newTranslateX / this.state.zoom;
const updatedWidgetY = widgetY - newTranslateY / this.state.zoom;
this.setState({
currentTranslate: {
x: newTranslateX,
y: newTranslateY
}
}, () => {
this.applyTransform()
this.updateWidgetPosition(updatedWidgetX, updatedWidgetY, widgetWidth, widgetHeight)
})
}
// TODO: FIX this, to ensure that the widget position remains the same
// Function to update the widget's position based on new updated canvas coordinates, use it after expandCanvas
updateWidgetPosition(widgetX, widgetY, widgetWidth, widgetHeight) {
const widgetElement = this.selectedWidgets[0].current; // Assuming the widget is referenced via `widgetRef`
console.log("widget element: ", this.selectedWidgets[0].current)
widgetElement.style.left = `${widgetX}px`;
widgetElement.style.top = `${widgetY}px`;
widgetElement.style.width = `${widgetWidth}px`;
widgetElement.style.height = `${widgetHeight}px`;
}
/**
* fits the canvas size to fit the widgets bounding box
*/
@@ -382,6 +268,9 @@ class Canvas extends React.Component {
}
}, this.applyTransform)
// this.canvasRef.current.style.width = `${100/zoom}%`
// this.canvasRef.current.style.height = `${100/zoom}%`
}
resetTransforms() {
@@ -485,19 +374,24 @@ class Canvas extends React.Component {
</Tooltip>
</div>
<div className="tw-w-full tw-relative tw-h-full tw-overflow-hidden"
ref={this.canvasContainerRef}
style={{transition: " transform 0.3s ease-in-out"}}
>
<div data-canvas className="tw-w-full tw-absolute tw-top-0 tw-h-full tw-bg-green-300"
ref={this.canvasRef}>
<div className="tw-relative tw-w-full tw-h-full">
{
this.state.widgets.map(this.renderWidget)
}
<Droppable id="canvas-droppable" className="tw-w-full tw-h-full">
{/* Canvas container */}
<div className="tw-w-full tw-h-full tw-flex tw-relative tw-bg-black tw-overflow-hidden"
ref={this.canvasContainerRef}
style={{transition: " transform 0.3s ease-in-out"}}
>
{/* 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"
ref={this.canvasRef}>
<div className="tw-relative tw-w-full tw-h-full">
{
this.state.widgets.map(this.renderWidget)
}
</div>
</div>
</div>
</div>
</div>
</Droppable>
</div>
)
}

View File

@@ -6,7 +6,7 @@ import { FileImageOutlined, GithubOutlined, GitlabOutlined, LinkOutlined,
FileTextOutlined} from "@ant-design/icons"
export function DraggableWidgetCard({name, img, url}){
export function DraggableWidgetCard({ name, img, url}){
const urlIcon = useMemo(() => {
if (url){
@@ -28,8 +28,8 @@ export function DraggableWidgetCard({name, img, url}){
return (
<Draggable className="tw-cursor-pointer">
<div className="tw-w-full tw-h-[240px] tw-flex tw-flex-col tw-rounded-md tw-overflow-hidden
<Draggable className="tw-cursor-pointer" id={name}>
<div className="tw-w-full tw-select-none tw-h-[240px] tw-flex tw-flex-col tw-rounded-md tw-overflow-hidden
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px] tw-border-[#888] ">
<div className="tw-h-[200px] tw-w-full tw-overflow-hidden">
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />

View File

@@ -1,18 +1,23 @@
import React from "react"
import {useDraggable} from "@dnd-kit/core"
import { CSS } from "@dnd-kit/utilities"
function Draggable(props) {
const {attributes, listeners, setNodeRef, transform} = useDraggable({
id: 'draggable',
id: props.id,
data: {title: props.children}
})
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
transform: CSS.Translate.toString(transform),
} : undefined
return (
<button className={`tw-bg-transparent tw-outline-none tw-border-none ${props.className}`} ref={setNodeRef} style={style} {...listeners} {...attributes}>
<button className={`tw-bg-transparent tw-outline-none tw-border-none ${props.className}`}
ref={setNodeRef}
style={style}
{...listeners}
{...attributes}>
{props.children}
</button>
)

View File

@@ -0,0 +1,20 @@
import React from 'react'
import {useDroppable} from '@dnd-kit/core'
function Droppable(props) {
const {isOver, setNodeRef} = useDroppable({
id: props.id,
})
const style = {
color: isOver ? 'green' : undefined,
}
return (
<div ref={setNodeRef} style={style} className={props.className || ''}>
{props.children}
</div>
)
}
export default Droppable

View File

@@ -0,0 +1,415 @@
// This is only for testing purpose, not really meant to be used
import './polyfills'
import {
addPointer,
getDistance,
getMiddle,
removePointer
} from './pointers'
import { destroyPointer, eventNames, onPointer } from './events'
import { getDimensions, setStyle, setTransform, setTransition } from './css'
import isAttached from './isAttached'
import isExcluded from './isExcluded'
import isSVGElement from './isSVGElement'
import shallowClone from './shallowClone'
const defaultOptions = {
animate: false,
canvas: false,
cursor: 'move',
disablePan: false,
disableZoom: false,
disableXAxis: false,
disableYAxis: false,
duration: 200,
easing: 'ease-in-out',
exclude: [],
excludeClass: 'panzoom-exclude',
handleStartEvent: (e) => {
e.preventDefault()
e.stopPropagation()
},
maxScale: 4,
minScale: 0.125,
overflow: 'hidden',
panOnlyWhenZoomed: false,
pinchAndPan: false,
relative: false,
setTransform,
startX: 0,
startY: 0,
startScale: 1,
step: 0.3,
touchAction: 'none'
}
function Panzoom(elem, options = {}) {
if (!elem) {
throw new Error('Panzoom requires an element as an argument')
}
if (elem.nodeType !== 1) {
throw new Error('Panzoom requires an element with a nodeType of 1')
}
if (!isAttached(elem)) {
throw new Error('Panzoom should be called on elements that have been attached to the DOM')
}
options = {
...defaultOptions,
...options
}
const isSVG = isSVGElement(elem)
const parent = elem.parentNode
// Set parent styles
parent.style.overflow = options.overflow // hidden
parent.style.userSelect = 'none'
parent.style.touchAction = options.touchAction
(options.canvas ? parent : elem).style.cursor = options.cursor
// Set element styles
elem.style.userSelect = 'none'
elem.style.touchAction = options.touchAction
setStyle(
elem,
'transformOrigin',
typeof options.origin === 'string' ? options.origin : isSVG ? '0 0' : '50% 50%'
)
function resetStyle() {
parent.style.overflow = ''
parent.style.userSelect = ''
parent.style.touchAction = ''
parent.style.cursor = ''
elem.style.cursor = ''
elem.style.userSelect = ''
elem.style.touchAction = ''
setStyle(elem, 'transformOrigin', '')
}
function setOptions(opts = {}) {
for (const key in opts) {
if (opts.hasOwnProperty(key)) {
options[key] = opts[key]
}
}
// Handle option side-effects
if (opts.hasOwnProperty('cursor') || opts.hasOwnProperty('canvas')) {
parent.style.cursor = elem.style.cursor = ''
(options.canvas ? parent : elem).style.cursor = options.cursor
}
if (opts.hasOwnProperty('overflow')) {
parent.style.overflow = opts.overflow
}
if (opts.hasOwnProperty('touchAction')) {
parent.style.touchAction = opts.touchAction
elem.style.touchAction = opts.touchAction
}
}
let x = 0
let y = 0
let scale = 1
let isPanning = false
zoom(options.startScale, { animate: false, force: true })
// Wait for scale to update
// for accurate dimensions
// to constrain initial values
setTimeout(() => {
pan(options.startX, options.startY, { animate: false, force: true })
})
function trigger(eventName, detail, opts) {
if (opts.silent) {
return
}
const event = new CustomEvent(eventName, { detail })
elem.dispatchEvent(event)
}
function setTransformWithEvent(
eventName,
opts,
originalEvent
) {
const value = { x, y, scale, isSVG, originalEvent }
requestAnimationFrame(() => {
if (typeof opts.animate === 'boolean') {
if (opts.animate) {
setTransition(elem, opts)
} else {
setStyle(elem, 'transition', 'none')
}
}
opts.setTransform(elem, value, opts)
trigger(eventName, value, opts)
trigger('panzoomchange', value, opts)
})
return value
}
function constrainXY(
toX,
toY,
toScale,
panOptions
) {
const opts = { ...options, ...panOptions }
const result = { x, y, opts }
if (!opts.force && (opts.disablePan || (opts.panOnlyWhenZoomed && scale === opts.startScale))) {
return result
}
toX = parseFloat(toX)
toY = parseFloat(toY)
if (!opts.disableXAxis) {
result.x = (opts.relative ? x : 0) + toX
}
if (!opts.disableYAxis) {
result.y = (opts.relative ? y : 0) + toY
}
if (opts.contain) {
const dims = getDimensions(elem)
const realWidth = dims.elem.width / scale
const realHeight = dims.elem.height / scale
const scaledWidth = realWidth * toScale
const scaledHeight = realHeight * toScale
const diffHorizontal = (scaledWidth - realWidth) / 2
const diffVertical = (scaledHeight - realHeight) / 2
if (opts.contain === 'inside') {
const minX = (-dims.elem.margin.left - dims.parent.padding.left + diffHorizontal) / toScale
const maxX =
(dims.parent.width -
scaledWidth -
dims.parent.padding.left -
dims.elem.margin.left -
dims.parent.border.left -
dims.parent.border.right +
diffHorizontal) /
toScale
result.x = Math.max(Math.min(result.x, maxX), minX)
const minY = (-dims.elem.margin.top - dims.parent.padding.top + diffVertical) / toScale
const maxY =
(dims.parent.height -
scaledHeight -
dims.parent.padding.top -
dims.elem.margin.top -
dims.parent.border.top -
dims.parent.border.bottom +
diffVertical) /
toScale
result.y = Math.max(Math.min(result.y, maxY), minY)
} else if (opts.contain === 'outside') {
const minX =
(-(scaledWidth - dims.parent.width) -
dims.parent.padding.left -
dims.parent.border.left -
dims.parent.border.right +
diffHorizontal) /
toScale
const maxX = (diffHorizontal - dims.parent.padding.left) / toScale
result.x = Math.max(Math.min(result.x, maxX), minX)
const minY =
(-(scaledHeight - dims.parent.height) -
dims.parent.padding.top -
dims.parent.border.top -
dims.parent.border.bottom +
diffVertical) /
toScale
const maxY = (diffVertical - dims.parent.padding.top) / toScale
result.y = Math.max(Math.min(result.y, maxY), minY)
}
}
if (opts.roundPixels) {
result.x = Math.round(result.x)
result.y = Math.round(result.y)
}
return result
}
function constrainScale(toScale, zoomOptions) {
const opts = { ...options, ...zoomOptions }
const result = { scale, opts }
if (!opts.force && opts.disableZoom) {
return result
}
let minScale = options.minScale
let maxScale = options.maxScale
if (opts.contain) {
const dims = getDimensions(elem)
const elemWidth = dims.elem.width / scale
const elemHeight = dims.elem.height / scale
if (elemWidth > 1 && elemHeight > 1) {
const parentWidth = dims.parent.width - dims.parent.border.left - dims.parent.border.right
const parentHeight = dims.parent.height - dims.parent.border.top - dims.parent.border.bottom
const elemScaledWidth = parentWidth / elemWidth
const elemScaledHeight = parentHeight / elemHeight
if (options.contain === 'inside') {
maxScale = Math.min(maxScale, elemScaledWidth, elemScaledHeight)
} else if (options.contain === 'outside') {
minScale = Math.max(minScale, elemScaledWidth, elemScaledHeight)
}
}
}
result.scale = Math.min(Math.max(toScale, minScale), maxScale)
return result
}
function pan(
toX,
toY,
panOptions,
originalEvent
) {
const result = constrainXY(toX, toY, scale, panOptions)
// Only try to set if the result is somehow different
if (x !== result.x || y !== result.y) {
x = result.x
y = result.y
return setTransformWithEvent('panzoompan', result.opts, originalEvent)
}
return { x, y, scale, isSVG, originalEvent }
}
function zoom(
toScale,
zoomOptions,
focal
) {
const result = constrainScale(toScale, zoomOptions)
const zoomScale = result.scale
const zoomOpts = result.opts
if (scale !== zoomScale) {
scale = zoomScale
let toX = x
let toY = y
if (focal) {
const focalX = focal.x || 0
const focalY = focal.y || 0
const diffScale = zoomScale / scale
toX = focalX - (focalX - x) * diffScale
toY = focalY - (focalY - y) * diffScale
}
return pan(toX, toY, zoomOpts, zoomOptions.originalEvent)
}
return { x, y, scale, isSVG: isSVGElement(elem), originalEvent: zoomOptions.originalEvent }
}
function zoomIn(zoomOptions) {
zoom(scale + options.step, zoomOptions)
}
function zoomOut(zoomOptions) {
zoom(scale - options.step, zoomOptions)
}
function zoomToPoint(toScale, point, zoomOptions) {
zoom(toScale, zoomOptions, point)
}
function zoomWithWheel(event) {
const delta = Math.sign(event.deltaY) * -0.1
const zoomFactor = Math.pow(1.2, delta)
zoom(scale * zoomFactor, {}, {
x: event.clientX,
y: event.clientY
})
}
function handleDown(event) {
if (isExcluded(event.target, options.exclude, options.excludeClass)) {
return
}
event.preventDefault()
event.stopPropagation()
isPanning = true
const point = { x: event.clientX, y: event.clientY }
addPointer(event)
elem.style.cursor = options.cursor
trigger('panzoomstart', { x, y, scale, originalEvent: event })
function handleMove(e) {
if (!isPanning) {
return
}
e.preventDefault()
e.stopPropagation()
if (options.pinchAndPan && e.pointerType === 'touch') {
if (e.pointerId === e.pointers[0].pointerId) {
e.target.style.cursor = options.cursor
return
}
e.target.style.cursor = ''
const dist = getDistance(e)
const middle = getMiddle(e)
zoom(scale * (dist / e.pointers[0].distance), {
animate: false,
originalEvent: e
}, middle)
} else {
pan(
x + (e.clientX - point.x) / scale,
y + (e.clientY - point.y) / scale,
{ animate: false, force: true },
e
)
}
}
function handleUp(e) {
isPanning = false
removePointer(e)
if (e.pointerType !== 'touch' || e.pointerId !== e.pointers[0].pointerId) {
return
}
elem.style.cursor = ''
trigger('panzoomend', { x, y, scale, originalEvent: e })
}
onPointer(handleMove, handleUp, event)
}
function reset() {
setOptions({
...defaultOptions,
...options
})
zoom(options.startScale, { animate: false, force: true })
pan(options.startX, options.startY, { animate: false, force: true })
}
reset()
return {
zoom,
zoomIn,
zoomOut,
zoomToPoint,
zoomWithWheel,
pan,
reset,
setOptions,
destroy() {
setStyle(elem, 'transition', '')
resetStyle()
destroyPointer()
eventNames.forEach((eventName) => {
elem.removeEventListener(eventName, handleDown)
})
}
}
}
export default Panzoom

View File

@@ -8,7 +8,13 @@ import ButtonWidget from "../assets/widgets/button.png"
import { filterObjectListStartingWith } from "../utils/filter"
function WidgetsContainer(){
/**
*
* @param {function} onWidgetsUpdate - this is a callback that will be called once the sidebar is populated with widgets
* @returns
*/
function WidgetsContainer({onWidgetsUpdate}){
const widgets = useMemo(() => {
return [
@@ -42,8 +48,14 @@ function WidgetsContainer(){
setWidgetData(widgets)
if (onWidgetsUpdate){
onWidgetsUpdate(widgets)
}
}, [widgets])
useEffect(() => {
if (searchValue.length > 0){
@@ -78,14 +90,15 @@ function WidgetsContainer(){
</div>
</div>
<div className="tw-flex tw-flex-col tw-gap-2 tw-h-full tw-p-1">
{
widgetData.map((widget, index) => {
return (
<DraggableWidgetCard key={widget.name}
name={widget.name}
img={widget.img}
url={widget.link}
/>
name={widget.name}
img={widget.img}
url={widget.link}
/>
)
})