Skip to content

Commit 4306b34

Browse files
committed
fix: Remove unused code for drag and drop
!
1 parent 13bcfec commit 4306b34

17 files changed

Lines changed: 609 additions & 914 deletions

‎src/app/dnd.tsx‎

Whitespace-only changes.

‎src/app/sortable-grid/page.tsx‎

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
"use client";
2+
3+
import {
4+
closestCenter,
5+
DndContext,
6+
DragOverEvent,
7+
DragOverlay,
8+
DragStartEvent,
9+
KeyboardSensor,
10+
PointerSensor,
11+
useSensor,
12+
} from "@dnd-kit/core";
13+
import { RowColumn } from "@/components/form-builder/canvas/sortable-row";
14+
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
15+
import { Form } from "@/components/ui/form";
16+
import { useFormBuilderStore } from "@/stores/form-builder-store";
17+
import { useForm } from "react-hook-form";
18+
import { useMemo, memo, useCallback, useState } from "react";
19+
import { Viewports } from "@/types/form-builder.types";
20+
import { FormComponentModel } from "@/models/FormComponent";
21+
import { RenderEditorComponent } from "../helpers/render-editor-component";
22+
// Memoize the empty state component
23+
const EmptyState = memo(() => (
24+
<div className="p-6 text-center text-sm text-muted-foreground bg-black/5 rounded-lg max-w-md mx-auto border-dashed border-2 border-slate-300">
25+
Please add a component to the form
26+
</div>
27+
));
28+
29+
EmptyState.displayName = "EmptyState";
30+
31+
const getGridRows = (
32+
items: FormComponentModel[],
33+
viewport: Viewports
34+
): FormComponentModel[][] => {
35+
const rows: FormComponentModel[][] = [];
36+
let currentRow: FormComponentModel[] = [];
37+
let currentRowSpan = 0;
38+
39+
items.forEach((item) => {
40+
const colSpan = +item.getField("properties.style.colSpan", viewport) || 12;
41+
42+
// If adding this item would exceed 12 columns, start a new row
43+
if (currentRowSpan + colSpan > 12) {
44+
if (currentRow.length > 0) {
45+
rows.push([...currentRow]);
46+
}
47+
currentRow = [item];
48+
currentRowSpan = colSpan;
49+
} else {
50+
currentRow.push(item);
51+
currentRowSpan += colSpan;
52+
}
53+
});
54+
55+
// Add the last row if it has items
56+
if (currentRow.length > 0) {
57+
rows.push(currentRow);
58+
}
59+
60+
return rows;
61+
};
62+
63+
export function updateColSpans(
64+
updateItems: FormComponentModel[],
65+
viewport: Viewports
66+
): { id: string; span: number }[] {
67+
if (!updateItems.length) return [];
68+
69+
const totalColumns = 12;
70+
const itemCount = updateItems.length;
71+
72+
// Calculate base span and remainder
73+
const baseSpan = Math.floor(totalColumns / itemCount);
74+
const remainder = totalColumns % itemCount;
75+
76+
const adjustedSpans: { id: string; span: number }[] = [];
77+
78+
// Distribute spans equally, with remainder distributed to first few items
79+
updateItems.forEach((item, index) => {
80+
if (!item) return;
81+
// Add one extra column to the first 'remainder' items
82+
const span = index < remainder ? baseSpan + 1 : baseSpan;
83+
adjustedSpans.push({ id: item.id, span });
84+
});
85+
86+
return adjustedSpans;
87+
}
88+
89+
export default function GenerateCanvasGrid() {
90+
// Split store selectors to minimize re-renders
91+
const components = useFormBuilderStore((state) => state.components);
92+
const selectComponent = useFormBuilderStore((state) => state.selectComponent);
93+
const mode = useFormBuilderStore((state) => state.mode);
94+
const form = useForm();
95+
const updateComponent = useFormBuilderStore((state) => state.updateComponent);
96+
const moveComponent = useFormBuilderStore((state) => state.moveComponent);
97+
const viewport = useFormBuilderStore((state) => state.viewport);
98+
const [activeId, setActiveId] = useState<string | null>(null);
99+
const activeIndex = activeId
100+
? components.findIndex((component) => component.id === activeId)
101+
: -1;
102+
103+
const gridRows = getGridRows(components, viewport);
104+
105+
// Create sensors outside of callback
106+
const pointerSensor = useSensor(PointerSensor, {
107+
activationConstraint: {
108+
distance: 20,
109+
},
110+
});
111+
const keyboardSensor = useSensor(KeyboardSensor, {
112+
coordinateGetter: sortableKeyboardCoordinates,
113+
});
114+
115+
// Memoize sensors array
116+
const sensors = useMemo(
117+
() => [pointerSensor, keyboardSensor],
118+
[pointerSensor, keyboardSensor]
119+
);
120+
121+
const handleDragStart = useCallback(
122+
(event: DragStartEvent) => {
123+
selectComponent(null);
124+
setActiveId(event.active.id as string);
125+
},
126+
[selectComponent]
127+
);
128+
129+
130+
// Memoize drag end handler
131+
const handleDragEnd = (event: any) => {
132+
const { active, over } = event;
133+
134+
if (!over) {
135+
return;
136+
}
137+
138+
const position = over.data.current.position;
139+
const activeIndex = active.data.current.index;
140+
const overIndex = over.data.current.index;
141+
142+
if (
143+
(activeIndex === overIndex &&
144+
(position === "left" || position === "right")) ||
145+
// Or the diff between active and over is 1
146+
(activeIndex - overIndex === 1 && position === "bottom") ||
147+
(overIndex - activeIndex === 1 && position === "top")
148+
) {
149+
return;
150+
}
151+
152+
const activeComponent = active.data.current.component;
153+
const overComponent = over.data.current.component;
154+
155+
if (activeComponent && overComponent) {
156+
const overRowItems =
157+
gridRows.find((row) =>
158+
row.some((item) => item.id === over.data.current?.component.id)
159+
) || [];
160+
161+
const overRowFirstItemIndex = components.findIndex(
162+
(component) => component.id === overRowItems[0].id
163+
);
164+
165+
const overRowLastItemIndex = components.findIndex(
166+
(component) => component.id === overRowItems[overRowItems.length - 1].id
167+
);
168+
169+
let activeRowItems =
170+
gridRows.find((row) =>
171+
row.some((item) => item.id === active.data.current?.component.id)
172+
) || [];
173+
174+
let draggingInSameRow = overRowItems === activeRowItems;
175+
176+
// Don´t update the spans if the component is being dragged in the same row
177+
activeRowItems = activeRowItems.filter(
178+
(item) => item.id !== activeComponent.id
179+
);
180+
let updatedOverItems = [];
181+
182+
if (position === "top" || position === "bottom") {
183+
updatedOverItems = updateColSpans([activeComponent], viewport);
184+
} else {
185+
updatedOverItems = updateColSpans(
186+
[...overRowItems, activeComponent],
187+
viewport
188+
);
189+
}
190+
191+
if (
192+
(!draggingInSameRow && (position === "left" || position === "right")) ||
193+
position === "top" ||
194+
position === "bottom"
195+
) {
196+
updatedOverItems.forEach((item) => {
197+
updateComponent(
198+
item.id,
199+
"properties.style.colSpan",
200+
`${item.span}`,
201+
false
202+
);
203+
});
204+
205+
const updatedActiveItems = updateColSpans(
206+
[...activeRowItems],
207+
viewport
208+
);
209+
210+
updatedActiveItems.forEach((item) => {
211+
updateComponent(
212+
item.id,
213+
"properties.style.colSpan",
214+
`${item.span}`,
215+
false
216+
);
217+
});
218+
}
219+
220+
const oldIndex = active.data.current.index;
221+
let newIndex =
222+
position === "left"
223+
? overIndex
224+
: activeIndex < overIndex
225+
? overIndex
226+
: overIndex + 1;
227+
228+
if (position === "top") {
229+
newIndex = activeIndex < overIndex ? overRowFirstItemIndex - 1 : overRowFirstItemIndex;
230+
}
231+
232+
if (position === "bottom") {
233+
newIndex = activeIndex < overIndex ? overRowLastItemIndex : overRowLastItemIndex + 1;
234+
}
235+
236+
moveComponent(oldIndex, newIndex);
237+
}
238+
setActiveId(null);
239+
};
240+
241+
if (components.length === 0) {
242+
return <EmptyState />;
243+
}
244+
245+
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
246+
selectComponent(null);
247+
};
248+
249+
const activeComponent = activeId
250+
? components.find((component) => component.id === activeId)
251+
: null;
252+
253+
return (
254+
<div
255+
className={`grid grid-cols-12 gap-4`}
256+
onClick={mode === "editor" ? handleClick : undefined}
257+
>
258+
<Form {...form}>
259+
<DndContext
260+
sensors={sensors}
261+
collisionDetection={closestCenter}
262+
onDragEnd={handleDragEnd}
263+
onDragStart={handleDragStart}
264+
>
265+
{components.map((component, index) => (
266+
<RowColumn
267+
key={component.id}
268+
component={component}
269+
index={index}
270+
form={form}
271+
/>
272+
))}
273+
<DragOverlay className="bg-white/80 shadow outline-1 outline-offset-6 outline-indigo-400 rounded-xs overflow-hidden">
274+
{activeComponent && (
275+
<RenderEditorComponent
276+
key={activeComponent.id}
277+
component={activeComponent}
278+
form={form}
279+
/>
280+
)}
281+
</DragOverlay>
282+
</DndContext>
283+
</Form>
284+
</div>
285+
);
286+
}

0 commit comments

Comments
 (0)