SVG Unit Display Tool - Part 2
Adrian Birta
React Front-end Developer | 5+ Years in React & Scalable Web Apps | I Build High-Performance UIs That Scale
I’m pleased to share some recent updates to the SVG Unit Display Tool, a web application aimed at simplifying the visualization of unit data on floor plans. This release includes several new features and improvements based on the initial version.
??? New Features
1. Enhanced Drawing Capabilities:
2. Improved Unit Interaction:
3. Updated Drawing Controls:
领英推荐
??? Sample Code
Here’s a brief overview of the updated code, illustrating how to integrate these new features:
Sidebar.tsx
import React from 'react';
import DrawControl from './DrawControl';
import { CircleUnit, DrawModeState, PolygonUnit } from './interfaces';
import { isCircleUnit, isPolygonUnit } from './typeGuards';
import { Trash } from 'react-bootstrap-icons';
type UnitData = PolygonUnit | CircleUnit;
interface SidebarProps {
units: UnitData[];
unit: UnitData | null;
selectModeState: boolean;
drawModeState: DrawModeState;
onSelectModeState: () => void;
onDrawModeState: (drawState: string) => void;
onDeleteUnit: (unitID: number) => void;
}
const Sidebar: React.FC<SidebarProps> = ({
unit,
units,
selectModeState,
drawModeState,
onSelectModeState,
onDrawModeState,
onDeleteUnit,
}) => {
const renderCoordinates = (unitItem: UnitData = unit!) => {
if (isPolygonUnit(unitItem)) {
return (
<>
<span><i>Coordinates:</i></span>
{unitItem.points.map((point, index) => (
<span key={unitItem.label + index}>
({point.x.toFixed(0)}, {point.y.toFixed(0)})
</span>
))}
</>
);
} else if (isCircleUnit(unitItem)) {
return (
<span>
<span><i>Coordinates:</i></span> ({unitItem.cx?.toFixed(0)}, {unitItem.cy?.toFixed(0)}, r: {unitItem.r?.toFixed(0)})
</span>
);
}
return <span><span><i>Coordinates:</i></span> ({(unitItem as any).x?.toFixed(0)}, {(unitItem as any).y?.toFixed(0)})</span>;
};
return (
<div className="sidebar">
<section className="sidebar-draw">
<DrawControl
selectModeState={selectModeState}
drawModeState={drawModeState}
onSelectModeState={onSelectModeState}
onDrawModeState={onDrawModeState}
/>
</section>
<section className="sidebar-selected-unit">
{unit && (
<div>
<span><h3>Selected Unit</h3><p>Label: {unit.label}</p>{renderCoordinates()}</span>
<span>
<button onClick={() => onDeleteUnit(unit.id)} className="control-button">
<Trash color="black" size={20} />
</button>
</span>
</div>
)}
</section>
<section className="sidebar-units">
<h3>Units</h3>
{units.length ? units.map((unitItem, index) => (
<div key={unitItem.label}>
<span>{index + 1}. <b>{unitItem.label}</b>: </span>
{renderCoordinates(unitItem)}
</div>
)) : <>No unit on the Floor Map. Start Drawing!</>}
</section>
</div>
);
};
export default Sidebar;
App.tsx
import React, { useEffect, useRef, useState } from 'react';
import FloorPlan from './components/FloorPlan';
import Sidebar from './components/Sidebar';
import './App.css';
import { CircleUnit, PolygonUnit, Point, DrawModeState } from './components/interfaces';
import { isCircleUnit } from './components/typeGuards';
type UnitData = PolygonUnit | CircleUnit;
function App() {
const shapesCounter = useRef(2);
const [unitsData, setUnitsData] = useState<UnitData[]>([
{
id: 1,
label: 'Unit 1',
points: [{ x: 230, y: 180 }, { x: 300, y: 200 }, { x: 260, y: 250 }],
},
{
id: 2,
label: 'Unit 2',
cx: 30,
cy: 30,
r: 20
}
]);
const [newShape, setNewShape] = useState<UnitData>({
id: 0,
label: '',
points: [],
cx: 0,
cy: 0,
r: 0,
});
const [drawModeState, setDrawModeState] = useState<DrawModeState>({ drawType: '', drawState: false });
const [selectModeState, setSelectModeState] = useState(true);
const [selectedUnit, setSelectedUnit] = useState<UnitData | null>(null);
const handleCreateNewShape = (svgPoint: Point | null, distance?: number) => {
setNewShape(prevNewShape => {
if (drawModeState.drawType === 'polygon') {
return svgPoint ? {
...prevNewShape,
points: [...(prevNewShape as PolygonUnit).points, { x: svgPoint.x, y: svgPoint.y }],
} : { ...prevNewShape, points: [] };
} else if (drawModeState.drawType === 'circle' && svgPoint && distance !== undefined) {
return { ...prevNewShape, cx: svgPoint.x, cy: svgPoint.y, r: distance };
}
return prevNewShape;
});
};
const handleAddNewShape = () => {
shapesCounter.current += 1;
if (isCircleUnit(newShape) && newShape.r < 20) {
setNewShape({ id: 0, label: '', cx: 0, cy: 0, r: 0 } as CircleUnit);
return;
}
setUnitsData(prevUnits => [
...prevUnits,
{ ...newShape, id: shapesCounter.current, label: `Unit ${shapesCounter.current}` }
]);
};
const handleSelectModeState = () => {
setSelectModeState(prevState => !prevState);
if (!selectModeState) {
setDrawModeState(prevDrawMode => ({ ...prevDrawMode, drawState: false }));
} else {
setSelectedUnit(null);
}
};
const handleDrawModeState = (drawType: string) => {
setSelectedUnit(null);
setDrawModeState(prevDrawMode => {
return !prevDrawMode.drawState || prevDrawMode.drawType !== drawType
? { drawType, drawState: true }
: { drawType, drawState: false };
});
};
const handleDeleteUnit = (unitID: number) => {
setSelectedUnit(null);
setUnitsData(prevUnitsData => prevUnitsData.filter(unitData => unitData.id !== unitID));
};
const handleUnitClick = (unit: UnitData) => {
if (selectModeState) {
setSelectedUnit(prevUnit => prevUnit?.id === unit.id ? null : unit);
setNewShape(unit);
}
};
useEffect(() => {
if (drawModeState.drawType === 'polygon') {
setNewShape({ id: 0, label: '', points: [] } as PolygonUnit);
} else if (drawModeState.drawType === 'circle') {
setNewShape({ id: 0, label: '', cx: 0, cy: 0, r: 0 } as CircleUnit);
}
}, [drawModeState.drawType]);
return (
<div className="App">
<h1>SVG Unit Display Tool</h1>
<section className="app-content">
<Sidebar
units={unitsData}
unit={selectedUnit}
selectModeState={selectModeState}
drawModeState={drawModeState}
onSelectModeState={handleSelectModeState}
onDrawModeState={handleDrawModeState}
onDeleteUnit={handleDeleteUnit}
/>
<FloorPlan
units={unitsData}
onUnitClick={handleUnitClick}
selectedUnitId={selectedUnit?.id}
drawModeState={drawModeState}
onCreateNewShape={handleCreateNewShape}
onAddNewShape={handleAddNewShape}
/>
</section>
</div>
);
}
export default App;
?? Looking Forward
This update represents progress in the development of the SVG Unit Display Tool, but there is always potential for further improvement.
If you're curious to see how it works, check out the CodeSandbox where I've set up a live demo. Feel free to explore the code and try it out yourself!
You can explore the project and contribute to its development through the GitHub repository.
Thank you for your time.