Files
homarr/apps/nextjs/src/components/board/items/actions/duplicate-item.ts
T

89 lines
2.7 KiB
TypeScript

import { createId } from "@homarr/common";
import type { Board, EmptySection, ItemLayout, Section } from "~/app/[locale]/boards/_types";
import { getFirstEmptyPosition } from "./empty-position";
import { getSectionElements } from "./section-elements";
export interface DuplicateItemInput {
itemId: string;
}
export const duplicateItemCallback =
({ itemId }: DuplicateItemInput) =>
(previous: Board): Board => {
const itemToDuplicate = previous.items.find((item) => item.id === itemId);
if (!itemToDuplicate) return previous;
const clonedItem = structuredClone(itemToDuplicate);
return {
...previous,
items: previous.items.concat({
...clonedItem,
id: createId(),
layouts: clonedItem.layouts.map((layout) => ({
...layout,
...getNextPosition(previous, layout),
})),
}),
};
};
const getNextPosition = (board: Board, layout: ItemLayout): { xOffset: number; yOffset: number; sectionId: string } => {
const currentSection = board.sections.find((section) => section.id === layout.sectionId);
if (currentSection) {
const emptySectionPosition = getEmptySectionPosition(board, layout, currentSection);
if (emptySectionPosition) {
return {
...emptySectionPosition,
sectionId: currentSection.id,
};
}
}
const firstSection = board.sections
.filter((section): section is EmptySection => section.kind === "empty")
.sort((sectionA, sectionB) => sectionA.yOffset - sectionB.yOffset)
.at(0);
if (!firstSection) {
throw new Error("Your board is full. reason='no first section'");
}
const emptySectionPosition = getEmptySectionPosition(board, layout, firstSection);
if (!emptySectionPosition) {
throw new Error("Your board is full. reason='no empty positions'");
}
return {
...emptySectionPosition,
sectionId: firstSection.id,
};
};
const getEmptySectionPosition = (
board: Board,
layout: ItemLayout,
section: Section,
): { xOffset: number; yOffset: number } | undefined => {
const boardLayout = board.layouts.find((boardLayout) => boardLayout.id === layout.layoutId);
if (!boardLayout) return;
const sectionElements = getSectionElements(board, { sectionId: layout.sectionId, layoutId: layout.layoutId });
if (section.kind !== "dynamic") {
return getFirstEmptyPosition(sectionElements, boardLayout.columnCount, undefined, {
width: layout.width,
height: layout.height,
});
}
const sectionLayout = section.layouts.find((sectionLayout) => sectionLayout.layoutId === layout.layoutId);
if (!sectionLayout) return;
return getFirstEmptyPosition(sectionElements, sectionLayout.width, sectionLayout.height, {
width: layout.width,
height: layout.height,
});
};