import {
closestCenter,
DndContext,
DragEndEvent,
DragOverEvent,
DragOverlay,
DragStartEvent,
KeyboardSensor,
PointerSensor,
useDroppable,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { t, Trans } from "@lingui/macro";
import { ArrowCounterClockwise, DotsSixVertical, Plus, TrashSimple } from "@phosphor-icons/react";
import { defaultMetadata } from "@reactive-resume/schema";
import { Button, Portal, Tooltip } from "@reactive-resume/ui";
import {
cn,
LayoutLocator,
moveItemInLayout,
parseLayoutLocator,
SortablePayload,
} from "@reactive-resume/utils";
import get from "lodash.get";
import { useState } from "react";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
type ColumnProps = {
id: string;
name: string;
items: string[];
};
const Column = ({ id, name, items }: ColumnProps) => {
const { setNodeRef } = useDroppable({ id });
return (
{name}
{items.map((section) => (
))}
);
};
type SortableSectionProps = {
id: string;
};
const SortableSection = ({ id }: SortableSectionProps) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
});
const style = {
transition,
opacity: isDragging ? 0.5 : 1,
transform: CSS.Translate.toString(transform),
};
return (
);
};
type SectionProps = {
id: string;
isDragging?: boolean;
};
const Section = ({ id, isDragging = false }: SectionProps) => {
const name = useResumeStore((state) =>
get(state.resume.data.sections, `${id}.name`, id),
) as string;
return (
);
};
export const LayoutSection = () => {
const setValue = useResumeStore((state) => state.setValue);
const layout = useResumeStore((state) => state.resume.data.metadata.layout);
const [activeId, setActiveId] = useState(null);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const onDragStart = ({ active }: DragStartEvent) => {
setActiveId(active.id as string);
};
const onDragCancel = () => {
setActiveId(null);
};
const onDragEvent = ({ active, over }: DragOverEvent | DragEndEvent) => {
if (!over || !active.data.current) return;
const currentPayload = active.data.current.sortable as SortablePayload | null;
const current = parseLayoutLocator(currentPayload);
if (active.id === over.id) return;
if (!over.data.current) {
const [page, column] = (over.id as string).split(".").map(Number);
const target = { page, column, section: 0 } as LayoutLocator;
const newLayout = moveItemInLayout(current, target, layout);
setValue("metadata.layout", newLayout);
return;
}
const targetPayload = over.data.current.sortable as SortablePayload | null;
const target = parseLayoutLocator(targetPayload);
const newLayout = moveItemInLayout(current, target, layout);
setValue("metadata.layout", newLayout);
};
const onDragEnd = (event: DragEndEvent) => {
onDragEvent(event);
setActiveId(null);
};
const onAddPage = () => {
const layoutCopy = JSON.parse(JSON.stringify(layout)) as string[][][];
layoutCopy.push([[], []]);
setValue("metadata.layout", layoutCopy);
};
const onRemovePage = (page: number) => {
const layoutCopy = JSON.parse(JSON.stringify(layout)) as string[][][];
layoutCopy[0][0].push(...layoutCopy[page][0]); // Main
layoutCopy[0][1].push(...layoutCopy[page][1]); // Sidebar
layoutCopy.splice(page, 1);
setValue("metadata.layout", layoutCopy);
};
const onResetLayout = () => {
const layoutCopy = JSON.parse(JSON.stringify(defaultMetadata.layout)) as string[][][];
// Loop through all pages and columns, and get any sections that start with "custom."
// These should be appended to the first page of the new layout.
const customSections: string[] = [];
layout.forEach((page) => {
page.forEach((column) => {
customSections.push(...column.filter((section) => section.startsWith("custom.")));
});
});
if (customSections.length > 0) layoutCopy[0][0].push(...customSections);
setValue("metadata.layout", layoutCopy);
};
return (
{/* Pages */}
{layout.map((page, pageIndex) => {
const mainIndex = `${pageIndex}.0`;
const sidebarIndex = `${pageIndex}.1`;
const main = page[0];
const sidebar = page[1];
return (
Page {pageIndex + 1}
{pageIndex !== 0 && (
)}
);
})}
{activeId && }
);
};