updated distanceLine and fix drag bug

This commit is contained in:
Nalvazhuthi 2025-03-31 18:22:40 +05:30
parent 7c85d2041e
commit 9611ad69cf
7 changed files with 535 additions and 151 deletions

View File

@ -0,0 +1,93 @@
import React from "react";
interface DistanceLinesProps {
obj: {
position: {
top?: number | "auto";
left?: number | "auto";
right?: number | "auto";
bottom?: number | "auto";
};
};
activeEdges: {
vertical: "top" | "bottom";
horizontal: "left" | "right";
} | null;
}
const DistanceLines: React.FC<DistanceLinesProps> = ({ obj, activeEdges }) => {
if (!activeEdges) return null;
return (
<>
{activeEdges.vertical === "top" && typeof obj.position.top === "number" && (
<div
className="distance-line top"
style={{
top: 0,
left:
activeEdges.horizontal === "left"
? `${(obj.position.left as number) + 125}px`
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
height: `${obj.position.top}px`,
}}
>
<span className="distance-label">{obj.position.top}px</span>
</div>
)}
{activeEdges.vertical === "bottom" &&
typeof obj.position.bottom === "number" && (
<div
className="distance-line bottom"
style={{
bottom: 0,
left:
activeEdges.horizontal === "left"
? `${(obj.position.left as number) + 125}px`
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
height: `${obj.position.bottom}px`,
}}
>
<span className="distance-label">{obj.position.bottom}px</span>
</div>
)}
{activeEdges.horizontal === "left" &&
typeof obj.position.left === "number" && (
<div
className="distance-line left"
style={{
left: 0,
top:
activeEdges.vertical === "top"
? `${(obj.position.top as number) + 41.5}px`
: `calc(100% - ${(obj.position.bottom as number) + 41.5}px)`,
width: `${obj.position.left}px`,
}}
>
<span className="distance-label">{obj.position.left}px</span>
</div>
)}
{activeEdges.horizontal === "right" &&
typeof obj.position.right === "number" && (
<div
className="distance-line right"
style={{
right: 0,
top:
activeEdges.vertical === "top"
? `${(obj.position.top as number) + 41.5}px`
: `calc(100% - ${(obj.position.bottom as number) + 41.5}px)`,
width: `${obj.position.right}px`,
}}
>
<span className="distance-label">{obj.position.right}px</span>
</div>
)}
</>
);
};
export default DistanceLines;

View File

