Files
Dwinzo_dev/app/src/modules/visualization/widgets/panel/Panel.tsx

345 lines
10 KiB
TypeScript
Raw Normal View History

2025-03-25 11:47:41 +05:30
import React, { useEffect, useMemo, useRef, useState } from "react";
2025-04-11 18:08:47 +05:30
import { arrayMove } from "@dnd-kit/sortable";
import { useSocketStore } from "../../../../store/builder/store";
2025-04-11 18:08:47 +05:30
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
import { useWidgetStore } from "../../../../store/useWidgetStore";
import { DraggableWidget } from "../2d/DraggableWidget";
2025-03-25 11:47:41 +05:30
type Side = "top" | "bottom" | "left" | "right";
interface Widget {
id: string;
type: string;
title: string;
panel: Side;
data: any;
}
interface PanelProps {
selectedZone: {
zoneName: string;
activeSides: Side[];
panelOrder: Side[];
lockedPanels: Side[];
2025-04-11 18:08:47 +05:30
points: [];
zoneId: string;
zoneViewPortTarget: number[];
zoneViewPortPosition: number[];
2025-03-25 11:47:41 +05:30
widgets: Widget[];
};
setSelectedZone: React.Dispatch<
React.SetStateAction<{
zoneName: string;
activeSides: Side[];
panelOrder: Side[];
lockedPanels: Side[];
2025-04-11 18:08:47 +05:30
points: [];
zoneId: string;
zoneViewPortTarget: number[];
zoneViewPortPosition: number[];
2025-03-25 11:47:41 +05:30
widgets: Widget[];
}>
>;
hiddenPanels: any;
setZonesData: React.Dispatch<React.SetStateAction<any>>;
waitingPanels: any;
2025-03-25 11:47:41 +05:30
}
const generateUniqueId = () =>
`${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
2025-03-25 11:47:41 +05:30
2025-03-25 15:55:48 +05:30
const Panel: React.FC<PanelProps> = ({
selectedZone,
setSelectedZone,
hiddenPanels,
2025-03-27 10:54:40 +05:30
setZonesData,
waitingPanels,
2025-03-25 15:55:48 +05:30
}) => {
2025-03-25 11:47:41 +05:30
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
const [panelDimensions, setPanelDimensions] = useState<{
[side in Side]?: { width: number; height: number };
}>({});
2025-03-27 12:16:20 +05:30
const [openKebabId, setOpenKebabId] = useState<string | null>(null);
2025-03-26 13:52:10 +05:30
const { isPlaying } = usePlayButtonStore();
2025-04-01 19:35:11 +05:30
const { visualizationSocket } = useSocketStore();
2025-04-07 17:55:14 +05:30
const [canvasDimensions, setCanvasDimensions] = useState({
width: 0,
height: 0,
});
2025-04-07 17:55:14 +05:30
// Track canvas dimensions
useEffect(() => {
const canvas = document.getElementById("real-time-vis-canvas");
2025-04-07 17:55:14 +05:30
if (!canvas) return;
const updateCanvasDimensions = () => {
const rect = canvas.getBoundingClientRect();
setCanvasDimensions({
width: rect.width,
height: rect.height,
});
};
updateCanvasDimensions();
const resizeObserver = new ResizeObserver(updateCanvasDimensions);
resizeObserver.observe(canvas);
return () => resizeObserver.unobserve(canvas);
2025-04-07 17:55:14 +05:30
}, []);
// Calculate panel size
const panelSize = Math.max(
Math.min(canvasDimensions.width * 0.25, canvasDimensions.height * 0.25),
170 // Min 170px
);
2025-04-07 18:01:48 +05:30
// Define getPanelStyle
2025-03-25 11:47:41 +05:30
const getPanelStyle = useMemo(
() => (side: Side) => {
const currentIndex = selectedZone.panelOrder.indexOf(side);
const previousPanels = selectedZone.panelOrder.slice(0, currentIndex);
const leftActive = previousPanels.includes("left");
const rightActive = previousPanels.includes("right");
const topActive = previousPanels.includes("top");
const bottomActive = previousPanels.includes("bottom");
2025-04-07 18:01:48 +05:30
2025-03-25 11:47:41 +05:30
switch (side) {
case "top":
case "bottom":
return {
minWidth: "170px",
width: `calc(100% - ${
(leftActive ? panelSize : 0) + (rightActive ? panelSize : 0)
}px)`,
minHeight: "170px",
height: `${panelSize}px`,
left: leftActive ? `${panelSize}px` : "0",
right: rightActive ? `${panelSize}px` : "0",
2025-03-25 11:47:41 +05:30
[side]: "0",
};
case "left":
case "right":
return {
minWidth: "170px",
width: `${panelSize}px`,
minHeight: "170px",
height: `calc(100% - ${
(topActive ? panelSize : 0) + (bottomActive ? panelSize : 0)
}px)`,
top: topActive ? `${panelSize}px` : "0",
bottom: bottomActive ? `${panelSize}px` : "0",
2025-03-25 11:47:41 +05:30
[side]: "0",
};
default:
return {};
}
},
[selectedZone.panelOrder, panelSize]
2025-03-25 11:47:41 +05:30
);
// Handle drop event
2025-03-25 11:47:41 +05:30
const handleDrop = (e: React.DragEvent, panel: Side) => {
e.preventDefault();
const { draggedAsset } = useWidgetStore.getState();
if (
!draggedAsset ||
isPanelLocked(panel) ||
hiddenPanels[selectedZone.zoneId]?.includes(panel)
)
return;
2025-03-25 14:25:51 +05:30
2025-03-25 11:47:41 +05:30
const currentWidgetsCount = getCurrentWidgetCount(panel);
const maxCapacity = calculatePanelCapacity(panel);
2025-03-25 14:25:51 +05:30
if (currentWidgetsCount < maxCapacity) {
addWidgetToPanel(draggedAsset, panel);
}
2025-03-25 11:47:41 +05:30
};
2025-03-25 14:25:51 +05:30
// Check if panel is locked
2025-03-25 14:25:51 +05:30
const isPanelLocked = (panel: Side) =>
2025-03-25 11:47:41 +05:30
selectedZone.lockedPanels.includes(panel);
2025-03-25 14:25:51 +05:30
// Get current widget count in a panel
2025-03-25 14:25:51 +05:30
const getCurrentWidgetCount = (panel: Side) =>
selectedZone.widgets.filter((w) => w.panel === panel).length;
// Calculate panel capacity
2025-03-25 11:47:41 +05:30
const calculatePanelCapacity = (panel: Side) => {
2025-04-10 10:06:53 +05:30
const CHART_WIDTH = panelSize - 10;
const CHART_HEIGHT = panelSize - 10;
2025-03-25 14:25:51 +05:30
2025-03-25 11:47:41 +05:30
const dimensions = panelDimensions[panel];
if (!dimensions) {
return panel === "top" || panel === "bottom" ? 5 : 3; // Fallback capacities
2025-03-25 11:47:41 +05:30
}
2025-03-25 14:25:51 +05:30
2025-03-25 11:47:41 +05:30
return panel === "top" || panel === "bottom"
? Math.max(1, Math.floor(dimensions.width / CHART_WIDTH))
: Math.max(1, Math.floor(dimensions.height / CHART_HEIGHT));
2025-03-25 11:47:41 +05:30
};
2025-03-25 14:25:51 +05:30
// Add widget to panel
const addWidgetToPanel = async (asset: any, panel: Side) => {
const email = localStorage.getItem("email") ?? "";
const organization = email?.split("@")[1]?.split(".")[0];
2025-03-25 11:47:41 +05:30
const newWidget = {
...asset,
id: generateUniqueId(),
panel,
};
2025-04-01 19:35:11 +05:30
let addWidget = {
organization: organization,
zoneId: selectedZone.zoneId,
2025-04-07 17:55:14 +05:30
widget: newWidget,
};
2025-04-01 19:35:11 +05:30
if (visualizationSocket) {
2025-04-07 17:55:14 +05:30
visualizationSocket.emit("v2:viz-widget:add", addWidget);
}
2025-04-01 19:35:11 +05:30
setSelectedZone((prev) => ({
...prev,
widgets: [...prev.widgets, newWidget],
}));
2025-03-25 11:47:41 +05:30
};
// Observe panel dimensions
2025-03-25 11:47:41 +05:30
useEffect(() => {
const observers: ResizeObserver[] = [];
const currentPanelRefs = panelRefs.current;
selectedZone.activeSides.forEach((side) => {
const element = currentPanelRefs[side];
if (element) {
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
setPanelDimensions((prev) => ({
...prev,
[side]: { width, height },
}));
}
});
2025-03-25 11:47:41 +05:30
observer.observe(element);
observers.push(observer);
}
});
return () => {
observers.forEach((observer) => observer.disconnect());
};
}, [selectedZone.activeSides]);
// Handle widget reordering
const handleReorder = (fromIndex: number, toIndex: number, panel: Side) => {
setSelectedZone((prev) => {
const widgetsInPanel = prev.widgets.filter((w) => w.panel === panel);
const reorderedWidgets = arrayMove(widgetsInPanel, fromIndex, toIndex);
const updatedWidgets = prev.widgets
.filter((widget) => widget.panel !== panel)
.concat(reorderedWidgets);
return {
...prev,
widgets: updatedWidgets,
};
});
};
// Calculate capacities and dimensions
const topWidth = getPanelStyle("top").width;
2025-04-09 09:42:49 +05:30
const bottomWidth = getPanelStyle("bottom").height;
const leftHeight = getPanelStyle("left").height;
const rightHeight = getPanelStyle("right").height;
const topCapacity = calculatePanelCapacity("top");
const bottomCapacity = calculatePanelCapacity("bottom");
const leftCapacity = calculatePanelCapacity("left");
const rightCapacity = calculatePanelCapacity("right");
2025-03-25 11:47:41 +05:30
return (
<>
<style>
{`
:root {
--topWidth: ${topWidth};
2025-04-09 09:42:49 +05:30
--bottomWidth: ${bottomWidth} ;
--leftHeight: ${leftHeight};
--rightHeight: ${rightHeight};
--topCapacity: ${topCapacity};
--bottomCapacity: ${bottomCapacity};
--leftCapacity: ${leftCapacity};
--rightCapacity: ${rightCapacity};
}
`}
</style>
2025-03-25 11:47:41 +05:30
{selectedZone.activeSides.map((side) => (
<div
key={side}
id={`panel-wrapper-${side}`}
className={`panel ${side}-panel absolute ${
hiddenPanels[selectedZone.zoneId]?.includes(side) ? "hidePanel" : ""
}`}
2025-03-25 11:47:41 +05:30
style={getPanelStyle(side)}
onDrop={(e) => handleDrop(e, side)}
onDragOver={(e) => e.preventDefault()}
ref={(el) => {
if (el) {
panelRefs.current[side] = el;
} else {
delete panelRefs.current[side];
}
}}
>
<div
className={`panel-content ${
waitingPanels === side ? `${side}-closing` : ""
}${
!hiddenPanels[selectedZone.zoneId]?.includes(side) &&
waitingPanels !== side
? `${side}-opening`
: ""
} ${isPlaying ? "fullScreen" : ""}`}
2025-03-25 11:47:41 +05:30
style={{
pointerEvents:
selectedZone.lockedPanels.includes(side) ||
hiddenPanels[selectedZone.zoneId]?.includes(side)
? "none"
: "auto",
2025-03-25 11:47:41 +05:30
opacity: selectedZone.lockedPanels.includes(side) ? "0.8" : "1",
}}
>
{selectedZone.widgets
.filter((w) => w.panel === side)
.map((widget, index) => (
2025-03-25 15:55:48 +05:30
<DraggableWidget
hiddenPanels={hiddenPanels}
widget={widget}
key={widget.id}
index={index}
onReorder={(fromIndex, toIndex) =>
handleReorder(fromIndex, toIndex, side)
}
2025-03-27 10:54:40 +05:30
openKebabId={openKebabId}
setOpenKebabId={setOpenKebabId}
selectedZone={selectedZone}
setSelectedZone={setSelectedZone}
2025-03-25 15:55:48 +05:30
/>
2025-03-25 11:47:41 +05:30
))}
</div>
</div>
))}
</>
);
};
export default Panel;