Skip to content

Commit 06b2f82

Browse files
authored
Merge pull request #13 from plagosus/drag-and-drop
Drag'n'Drop Improved
2 parents 999712d + ca83447 commit 06b2f82

1 file changed

Lines changed: 70 additions & 27 deletions

File tree

src/App.tsx

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,41 @@ export default function RackPlanner() {
254254

255255
// --- Drag and Drop Handlers ---
256256

257+
// Helper: Check if a module can be dropped at targetIndex
258+
const checkCanDrop = (
259+
targetIndex: number,
260+
moduleUSize: number,
261+
originalIndex?: number
262+
): boolean => {
263+
// Boundary Check: If top of module goes below index 0 (top of rack)
264+
if (targetIndex - moduleUSize + 1 < 0) {
265+
return false;
266+
}
267+
268+
// Collision Check
269+
const slotsNeeded = moduleUSize;
270+
let originalModuleId: string | null = null;
271+
272+
if (originalIndex !== undefined && rackSlots[originalIndex]) {
273+
originalModuleId = rackSlots[originalIndex].moduleId;
274+
}
275+
276+
for (let i = 0; i < slotsNeeded; i++) {
277+
const slotIndex = targetIndex - i;
278+
if (slotIndex < 0) return false;
279+
280+
const slot = rackSlots[slotIndex];
281+
if (slot.moduleId !== null) {
282+
// If it's occupied, check if it's the module we are currently moving
283+
if (originalModuleId && slot.moduleId === originalModuleId) {
284+
continue; // Same module, safe.
285+
}
286+
return false; // Collision with another module
287+
}
288+
}
289+
return true;
290+
};
291+
257292
const handleDragStart = (e: React.DragEvent, module: RackModule, originalIndex?: number) => {
258293
setDraggedItem({ module, originalIndex });
259294
e.dataTransfer.effectAllowed = 'move';
@@ -274,17 +309,17 @@ export default function RackPlanner() {
274309
const { module, originalIndex } = draggedItem;
275310
const slotsNeeded = module.uSize;
276311

277-
// Boundary Check
278-
if (targetIndex - slotsNeeded + 1 < 0) {
279-
alert(`Not enough space at top for a ${slotsNeeded}U module.`);
312+
if (!checkCanDrop(targetIndex, slotsNeeded, originalIndex)) {
313+
// Visual feedback is shown during drag, but if they drop anyway, we just cancel.
314+
// Optionally alert if you want, but silent fail is arguably better if UI provides red cue.
280315
setDraggedItem(null);
281316
return;
282317
}
283318

284-
// Collision Check
319+
// Proceed to place item
285320
const newSlots = [...rackSlots];
286321

287-
// 1. If moving, clear old position first
322+
// 1. Clear old position if moving
288323
if (originalIndex !== undefined) {
289324
const originalId = newSlots[originalIndex].moduleId;
290325
if (originalId) {
@@ -296,22 +331,7 @@ export default function RackPlanner() {
296331
}
297332
}
298333

299-
// 2. Check for collisions
300-
let collision = false;
301-
for (let i = 0; i < slotsNeeded; i++) {
302-
if (newSlots[targetIndex - i].moduleId !== null) {
303-
collision = true;
304-
break;
305-
}
306-
}
307-
308-
if (collision) {
309-
alert('Space is occupied.');
310-
setDraggedItem(null);
311-
return;
312-
}
313-
314-
// 3. Place item
334+
// 2. Place item
315335
const instanceId =
316336
originalIndex !== undefined
317337
? rackSlots[originalIndex!].moduleId!
@@ -464,6 +484,8 @@ export default function RackPlanner() {
464484
);
465485
}
466486

487+
488+
467489
return (
468490
<div className="h-screen w-full overflow-hidden flex flex-col">
469491
<div className="flex-1 flex flex-col bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-sans transition-colors duration-300">
@@ -573,7 +595,26 @@ export default function RackPlanner() {
573595
const isModulePart = isOccupied && !isModuleStart;
574596

575597
// Drag Over styling
576-
const isDragTarget = dragOverIndex === index;
598+
const uSize = draggedItem?.module.uSize || 1;
599+
const isDragTarget =
600+
dragOverIndex !== null &&
601+
index <= dragOverIndex &&
602+
index > dragOverIndex - uSize;
603+
// Only show text on the main hover slot
604+
const isMainDragTarget = dragOverIndex === index;
605+
606+
// Determine validity if it's a drag target
607+
// Use checkCanDrop on the dragOverIndex, not current index (since highlight depends on master drop index)
608+
// But wait, the loop runs for each slot.
609+
// If I am highlighting slots 4 and 5 because dragOverIndex is 5 (and size is 2).
610+
// I want to know if dropping at 5 is valid.
611+
612+
const isValid = dragOverIndex !== null ? checkCanDrop(dragOverIndex, uSize, draggedItem?.originalIndex) : true;
613+
614+
// Styles
615+
const dragColorClass = isValid
616+
? 'bg-indigo-500/20 border-indigo-500' // Valid
617+
: 'bg-red-500/20 border-red-500'; // Invalid
577618

578619
// Calculate height if start
579620
const moduleHeight = isModuleStart
@@ -585,7 +626,7 @@ export default function RackPlanner() {
585626
key={index}
586627
onDragOver={(e) => handleDragOver(e, index)}
587628
onDrop={(e) => handleDrop(e, index)}
588-
className={`relative flex w-full transition-colors ${isDragTarget ? 'bg-indigo-500/20 z-20' : ''
629+
className={`relative flex w-full transition-colors ${isDragTarget ? `${dragColorClass} z-20` : ''
589630
}`}
590631
style={{ height: isModulePart ? 0 : moduleHeight }} // Collapse covered slots
591632
>
@@ -733,10 +774,12 @@ export default function RackPlanner() {
733774

734775
{/* Drop Zone Indicator */}
735776
{isDragTarget && (
736-
<div className="absolute inset-0 border-2 border-indigo-500 bg-indigo-500/10 z-30 pointer-events-none flex items-center justify-center">
737-
<span className="text-xs font-bold text-indigo-500 bg-white dark:bg-gray-900 px-2 py-1 rounded shadow">
738-
Drop Here ({draggedItem?.module.uSize}U)
739-
</span>
777+
<div className={`absolute inset-0 border-2 ${isValid ? 'border-indigo-500 bg-indigo-500/10' : 'border-red-500 bg-red-500/10'} z-30 pointer-events-none flex items-center justify-center`}>
778+
{isMainDragTarget && (
779+
<span className={`text-xs font-bold ${isValid ? 'text-indigo-500' : 'text-red-500'} bg-white dark:bg-gray-900 px-2 py-1 rounded shadow`}>
780+
{isValid ? `Mount Here (${uSize}U)` : 'Cannot Mount Here'}
781+
</span>
782+
)}
740783
</div>
741784
)}
742785
</div>

0 commit comments

Comments
 (0)