@ -1,6 +1,5 @@
import { WalletIcon } from "../../icons/3dChartIcons";
import { useEffect, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import {
useDroppedObjectsStore,
Zones,
@ -9,29 +8,61 @@ import useModuleStore from "../../../store/useModuleStore";
import { determinePosition } from "./functions/determinePosition";
import { getActiveProperties } from "./functions/getActiveProperties";
import { addingFloatingWidgets } from "../../../services/realTimeVisulization/zoneData/addFloatingWidgets";
import {
DublicateIcon,
KebabIcon,
DeleteIcon,
} from "../../icons/ExportCommonIcons";
import DistanceLines from "./DistanceLines"; // Import the DistanceLines component
interface DraggingState {
zone: string;
index: number;
initialPosition: {
top: number | "auto";
left: number | "auto";
right: number | "auto";
bottom: number | "auto";
};
}
const DroppedObjects: React.FC = () => {
const zones = useDroppedObjectsStore((state) => state.zones);
const [openKebabId, setOpenKebabId] = useState<string | null>(null);
const updateObjectPosition = useDroppedObjectsStore(
(state) => state.updateObjectPosition
);
const [draggingIndex, setDraggingIndex] = useState<{
zone: string;
index: number;
} | null>(null);
const [draggingIndex, setDraggingIndex] = useState<DraggingState | null>(
null
);
const [offset, setOffset] = useState<[number, number] | null>(null);
const positionRef = useRef<[number, number] | null>(null);
const [activeEdges, setActiveEdges] = useState<{
vertical: "top" | "bottom";
horizontal: "left" | "right";
} | null>(null); // State to track active edges for distance lines
const [currentPosition, setCurrentPosition] = useState<{
top: number | "auto";
left: number | "auto";
right: number | "auto";
bottom: number | "auto";
} | null>(null); // State to track the current position during drag
const animationRef = useRef<number | null>(null);
const { activeModule } = useModuleStore();
// Get the first zone and its objects
const zoneEntries = Object.entries(zones);
if (zoneEntries.length === 0) return null; // No zone, nothing to render
const [zoneName, zone] = zoneEntries[0]; // Only render the first zone
// Clean up animation frame on unmount
useEffect(() => {
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}, []);
// Handle pointer down event
function handlePointerDown(event: React.PointerEvent, index: number) {
const zoneEntries = Object.entries(zones);
if (zoneEntries.length === 0) return null;
const [zoneName, zone] = zoneEntries[0];
const handlePointerDown = (event: React.PointerEvent, index: number) => {
const obj = zone.objects[index];
const container = document.getElementById("real-time-vis-canvas");
if (!container) return;
@ -40,41 +71,41 @@ const DroppedObjects: React.FC = () => {
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
// Determine which properties are active for this object
// Determine active properties for the initial position
const [activeProp1, activeProp2] = getActiveProperties(obj.position);
// Calculate the offset based on the active properties
// Set active edges for distance lines
const vertical = activeProp1 === "top" ? "top" : "bottom";
const horizontal = activeProp2 === "left" ? "left" : "right";
setActiveEdges({ vertical, horizontal });
// Store the initial position strategy and active edges
setDraggingIndex({
zone: zoneName,
index,
initialPosition: { ...obj.position },
});
// Calculate offset from mouse to object edges
let offsetX = 0;
let offsetY = 0;
if (activeProp1 === "top") {
offsetY =
relativeY -
(typeof obj.position.top === "number" ? obj.position.top : 0);
} else if (activeProp1 === "bottom") {
offsetY =
rect.height -
relativeY -
(typeof obj.position.bottom === "number" ? obj.position.bottom : 0);
offsetY = relativeY - (obj.position.top as number);
} else {
offsetY = rect.height - relativeY - (obj.position.bottom as number);
}
if (activeProp2 === "left") {
offsetX =
relativeX -
(typeof obj.position.left === "number" ? obj.position.left : 0);
} else if (activeProp2 === "right") {
offsetX =
rect.width -
relativeX -
(typeof obj.position.right === "number" ? obj.position.right : 0);
offsetX = relativeX - (obj.position.left as number);
} else {
offsetX = rect.width - relativeX - (obj.position.right as number);
}
setDraggingIndex({ zone: zoneName, index });
setOffset([offsetY, offsetX]);
}
};
// Handle pointer move event
function handlePointerMove(event: React.PointerEvent) {
const handlePointerMove = (event: React.PointerEvent) => {
if (!draggingIndex || !offset) return;
const container = document.getElementById("real-time-vis-canvas");
@ -84,91 +115,194 @@ const DroppedObjects: React.FC = () => {
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
// Determine which properties are active for the dragged object
const obj = zone.objects[draggingIndex.index];
const [activeProp1, activeProp2] = getActiveProperties(obj.position);
// Dynamically determine the current position strategy
const newPositionStrategy = determinePosition(rect, relativeX, relativeY);
const [activeProp1, activeProp2] = getActiveProperties(newPositionStrategy);
// Calculate the new position based on the active properties
let newX = 0;
// Update active edges for distance lines
const vertical = activeProp1 === "top" ? "top" : "bottom";
const horizontal = activeProp2 === "left" ? "left" : "right";
setActiveEdges({ vertical, horizontal });
// Calculate new position based on the active properties
let newY = 0;
if (activeProp2 === "left") {
newX = relativeX - offset[1];
} else if (activeProp2 === "right") {
newX = rect.width - (relativeX + offset[1]);
}
let newX = 0;
if (activeProp1 === "top") {
newY = relativeY - offset[0];
} else if (activeProp1 === "bottom") {
} else {
newY = rect.height - (relativeY + offset[0]);
}
// Ensure the object stays within the canvas boundaries
if (activeProp2 === "left") {
newX = relativeX - offset[1];
} else {
newX = rect.width - (relativeX + offset[1]);
}
// Apply boundaries
newX = Math.max(0, Math.min(rect.width - 50, newX));
newY = Math.max(0, Math.min(rect.height - 50, newY));
// Update the position reference
positionRef.current = [newY, newX];
// Create new position object
const newPosition = {
...newPositionStrategy,
[activeProp1]: newY,
[activeProp2]: newX,
// Clear opposite properties
[activeProp1 === "top" ? "bottom" : "top"]: "auto",
[activeProp2 === "left" ? "right" : "left"]: "auto",
};
// Update the current position state for DistanceLines
setCurrentPosition(newPosition);
// Update the object's position using requestAnimationFrame for smoother animations
if (!animationRef.current) {
animationRef.current = requestAnimationFrame(() => {
if (positionRef.current) {
updateObjectPosition(zoneName, draggingIndex.index, {
...obj.position,
[activeProp1]: positionRef.current[0],
[activeProp2]: positionRef.current[1],
});
}
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
animationRef.current = null;
});
}
}
};
// Handle pointer up event
async function handlePointerUp(event: React.MouseEvent<HTMLDivElement>) {
const handlePointerUp = async (event: React.PointerEvent<HTMLDivElement>) => {
try {
if (!draggingIndex || !offset) return;
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
const container = document.getElementById("real-time-vis-canvas");
if (!container) throw new Error("Canvas container not found");
if (!container) return;
const rect = container.getBoundingClientRect();
const relativeX = event.clientX - rect.left;
const relativeY = event.clientY - rect.top;
// Recalculate the position using determinePosition
const newPosition = determinePosition(rect, relativeX, relativeY);
// Only now determine the final position strategy
const finalPosition = determinePosition(rect, relativeX, relativeY);
const [activeProp1, activeProp2] = getActiveProperties(finalPosition);
// Validate the dragging index and get the object
if (!zone.objects[draggingIndex.index]) {
throw new Error("Dragged object not found in the zone");
// Calculate final position using the new strategy
let finalY = 0;
let finalX = 0;
if (activeProp1 === "top") {
finalY = relativeY - offset[0];
} else {
finalY = rect.height - (relativeY + offset[0]);
}
const obj = { ...zone.objects[draggingIndex.index], position: newPosition };
let response = await addingFloatingWidgets(zone.zoneId, organization, obj);
if (activeProp2 === "left") {
finalX = relativeX - offset[1];
} else {
finalX = rect.width - (relativeX + offset[1]);
}
// Apply boundaries
finalX = Math.max(0, Math.min(rect.width - 50, finalX));
finalY = Math.max(0, Math.min(rect.height - 50, finalY));
const boundedPosition = {
...finalPosition,
[activeProp1]: finalY,
[activeProp2]: finalX,
};
// Save to backend
const email = localStorage.getItem("email") || "";
const organization = email?.split("@")[1]?.split(".")[0];
const response = await addingFloatingWidgets(zone.zoneId, organization, {
...zone.objects[draggingIndex.index],
position: boundedPosition,
});
if (response.message === "Widget updated successfully") {
updateObjectPosition(zoneName, draggingIndex.index, newPosition);
updateObjectPosition(zoneName, draggingIndex.index, boundedPosition);
}
// Reset states
// Clean up
setDraggingIndex(null);
setOffset(null);
setActiveEdges(null); // Clear active edges
setCurrentPosition(null); // Reset current position
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
animationRef.current = null;
}
} catch (error) {
console.error("Error in handlePointerUp:", error);
}
}
};
const handleKebabClick = (id: string, event: React.MouseEvent) => {
event.stopPropagation();
setOpenKebabId((prevId) => (prevId === id ? null : id));
};
const renderObjectContent = (obj: any) => {
switch (obj.className) {
case "floating total-card":
return (
<>
<div className="header-wrapper">
<div className="header">{obj.header}</div>
<div className="data-values">
<div className="value">{obj.value}</div>
<div className="per">{obj.per}</div>
</div>
</div>
<div className="icon">
<WalletIcon />
</div>
</>
);
case "warehouseThroughput floating":
return (
<>
<div className="header">
<h2>Warehouse Throughput</h2>
<p>
<span>(+5) more</span> in 2025
</p>
</div>
<div className="lineGraph" style={{ height: "100%" }}>
{/* <Line data={lineGraphData} options={lineGraphOptions} /> */}
</div>
</>
);
case "fleetEfficiency floating":
return (
<>
<h2 className="header">Fleet Efficiency</h2>
<div className="progressContainer">
<div className="progress">
<div className="barOverflow">
<div
className="bar"
style={{ transform: `rotate(${obj.value}deg)` }}
></div>
</div>
</div>
</div>
<div className="scaleLabels">
<span>0%</span>
<div className="centerText">
<div className="percentage">{obj.per}%</div>
<div className="status">Optimal</div>
</div>
<span>100%</span>
</div>
</>
);
default:
return null;
}
};
return (
<div onPointerMove={handlePointerMove} onPointerUp={handlePointerUp}>
<div
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
className="floating-wrapper"
>
{zone.objects.map((obj, index) => (
<div
key={`${zoneName}-${index}`}
@ -176,77 +310,82 @@ const DroppedObjects: React.FC = () => {
style={{
position: "absolute",
top:
typeof obj.position.top !== "string"
typeof obj.position.top === "number"
? `${obj.position.top}px`
: "auto",
left:
typeof obj.position.left !== "string"
typeof obj.position.left === "number"
? `${obj.position.left}px`
: "auto",
right:
typeof obj.position.right !== "string"
typeof obj.position.right === "number"
? `${obj.position.right}px`
: "auto",
bottom:
typeof obj.position.bottom !== "string"
typeof obj.position.bottom === "number"
? `${obj.position.bottom}px`
: "auto",
// transition: draggingIndex?.index === index ? "none" : "transform 0.1s ease-out",
}}
onPointerDown={(event) => handlePointerDown(event, index)}
>
{obj.className === "floating total-card" ? (
<>
<div className="header-wrapper">
<div className="header">{obj.header}</div>
<div className="data-values">
<div className="value">{obj.value}</div>
<div className="per">{obj.per}</div>
{renderObjectContent(obj)}
<div
className="icon kebab"
onClick={(event) => handleKebabClick(obj.id, event)}
>
<KebabIcon />
</div>
{openKebabId === obj.id && (
<div className="kebab-options">
<div className="dublicate btn">
<div className="icon">
<DublicateIcon />
</div>
<div className="label">Duplicate</div>
</div>
<div className="icon">
<WalletIcon />
</div>
</>
) : obj.className === "warehouseThroughput floating" ? (
<>
<div className="header">
<h2>Warehouse Throughput</h2>
<p>
<span>(+5) more</span> in 2025
</p>
</div>
<div className="lineGraph" style={{ height: "100%" }}>
{/* <Line data={lineGraphData} options={lineGraphOptions} /> */}
</div>
</>
) : obj.className === "fleetEfficiency floating" ? (
<>
<h2 className="header">Fleet Efficiency</h2>
<div className="progressContainer">
<div className="progress">
<div className="barOverflow">
<div
className="bar"
style={{ transform: `rotate(${obj.value}deg)` }}
></div>
</div>
<div className="edit btn">
<div className="icon">
<DeleteIcon />
</div>
<div className="label">Delete</div>
</div>
<div className="scaleLabels">
<span>0%</span>
<div className="centerText">
<div className="percentage">{obj.per}%</div>
<div className="status">Optimal</div>
</div>
<span>100%</span>
</div>
</>
) : null}
</div>
)}
</div>
))}
{/* Render DistanceLines component during drag */}
{draggingIndex !== null &&
activeEdges !== null &&
currentPosition !== null && (
<DistanceLines
obj={{
position: {
top:
currentPosition.top !== "auto"
? currentPosition.top
: undefined,
bottom:
currentPosition.bottom !== "auto"
? currentPosition.bottom
: undefined,
left:
currentPosition.left !== "auto"
? currentPosition.left
: undefined,
right:
currentPosition.right !== "auto"
? currentPosition.right
: undefined,
},
}}
activeEdges={activeEdges}
/>
)}
</div>
);
};
export default DroppedObjects;
// in pointer move even when i goes to top right the value not changes to top right same problem in bottom left

View File

@ -8,11 +8,9 @@ export function determinePosition(
right: number | "auto";
bottom: number | "auto";
} {
// Calculate the midpoints of the canvas
const centerX = canvasRect.width / 2;
const centerY = canvasRect.height / 2;
// Initialize position with default values
let position: {
top: number | "auto";
left: number | "auto";
@ -21,9 +19,8 @@ export function determinePosition(
};
if (relativeY < centerY) {
// Top half
if (relativeX < centerX) {
// Left side
console.log("Top-left");
position = {
top: relativeY,
left: relativeX,
@ -31,7 +28,7 @@ export function determinePosition(
bottom: "auto",
};
} else {
// Right side
console.log("Top-right");
position = {
top: relativeY,
right: canvasRect.width - relativeX,
@ -40,9 +37,8 @@ export function determinePosition(
};
}
} else {
// Bottom half
if (relativeX < centerX) {
// Left side
console.log("Bottom-left");
position = {
bottom: canvasRect.height - relativeY,
left: relativeX,
@ -50,7 +46,7 @@ export function determinePosition(
top: "auto",
};
} else {
// Right side
console.log("Bottom-right");
position = {
bottom: canvasRect.height - relativeY,
right: canvasRect.width - relativeX,
@ -61,4 +57,4 @@ export function determinePosition(
}
return position;
}
}

View File

@ -1,17 +1,11 @@
export const getActiveProperties = (position: {
top: number | "auto";
left: number | "auto";
right: number | "auto";
bottom: number | "auto";
}) => {
let activeProps: ["top", "left"] | ["bottom", "right"] = ["top", "left"]; // Default to top-left
if (
typeof position.bottom !== "string" &&
typeof position.right !== "string"
) {
activeProps = ["bottom", "right"];
export function getActiveProperties(position: any): [string, string] {
if (position.top !== "auto" && position.left !== "auto") {
return ["top", "left"]; // Top-left
} else if (position.top !== "auto" && position.right !== "auto") {
return ["top", "right"]; // Top-right
} else if (position.bottom !== "auto" && position.left !== "auto") {
return ["bottom", "left"]; // Bottom-left
} else {
return ["bottom", "right"]; // Bottom-right
}
return activeProps;
};
}

View File

@ -0,0 +1,8 @@
import { create } from "zustand";
const useFloatingDataStore = create((set) => ({
floatingdata: [], // Initial state
setfloatingadata: (newData: []) => set({ floatingdata: newData }), // Setter function
}));
export default useFloatingDataStore;

View File

@ -90,3 +90,5 @@ export interface Zones {
zoneId: string;
objects: DroppedObject[];
}

View File

@ -541,4 +541,156 @@
.zone.active {
background-color: #007bff;
color: white;
}
.floating-wrapper {
.icon {
width: 25px !important;
height: 25px !important;
background-color: transparent;
}
.kebab {
width: 30px;
height: 30px;
position: absolute !important;
top: 0px;
right: 0px;
z-index: 10;
cursor: pointer;
@include flex-center;
}
.kebab-options {
position: absolute;
top: 18px;
right: 5px;
transform: translate(0px, 0);
background-color: var(--background-color);
z-index: 10;
display: flex;
flex-direction: column;
gap: 6px;
border-radius: 4px;
box-shadow: var(--box-shadow-medium);
.btn {
display: flex;
gap: 6px;
align-items: center;
padding: 5px 10px;
color: var(--text-color);
cursor: pointer;
&:hover {
.label {
color: var(--accent-color);
}
}
&:hover {
background-color: var(--highlight-accent-color);
width: 100%;
svg {
&:first-child {
fill: var(--accent-color);
}
&:last-child {
fill: auto;
stroke: var(--accent-color);
}
}
}
}
.dublicate {
cursor: not-allowed;
}
}
}
/* General styles for all distance lines */
.distance-line {
position: absolute;
border-style: dashed;
border-color: var(--accent-color); /* Green color for visibility */
border-width: 1px;
pointer-events: none; /* Ensure lins don't interfere with dragging */
z-index: 10000;
}
/* Label styles for displaying distance values */
.distance-label {
position: absolute;
background-color: var(--accent-color);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 3px;
white-space: nowrap;
transform: translate(-50%, -50%); /* Center the label */
}
/* Specific styles for each type of line */
/* Top distance line */
.distance-line.top {
border-bottom: none; /* Remove bottom border for a single line */
width: 2px; /* Thin vertical line */
}
.distance-line.top .distance-label {
top: -10px; /* Position label above the line */
left: 50%; /* Center horizontally */
}
/* Bottom distance line */
.distance-line.bottom {
border-top: none; /* Remove top border for a single line */
width: 2px; /* Thin vertical line */
}
.distance-line.bottom .distance-label {
bottom: -10px; /* Position label below the line */
left: 50%; /* Center horizontally */
}
/* Left distance line */
.distance-line.left {
border-right: none; /* Remove right border for a single line */
height: 2px; /* Thin horizontal line */
}
.distance-line.left .distance-label {
left: -10px; /* Position label to the left of the line */
top: 50%; /* Center vertically */
}
/* Right distance line */
.distance-line.right {
border-left: none; /* Remove left border for a single line */
height: 2px; /* Thin horizontal line */
}
.distance-line.right .distance-label {
right: -10px; /* Position label to the right of the line */
top: 50%; /* Center vertically */
}