fix: stabilize responsive layout editing
This commit is contained in:
1 parent
1877b4f9a4
commit
a9fc3c6faa
14 files changed
+168
-60
No files matched your search
@@ -4,6 +4,10 @@
|
||||
background: color-mix(in srgb, var(--mantine-color-body) 82%, var(--mantine-primary-color-light));
|
||||
padding: var(--mantine-spacing-xs);
|
||||
min-height: 7rem;
|
||||
max-height: min(24rem, 48dvh);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.section {
|
||||
|
||||
@@ -20,6 +20,18 @@ describe("layout representative widths", () => {
|
||||
});
|
||||
|
||||
describe("projectBoardLayout", () => {
|
||||
test("returns exact saved positions without recursively re-projecting the active layout", () => {
|
||||
const rootSection = new EmptySectionMockBuilder({ id: "root" }).build();
|
||||
const dynamicSection = new DynamicSectionMockBuilder({ id: "dynamic" })
|
||||
.addLayout({ layoutId: mobile.id, parentSectionId: rootSection.id, width: 3, height: 2 })
|
||||
.build();
|
||||
const board = new BoardMockBuilder().addLayout(mobile).addSections([rootSection, dynamicSection]).build();
|
||||
|
||||
expect(projectBoardLayout(board, mobile, mobile)).toEqual([
|
||||
expect.objectContaining({ id: dynamicSection.id, sectionId: rootSection.id, width: 3, height: 2 }),
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses production ordering and sizing for items and dynamic sections", () => {
|
||||
const rootSection = new EmptySectionMockBuilder({ id: "root" }).build();
|
||||
const dynamicSection = new DynamicSectionMockBuilder({ id: "dynamic" })
|
||||
|
||||
@@ -24,6 +24,10 @@ export const projectBoardLayout = (
|
||||
) => {
|
||||
const elements = getElementsForLayout(board, sourceLayout.id);
|
||||
|
||||
if (sourceLayout.id === targetLayout.id && sourceLayout.columnCount === targetLayout.columnCount) {
|
||||
return elements;
|
||||
}
|
||||
|
||||
return board.sections
|
||||
.filter((section) => section.kind !== "dynamic")
|
||||
.flatMap(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getCurrentLayout } from "@homarr/boards/context";
|
||||
|
||||
import type { Board } from "~/app/[locale]/boards/_types";
|
||||
|
||||
export interface MoveAndResizeItemInput {
|
||||
itemId: string;
|
||||
layoutId: string;
|
||||
xOffset: number;
|
||||
yOffset: number;
|
||||
width: number;
|
||||
@@ -11,10 +10,8 @@ export interface MoveAndResizeItemInput {
|
||||
}
|
||||
|
||||
export const moveAndResizeItemCallback =
|
||||
({ itemId, ...layoutInput }: MoveAndResizeItemInput) =>
|
||||
({ itemId, layoutId, ...layoutInput }: MoveAndResizeItemInput) =>
|
||||
(previous: Board): Board => {
|
||||
const currentLayout = getCurrentLayout(previous);
|
||||
|
||||
return {
|
||||
...previous,
|
||||
items: previous.items.map((item) =>
|
||||
@@ -23,7 +20,7 @@ export const moveAndResizeItemCallback =
|
||||
: {
|
||||
...item,
|
||||
layouts: item.layouts.map((layout) =>
|
||||
layout.layoutId !== currentLayout
|
||||
layout.layoutId !== layoutId
|
||||
? layout
|
||||
: {
|
||||
...layout,
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getCurrentLayout } from "@homarr/boards/context";
|
||||
|
||||
import type { Board } from "~/app/[locale]/boards/_types";
|
||||
|
||||
export interface MoveItemToSectionInput {
|
||||
itemId: string;
|
||||
layoutId: string;
|
||||
sectionId: string;
|
||||
xOffset: number;
|
||||
yOffset: number;
|
||||
@@ -12,10 +11,8 @@ export interface MoveItemToSectionInput {
|
||||
}
|
||||
|
||||
export const moveItemToSectionCallback =
|
||||
({ itemId, ...layoutInput }: MoveItemToSectionInput) =>
|
||||
({ itemId, layoutId, ...layoutInput }: MoveItemToSectionInput) =>
|
||||
(board: Board): Board => {
|
||||
const currentLayout = getCurrentLayout(board);
|
||||
|
||||
return {
|
||||
...board,
|
||||
items: board.items.map((item) =>
|
||||
@@ -24,7 +21,7 @@ export const moveItemToSectionCallback =
|
||||
: {
|
||||
...item,
|
||||
layouts: item.layouts.map((layout) =>
|
||||
layout.layoutId !== currentLayout
|
||||
layout.layoutId !== layoutId
|
||||
? layout
|
||||
: {
|
||||
...layout,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import * as boardContext from "@homarr/boards/context";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { moveAndResizeItemCallback } from "../move-and-resize-item";
|
||||
import { BoardMockBuilder } from "./mocks/board-mock";
|
||||
@@ -11,8 +9,6 @@ describe("moveItemToSectionCallback should move item in section", () => {
|
||||
// Arrange
|
||||
const itemToMove = "2";
|
||||
const layoutId = "1";
|
||||
const spy = vi.spyOn(boardContext, "getCurrentLayout");
|
||||
spy.mockReturnValue(layoutId);
|
||||
const newPosition = {
|
||||
xOffset: 20,
|
||||
yOffset: 30,
|
||||
@@ -25,7 +21,7 @@ describe("moveItemToSectionCallback should move item in section", () => {
|
||||
const board = new BoardMockBuilder().addItem(itemA).addItem(itemB).addItem(itemC).build();
|
||||
|
||||
// Act
|
||||
const updatedBoard = moveAndResizeItemCallback({ itemId: itemToMove, ...newPosition })(board);
|
||||
const updatedBoard = moveAndResizeItemCallback({ itemId: itemToMove, layoutId, ...newPosition })(board);
|
||||
|
||||
// Assert
|
||||
expect(updatedBoard.items).toHaveLength(3);
|
||||
@@ -43,8 +39,6 @@ describe("moveItemToSectionCallback should move item in section", () => {
|
||||
// Arrange
|
||||
const itemToMove = "2";
|
||||
const layoutId = "1";
|
||||
const spy = vi.spyOn(boardContext, "getCurrentLayout");
|
||||
spy.mockReturnValue(layoutId);
|
||||
const newPosition = {
|
||||
xOffset: 20,
|
||||
yOffset: 30,
|
||||
@@ -56,7 +50,7 @@ describe("moveItemToSectionCallback should move item in section", () => {
|
||||
const board = new BoardMockBuilder().addItem(itemA).addItem(itemC).build();
|
||||
|
||||
// Act
|
||||
const updatedBoard = moveAndResizeItemCallback({ itemId: itemToMove, ...newPosition })(board);
|
||||
const updatedBoard = moveAndResizeItemCallback({ itemId: itemToMove, layoutId, ...newPosition })(board);
|
||||
|
||||
// Assert
|
||||
expect(updatedBoard.items).toHaveLength(2);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import * as boardContext from "@homarr/boards/context";
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { moveItemToSectionCallback } from "../move-item-to-section";
|
||||
import { BoardMockBuilder } from "./mocks/board-mock";
|
||||
@@ -11,8 +9,6 @@ describe("moveItemToSectionCallback should move item to section", () => {
|
||||
// Arrange
|
||||
const itemToMove = "2";
|
||||
const layoutId = "1";
|
||||
const spy = vi.spyOn(boardContext, "getCurrentLayout");
|
||||
spy.mockReturnValue(layoutId);
|
||||
const newPosition = {
|
||||
sectionId: "3",
|
||||
xOffset: 20,
|
||||
@@ -26,7 +22,7 @@ describe("moveItemToSectionCallback should move item to section", () => {
|
||||
const board = new BoardMockBuilder().addItem(itemA).addItem(itemB).addItem(itemC).build();
|
||||
|
||||
// Act
|
||||
const updatedBoard = moveItemToSectionCallback({ itemId: itemToMove, ...newPosition })(board);
|
||||
const updatedBoard = moveItemToSectionCallback({ itemId: itemToMove, layoutId, ...newPosition })(board);
|
||||
|
||||
// Assert
|
||||
expect(updatedBoard.items).toHaveLength(3);
|
||||
@@ -44,8 +40,6 @@ describe("moveItemToSectionCallback should move item to section", () => {
|
||||
// Arrange
|
||||
const itemToMove = "2";
|
||||
const layoutId = "1";
|
||||
const spy = vi.spyOn(boardContext, "getCurrentLayout");
|
||||
spy.mockReturnValue(layoutId);
|
||||
const newPosition = {
|
||||
sectionId: "3",
|
||||
xOffset: 20,
|
||||
@@ -58,7 +52,7 @@ describe("moveItemToSectionCallback should move item to section", () => {
|
||||
const board = new BoardMockBuilder().addItem(itemA).addItem(itemC).build();
|
||||
|
||||
// Act
|
||||
const updatedBoard = moveItemToSectionCallback({ itemId: itemToMove, ...newPosition })(board);
|
||||
const updatedBoard = moveItemToSectionCallback({ itemId: itemToMove, layoutId, ...newPosition })(board);
|
||||
|
||||
// Assert
|
||||
expect(updatedBoard.items).toHaveLength(2);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useCurrentLayout } from "@homarr/boards/context";
|
||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||
|
||||
@@ -31,6 +32,7 @@ interface UpdateItemIntegrations {
|
||||
|
||||
export const useItemActions = () => {
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
const currentLayoutId = useCurrentLayout();
|
||||
|
||||
const createItem = useCallback(
|
||||
(input: CreateItemInput) => {
|
||||
@@ -81,17 +83,17 @@ export const useItemActions = () => {
|
||||
);
|
||||
|
||||
const moveAndResizeItem = useCallback(
|
||||
(input: MoveAndResizeItemInput) => {
|
||||
updateBoard(moveAndResizeItemCallback(input));
|
||||
(input: Omit<MoveAndResizeItemInput, "layoutId">) => {
|
||||
updateBoard(moveAndResizeItemCallback({ ...input, layoutId: currentLayoutId }));
|
||||
},
|
||||
[updateBoard],
|
||||
[currentLayoutId, updateBoard],
|
||||
);
|
||||
|
||||
const moveItemToSection = useCallback(
|
||||
(input: MoveItemToSectionInput) => {
|
||||
updateBoard(moveItemToSectionCallback(input));
|
||||
(input: Omit<MoveItemToSectionInput, "layoutId">) => {
|
||||
updateBoard(moveItemToSectionCallback({ ...input, layoutId: currentLayoutId }));
|
||||
},
|
||||
[updateBoard],
|
||||
[currentLayoutId, updateBoard],
|
||||
);
|
||||
|
||||
const removeItem = useCallback(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { fetchApi } from "@homarr/api/client";
|
||||
import { getCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
|
||||
import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
|
||||
import { createId } from "@homarr/common";
|
||||
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||
import { useSettings } from "@homarr/settings";
|
||||
@@ -18,6 +18,7 @@ export const useCategoryMenuActions = (category: CategorySection) => {
|
||||
const { addCategory, moveCategory, removeCategory, renameCategory } = useCategoryActions();
|
||||
const t = useI18n();
|
||||
const board = useRequiredBoard();
|
||||
const currentLayoutId = useCurrentLayout();
|
||||
|
||||
const createCategoryAtYOffset = useCallback(
|
||||
(position: number) => {
|
||||
@@ -104,7 +105,6 @@ export const useCategoryMenuActions = (category: CategorySection) => {
|
||||
|
||||
const settings = useSettings();
|
||||
const openAllInNewTabs = useCallback(async () => {
|
||||
const currentLayoutId = getCurrentLayout(board);
|
||||
const appIds = (
|
||||
await filterByItemKind(
|
||||
board.items.filter(
|
||||
@@ -132,7 +132,7 @@ export const useCategoryMenuActions = (category: CategorySection) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
}, [category, board, t, openConfirmModal, settings]);
|
||||
}, [category, board, currentLayoutId, t, openConfirmModal, settings]);
|
||||
|
||||
return {
|
||||
addCategoryAbove,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { BoardMockBuilder } from "~/components/board/items/actions/test/mocks/board-mock";
|
||||
import { DynamicSectionMockBuilder } from "~/components/board/items/actions/test/mocks/dynamic-section-mock";
|
||||
import { EmptySectionMockBuilder } from "~/components/board/items/actions/test/mocks/empty-section-mock";
|
||||
import { wouldCreateSectionCycle } from "./section-actions";
|
||||
|
||||
describe("wouldCreateSectionCycle", () => {
|
||||
test("rejects moving a dynamic section into itself or one of its descendants", () => {
|
||||
const layoutId = "layout";
|
||||
const rootSection = new EmptySectionMockBuilder({ id: "root" }).build();
|
||||
const parentSection = new DynamicSectionMockBuilder({ id: "parent" })
|
||||
.addLayout({ layoutId, parentSectionId: rootSection.id })
|
||||
.build();
|
||||
const childSection = new DynamicSectionMockBuilder({ id: "child" })
|
||||
.addLayout({ layoutId, parentSectionId: parentSection.id })
|
||||
.build();
|
||||
const board = new BoardMockBuilder().addSections([rootSection, parentSection, childSection]).build();
|
||||
|
||||
expect(
|
||||
wouldCreateSectionCycle(board, {
|
||||
innerSectionId: parentSection.id,
|
||||
sectionId: parentSection.id,
|
||||
layoutId,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
wouldCreateSectionCycle(board, {
|
||||
innerSectionId: parentSection.id,
|
||||
sectionId: childSection.id,
|
||||
layoutId,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
wouldCreateSectionCycle(board, {
|
||||
innerSectionId: childSection.id,
|
||||
sectionId: rootSection.id,
|
||||
layoutId,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { getCurrentLayout } from "@homarr/boards/context";
|
||||
import { useCurrentLayout } from "@homarr/boards/context";
|
||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||
|
||||
import type { Board } from "~/app/[locale]/boards/_types";
|
||||
|
||||
interface MoveAndResizeInnerSection {
|
||||
innerSectionId: string;
|
||||
xOffset: number;
|
||||
@@ -21,6 +23,7 @@ interface MoveInnerSectionToSection {
|
||||
|
||||
export const useSectionActions = () => {
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
const currentLayoutId = useCurrentLayout();
|
||||
|
||||
const moveAndResizeInnerSection = useCallback(
|
||||
({ innerSectionId, ...positionProps }: MoveAndResizeInnerSection) => {
|
||||
@@ -31,12 +34,10 @@ export const useSectionActions = () => {
|
||||
if (section.id !== innerSectionId) return section;
|
||||
if (section.kind !== "dynamic") return section;
|
||||
|
||||
const currentLayout = getCurrentLayout(previous);
|
||||
|
||||
return {
|
||||
...section,
|
||||
layouts: section.layouts.map((layout) => {
|
||||
if (layout.layoutId !== currentLayout) return layout;
|
||||
if (layout.layoutId !== currentLayoutId) return layout;
|
||||
return {
|
||||
...layout,
|
||||
...positionProps,
|
||||
@@ -46,12 +47,16 @@ export const useSectionActions = () => {
|
||||
}),
|
||||
}));
|
||||
},
|
||||
[updateBoard],
|
||||
[currentLayoutId, updateBoard],
|
||||
);
|
||||
|
||||
const moveInnerSectionToSection = useCallback(
|
||||
({ innerSectionId, sectionId, ...positionProps }: MoveInnerSectionToSection) => {
|
||||
updateBoard((previous) => {
|
||||
if (wouldCreateSectionCycle(previous, { innerSectionId, sectionId, layoutId: currentLayoutId })) {
|
||||
return previous;
|
||||
}
|
||||
|
||||
return {
|
||||
...previous,
|
||||
sections: previous.sections.map((section) => {
|
||||
@@ -59,12 +64,10 @@ export const useSectionActions = () => {
|
||||
if (section.id !== innerSectionId) return section;
|
||||
if (section.kind !== "dynamic") return section;
|
||||
|
||||
const currentLayout = getCurrentLayout(previous);
|
||||
|
||||
return {
|
||||
...section,
|
||||
layouts: section.layouts.map((layout) => {
|
||||
if (layout.layoutId !== currentLayout) return layout;
|
||||
if (layout.layoutId !== currentLayoutId) return layout;
|
||||
return {
|
||||
...layout,
|
||||
...positionProps,
|
||||
@@ -76,7 +79,7 @@ export const useSectionActions = () => {
|
||||
};
|
||||
});
|
||||
},
|
||||
[updateBoard],
|
||||
[currentLayoutId, updateBoard],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -84,3 +87,24 @@ export const useSectionActions = () => {
|
||||
moveInnerSectionToSection,
|
||||
};
|
||||
};
|
||||
|
||||
export const wouldCreateSectionCycle = (
|
||||
board: Board,
|
||||
{ innerSectionId, sectionId, layoutId }: { innerSectionId: string; sectionId: string; layoutId: string },
|
||||
) => {
|
||||
const visitedSectionIds = new Set<string>();
|
||||
let currentSectionId: string | null = sectionId;
|
||||
|
||||
while (currentSectionId) {
|
||||
if (currentSectionId === innerSectionId) return true;
|
||||
if (visitedSectionIds.has(currentSectionId)) return true;
|
||||
visitedSectionIds.add(currentSectionId);
|
||||
|
||||
const currentSection = board.sections.find((section) => section.id === currentSectionId);
|
||||
if (currentSection?.kind !== "dynamic") return false;
|
||||
|
||||
currentSectionId = currentSection.layouts.find((layout) => layout.layoutId === layoutId)?.parentSectionId ?? null;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -78,6 +78,26 @@ describe("Grid Algorithm", () => {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("stops projecting a corrupted recursive section graph", () => {
|
||||
const recursiveSection = algoItem({ id: "recursive", type: "section", width: 3 });
|
||||
const duplicateRecursiveLayout = algoItem({
|
||||
id: "recursive",
|
||||
type: "section",
|
||||
width: 3,
|
||||
sectionId: "recursive",
|
||||
});
|
||||
|
||||
const result = generateResponsiveGridFor({
|
||||
items: [recursiveSection, duplicateRecursiveLayout],
|
||||
width: 3,
|
||||
previousWidth: 12,
|
||||
sectionId: ROOT_SECTION_ID,
|
||||
});
|
||||
|
||||
expect(result.items).toContainEqual(expect.objectContaining({ id: "recursive", sectionId: ROOT_SECTION_ID }));
|
||||
expect(result.items).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
const algoItem = (item: Partial<GridAlgorithmItem>): GridAlgorithmItem => ({
|
||||
|
||||
@@ -71,13 +71,18 @@ export const useLayoutOverride = () => useContext(BoardContext)?.layoutOverrideI
|
||||
export const getCurrentLayout = (
|
||||
board: RouterOutputs["board"]["getBoardByName"],
|
||||
layoutOverrideId: string | null = null,
|
||||
) => getCurrentLayoutFromLayouts(board.layouts, layoutOverrideId);
|
||||
|
||||
const getCurrentLayoutFromLayouts = (
|
||||
layouts: RouterOutputs["board"]["getBoardByName"]["layouts"],
|
||||
layoutOverrideId: string | null,
|
||||
) => {
|
||||
if (layoutOverrideId && board.layouts.some((layout) => layout.id === layoutOverrideId)) return layoutOverrideId;
|
||||
if (layoutOverrideId && layouts.some((layout) => layout.id === layoutOverrideId)) return layoutOverrideId;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (typeof window === "undefined") return board.layouts.at(0)!.id;
|
||||
if (typeof window === "undefined") return layouts.at(0)!.id;
|
||||
|
||||
const sortedLayouts = board.layouts.toSorted((layoutA, layoutB) => layoutB.breakpoint - layoutA.breakpoint);
|
||||
const sortedLayouts = layouts.toSorted((layoutA, layoutB) => layoutB.breakpoint - layoutA.breakpoint);
|
||||
|
||||
// Fallback to smallest if none exists with breakpoint smaller than window width
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -88,10 +93,11 @@ export const useCurrentLayout = () => {
|
||||
const board = useRequiredBoard();
|
||||
const layoutOverrideId = useLayoutOverride();
|
||||
const [currentLayout, setCurrentLayout] = useState(getCurrentLayout(board, layoutOverrideId));
|
||||
const layouts = board.layouts;
|
||||
|
||||
const onResize = useCallback(() => {
|
||||
setCurrentLayout(getCurrentLayout(board, layoutOverrideId));
|
||||
}, [board, layoutOverrideId]);
|
||||
setCurrentLayout(getCurrentLayoutFromLayouts(layouts, layoutOverrideId));
|
||||
}, [layoutOverrideId, layouts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
@@ -25,7 +25,16 @@ export const generateResponsiveGridFor = ({
|
||||
previousWidth,
|
||||
width,
|
||||
sectionId,
|
||||
}: GridAlgorithmInput): GridAlgorithmOutput => {
|
||||
}: GridAlgorithmInput): GridAlgorithmOutput =>
|
||||
generateResponsiveGridForSection({ items, previousWidth, width, sectionId }, new Set());
|
||||
|
||||
const generateResponsiveGridForSection = (
|
||||
{ items, previousWidth, width, sectionId }: GridAlgorithmInput,
|
||||
ancestorSectionIds: ReadonlySet<string>,
|
||||
): GridAlgorithmOutput => {
|
||||
if (ancestorSectionIds.has(sectionId)) return { height: 0, items: [] };
|
||||
|
||||
const nextAncestorSectionIds = new Set(ancestorSectionIds).add(sectionId);
|
||||
const itemsOfCurrentSection = items
|
||||
.filter((item) => item.sectionId === sectionId)
|
||||
.toSorted((itemA, itemB) =>
|
||||
@@ -38,12 +47,15 @@ export const generateResponsiveGridFor = ({
|
||||
const newItems: GridAlgorithmItem[] = [];
|
||||
const dynamicSectionHeightMap = new Map<string, number>();
|
||||
for (const dynamicSection of normalizedItems.filter((item) => item.type === "section")) {
|
||||
const result = generateResponsiveGridFor({
|
||||
items,
|
||||
previousWidth: dynamicSection.previousWidth,
|
||||
width: dynamicSection.width,
|
||||
sectionId: dynamicSection.id,
|
||||
});
|
||||
const result = generateResponsiveGridForSection(
|
||||
{
|
||||
items,
|
||||
previousWidth: dynamicSection.previousWidth,
|
||||
width: dynamicSection.width,
|
||||
sectionId: dynamicSection.id,
|
||||
},
|
||||
nextAncestorSectionIds,
|
||||
);
|
||||
newItems.push(...result.items);
|
||||
dynamicSectionHeightMap.set(dynamicSection.id, result.height);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user