diff --git a/src/App.js b/src/App.js index 584a5c2..8b4b232 100644 --- a/src/App.js +++ b/src/App.js @@ -19,6 +19,9 @@ function App() { * @type {Canvas | null>} */ const canvasRef = useRef() + const widgetOverlayRef = useRef() + const [initialPosition, setInitialPosition] = useState({ x: 0, y: 0 }) + const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files) @@ -56,9 +59,23 @@ function App() { console.log("Drag start: ", event) const draggedItem = sidebarWidgets.find((item) => item.name === event.active.id) setActiveSidebarWidget(draggedItem) + + const activeItemElement = widgetOverlayRef.current + + if (activeItemElement) { + const rect = activeItemElement.getBoundingClientRect() + + // Store the initial position of the dragged element + setInitialPosition({ + x: rect.left, + y: rect.top, + }) + } } const handleDragMove = (event) => { + + // console.log("drag move: ", event) } const handleDragEnd = (event) => { @@ -67,10 +84,11 @@ function App() { const {active, over, delta, activatorEvent} = event const widgetItem = active.data.current?.title - const activeItemElement = document.getElementById(`${active.id}`) + const activeItemElement = widgetOverlayRef.current - // console.log("ended: ", activatorEvent, "delta", delta, "drag ended: ", event, "active: ", active, "over: ", over) - console.log("over: ", active, over, activeItemElement) + + console.log("ended: ", activatorEvent.clientX, activatorEvent.clientY) + // console.log("over: ", active, over, activeItemElement) if (over?.id !== "canvas-droppable" || !widgetItem) { setDropAnimation({ duration: 250, easing: "ease" }) return @@ -79,34 +97,40 @@ function App() { // FIXME: drop offset is not correct // 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 + const itemRect = activeItemElement.getBoundingClientRect(); + const itemCenterX = itemRect.left + itemRect.width / 2 + const itemCenterY = itemRect.top + itemRect.height / 2 + + console.log("widget overlay: ", delta, itemRect) + - // // 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 + // Get widget dimensions (assuming you have a way to get these) + const widgetWidth = activeItemElement.offsetWidth; // Adjust this based on how you get widget size + const widgetHeight = activeItemElement.offsetHeight; // Adjust this based on how you get widget size + 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, + // } + let finalPosition = { - x: (delta.x - canvasContainerRect.x - canvasTranslate.x) / zoom, - y: (delta.y - canvasContainerRect.y - canvasTranslate.y) / zoom, + x: (initialPosition.x + delta.x - canvasContainerRect.x - canvasTranslate.x) / zoom - (widgetWidth / 2), + y: (initialPosition.y + delta.y - canvasContainerRect.y - canvasTranslate.y) / zoom - (widgetHeight / 2), } + // find the center of the active widget then set the final position // finalPosition = { // finalPosition // } - // console.log("drop position: ", "delta: ", delta, "activator", canvasContainerRect, canvasTranslate,) + console.log("drop position: ", "delta: ", delta, "activator", finalPosition, canvasTranslate,) canvasRef.current.addWidget(Widget, ({id, widgetRef}) => { widgetRef.current.setPos(finalPosition.x, finalPosition.y) @@ -126,7 +150,7 @@ function App() {
- {/* 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 3537dee..76cb632 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -2,13 +2,14 @@ import React from "react" import {DndContext} from '@dnd-kit/core' -import { CloseOutlined, FullscreenOutlined, ReloadOutlined } from "@ant-design/icons" +import { CloseOutlined, DeleteOutlined, EditOutlined, FullscreenOutlined, ReloadOutlined } from "@ant-design/icons" import { Button, Tooltip, Dropdown } from "antd" import Droppable from "../components/utils/droppable" import Widget from "./widgets/base" import Cursor from "./constants/cursor" import { UID } from "../utils/uid" +import { removeDuplicateObjects } from "../utils/common" const CanvasModes = { @@ -71,6 +72,8 @@ class Canvas extends React.Component { this.fitCanvasToBoundingBox = this.fitCanvasToBoundingBox.bind(this) this.getCanvasBoundingRect = this.getCanvasContainerBoundingRect.bind(this) + this.deleteSelectedWidgets = this.deleteSelectedWidgets.bind(this) + this.removeWidget = this.removeWidget.bind(this) this.clearSelections = this.clearSelections.bind(this) this.clearCanvas = this.clearCanvas.bind(this) @@ -159,19 +162,23 @@ class Canvas extends React.Component { if (selectedWidget){ // if the widget is selected don't pan, instead move the widget if (!selectedWidget._disableSelection){ - selectedWidget.select() - if (this.selectedWidgets.length >= 1){ // allow only one selection for now - this.clearSelections() + const selectedLength = this.selectedWidgets.length + + if (selectedLength === 0 || (selectedLength === 1 && selectedWidget.__id !== this.selectedWidgets[0].__id)){ + this.selectedWidgets[0]?.deSelect() // deselect the previous widget before adding the new one + this.selectedWidgets[0]?.setZIndex(0) + + selectedWidget.setZIndex(1000) + selectedWidget.select() + this.selectedWidgets[0] = selectedWidget } - - this.selectedWidgets.push(selectedWidget) this.currentMode = CanvasModes.MOVE_WIDGET } this.currentMode = CanvasModes.PAN - }else if (this.state?.widgets?.length > 0){ + }else if (!selectedWidget){ // get the canvas ready to pan, if there are widgets on the canvas this.clearSelections() this.currentMode = CanvasModes.PAN @@ -187,24 +194,36 @@ class Canvas extends React.Component { // }) }else if (event.button === 2){ //right click - if (selectedWidget) - this.setState({ - contextMenuItems: [{ - key: "delete", - label: "Delete" - }] - }) + + if (this.selectedWidgets.length > 0 && this.selectedWidgets[0].__id !== selectedWidget.__id){ + this.clearSelections() + } - // this.setState({ - // showContextMenu: true - // }) - console.log("button: ", selectedWidget) + if (selectedWidget) + this.selectedWidgets[0] = selectedWidget + console.log("renaming: ", selectedWidget) + this.setState({ + contextMenuItems: [ + { + key: "rename", + label: (
selectedWidget.openRenaming()}> Rename
), + icons: , + }, + { + key: "delete", + label: (
this.deleteSelectedWidgets([selectedWidget])}> Delete
), + icons: , + danger: true + } + ] + }) } } mouseMoveEvent(event){ + // console.log("mode: ", this.currentMode, this.getActiveObjects()) if (this.mousePressed && [CanvasModes.PAN, CanvasModes.MOVE_WIDGET].includes(this.currentMode)) { const deltaX = event.clientX - this.mousePos.x @@ -401,16 +420,38 @@ class Canvas extends React.Component { return {id, widgetRef} } + deleteSelectedWidgets(widgets){ + + + let activeWidgets = removeDuplicateObjects([...widgets, ...this.selectedWidgets], "__id") + console.log("active widget: ", widgets, activeWidgets) + const widgetIds = activeWidgets.map(widget => widget.__id) + + for (let widgetId of widgetIds){ + console.log("removed: ", widgetId) + + // this.widgetRefs[widgetId]?.current.remove() + delete this.widgetRefs[widgetId] + + this.setState((prevState) => ({ + widgets: prevState.widgets.filter(widget => widget.id !== widgetId) + })) + // value.current?.remove() + } + + } + /** * removes all the widgets from the canvas */ clearCanvas(){ - for (let [key, value] of Object.entries(this.widgetRefs)){ - console.log("removed: ", value, value.current?.getElement()) + // NOTE: Don't remove from it using remove() function since, it already removed from the DOM tree when its removed from widgets + // for (let [key, value] of Object.entries(this.widgetRefs)){ + // console.log("removed: ", value, value.current?.getElement()) - value.current?.remove() - } + // value.current?.remove() + // } this.widgetRefs = {} this.setState(() => ({ @@ -422,7 +463,7 @@ class Canvas extends React.Component { removeWidget(widgetId){ - this.widgetRefs[widgetId]?.current.remove() + // this.widgetRefs[widgetId]?.current.remove() delete this.widgetRefs[widgetId] this.setState((prevState) => ({ @@ -454,7 +495,7 @@ class Canvas extends React.Component {
- +
0 ? value : prev.widgetName })) } - + // console.log("selected: ", this.state.selected) return (
diff --git a/src/components/cards.js b/src/components/cards.js index 6595281..8ee0b01 100644 --- a/src/components/cards.js +++ b/src/components/cards.js @@ -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, innerRef}){ const urlIcon = useMemo(() => { if (url){ @@ -29,7 +29,7 @@ export function DraggableWidgetCard({ name, img, url}){ return ( -
{name} diff --git a/src/components/editableDiv.js b/src/components/editableDiv.js index 3230510..da88e6b 100644 --- a/src/components/editableDiv.js +++ b/src/components/editableDiv.js @@ -1,8 +1,8 @@ import React, { useState, useRef, useEffect } from 'react' -function EditableDiv({value, onChange, maxLength=Infinity, className='', inputClassName}) { - const [isEditable, setIsEditable] = useState(false) +function EditableDiv({value, onChange, openEdit=false, maxLength=Infinity, className='', inputClassName}) { + const [isEditable, setIsEditable] = useState(openEdit) const [content, setContent] = useState(value) const inputRef = useRef(null) @@ -12,6 +12,17 @@ function EditableDiv({value, onChange, maxLength=Infinity, className='', inputCl }, [value]) + useEffect(() => { + setIsEditable(openEdit) + + if (openEdit){ + setTimeout(() => { + inputRef.current.focus() + }, 15) + } + + }, [openEdit]) + const handleInput = (event) => { console.log("Event key: ", event.key) diff --git a/src/components/utils/draggable.js b/src/components/utils/draggable.js index 1639494..7cca531 100644 --- a/src/components/utils/draggable.js +++ b/src/components/utils/draggable.js @@ -14,10 +14,10 @@ function Draggable(props) { return ( ) diff --git a/src/utils/common.js b/src/utils/common.js new file mode 100644 index 0000000..b9b302d --- /dev/null +++ b/src/utils/common.js @@ -0,0 +1,14 @@ +// contains commonly used functions to manipulate objects, array etc. + + +export function removeDuplicateObjects(array, key) { + const seen = new Set() + + return array.filter(item => { + if (!seen.has(item[key])) { + seen.add(item[key]) + return true + } + return false + }) +} \ No newline at end of file