diff --git a/src/components/editor/action.ts b/src/components/editor/action.ts
index 6646653..1bdf46c 100644
--- a/src/components/editor/action.ts
+++ b/src/components/editor/action.ts
@@ -16,6 +16,17 @@ type EditorActionMap = {
containerId: string;
elementDetails: EditorElement;
};
+ MOVE_ELEMENT: {
+ elementId: string;
+ newParentId: string;
+ newIndex: number;
+ };
+ MOVE_ELEMENT_UP: {
+ elementId: string;
+ };
+ MOVE_ELEMENT_DOWN: {
+ elementId: string;
+ };
UPDATE_ELEMENT: {
elementDetails: EditorElement;
};
@@ -90,6 +101,63 @@ const traverseElements = (
}, []);
};
+const findElementAndParent = (
+ elements: EditorElement[],
+ elementId: string,
+): [EditorElement | null, EditorElement | null, number] => {
+ for (const el of elements) {
+ if (el.id === elementId) {
+ return [el, null, -1];
+ }
+ if (Array.isArray(el.content)) {
+ const index = el.content.findIndex((child) => child.id === elementId);
+ if (index !== -1) {
+ return [el.content[index], el, index];
+ }
+ const [foundEl, foundParent, foundIndex] = findElementAndParent(
+ el.content,
+ elementId,
+ );
+ if (foundEl) {
+ return [foundEl, foundParent, foundIndex];
+ }
+ }
+ }
+ return [null, null, -1];
+};
+
+const removeElementFromParent = (
+ elements: EditorElement[],
+ elementId: string,
+): [EditorElement[], EditorElement | null] => {
+ let removedElement: EditorElement | null = null;
+ const newElements = elements.map((el) => {
+ if (Array.isArray(el.content)) {
+ const index = el.content.findIndex((child) => child.id === elementId);
+ if (index !== -1) {
+ removedElement = el.content[index];
+ return {
+ ...el,
+ content: [
+ ...el.content.slice(0, index),
+ ...el.content.slice(index + 1),
+ ],
+ };
+ }
+ const [newContent, removed] = removeElementFromParent(
+ el.content,
+ elementId,
+ );
+ if (removed) {
+ removedElement = removed;
+ return { ...el, content: newContent };
+ }
+ }
+ return el;
+ }) as EditorElement[];
+ return [newElements, removedElement];
+};
+
/**
* Action Handlers
*/
@@ -120,6 +188,79 @@ const actionHandlers: {
});
},
+ MOVE_ELEMENT: (editor, payload) => {
+ const { elementId, newParentId, newIndex } = payload;
+
+ const [elements, removedElement] = removeElementFromParent(
+ editor.state.elements,
+ elementId,
+ );
+ if (!removedElement) return editor;
+
+ const insertElement = (els: EditorElement[]): EditorElement[] => {
+ return els.map((el) => {
+ if (el.id === newParentId && Array.isArray(el.content)) {
+ const newContent = [...el.content];
+ newContent.splice(newIndex, 0, removedElement);
+ return { ...el, content: newContent };
+ }
+ if (Array.isArray(el.content)) {
+ return { ...el, content: insertElement(el.content) };
+ }
+ return el;
+ }) as EditorElement[];
+ };
+
+ const newElements = insertElement(elements);
+
+ return updateEditorHistory(editor, {
+ ...editor.state,
+ elements: newElements,
+ });
+ },
+
+ MOVE_ELEMENT_UP: (editor, payload) => {
+ const { elementId } = payload;
+ const [element, parent, currentIndex] = findElementAndParent(
+ editor.state.elements,
+ elementId,
+ );
+ if (
+ !element ||
+ !parent ||
+ !Array.isArray(parent.content) ||
+ currentIndex <= 0
+ )
+ return editor;
+
+ return actionHandlers.MOVE_ELEMENT(editor, {
+ elementId,
+ newParentId: parent.id,
+ newIndex: currentIndex - 1,
+ });
+ },
+
+ MOVE_ELEMENT_DOWN: (editor, payload) => {
+ const { elementId } = payload;
+ const [element, parent, currentIndex] = findElementAndParent(
+ editor.state.elements,
+ elementId,
+ );
+ if (
+ !element ||
+ !parent ||
+ !Array.isArray(parent.content) ||
+ currentIndex === parent.content.length - 1
+ )
+ return editor;
+
+ return actionHandlers.MOVE_ELEMENT(editor, {
+ elementId,
+ newParentId: parent.id,
+ newIndex: currentIndex + 1,
+ });
+ },
+
UPDATE_ELEMENT: (editor, payload) => {
const newElements = traverseElements(editor.state.elements, (element) => {
if (element.id === payload.elementDetails.id) {
diff --git a/src/components/editor/elements/element-helper.tsx b/src/components/editor/elements/element-helper.tsx
index 529e893..1888637 100644
--- a/src/components/editor/elements/element-helper.tsx
+++ b/src/components/editor/elements/element-helper.tsx
@@ -7,15 +7,14 @@ import {
GripVerticalIcon,
Trash2Icon,
} from "lucide-react";
+import { useRef } from "react";
import { createPortal } from "react-dom";
+import { useResizeObserver } from "usehooks-ts";
import { useEditor } from "~/components/editor/provider";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { cn } from "~/lib/utils";
-import { useRef } from "react";
-import { useResizeObserver } from "usehooks-ts";
-
export default function ElementHelper() {
const { editor, dispatch } = useEditor();
const element = editor.state.selectedElement;
@@ -27,6 +26,26 @@ export default function ElementHelper() {
box: "border-box",
});
+ const handleMoveUp = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ dispatch({
+ type: "MOVE_ELEMENT_UP",
+ payload: {
+ elementId: element.id,
+ },
+ });
+ };
+
+ const handleMoveDown = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ dispatch({
+ type: "MOVE_ELEMENT_DOWN",
+ payload: {
+ elementId: element.id,
+ },
+ });
+ };
+
const handleDeleteElement = () => {
dispatch({
type: "DELETE_ELEMENT",
@@ -60,10 +79,10 @@ export default function ElementHelper() {
-
+
-
+