fix: guard recursive section rendering
This commit is contained in:
1 parent
a9fc3c6faa
commit
bc41125b7f
4 files changed
+33
-4
No files matched your search
@@ -6,6 +6,7 @@ import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
|
||||
import type { DynamicSectionItem } from "~/app/[locale]/boards/_types";
|
||||
import { BoardDynamicSectionMenu } from "./dynamic/dynamic-menu";
|
||||
import { GridStack } from "./gridstack/gridstack";
|
||||
import { useSectionContext } from "./section-context";
|
||||
import classes from "./item.module.css";
|
||||
|
||||
interface Props {
|
||||
@@ -15,6 +16,7 @@ interface Props {
|
||||
export const BoardDynamicSection = ({ section }: Props) => {
|
||||
const board = useRequiredBoard();
|
||||
const currentLayoutId = useCurrentLayout();
|
||||
const { ancestorSectionIds } = useSectionContext();
|
||||
const options = section.options;
|
||||
|
||||
return (
|
||||
@@ -53,7 +55,12 @@ export const BoardDynamicSection = ({ section }: Props) => {
|
||||
</Badge>
|
||||
)}
|
||||
{/* Use unique key by layout to reinitialize gridstack */}
|
||||
<GridStack key={`${currentLayoutId}-${section.id}`} section={section} className="min-row" />
|
||||
<GridStack
|
||||
key={`${currentLayoutId}-${section.id}`}
|
||||
section={section}
|
||||
ancestorSectionIds={ancestorSectionIds}
|
||||
className="min-row"
|
||||
/>
|
||||
</Card>
|
||||
<BoardDynamicSectionMenu section={section} />
|
||||
</Box>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { filterRecursiveInnerSections } from "./gridstack";
|
||||
|
||||
describe("filterRecursiveInnerSections", () => {
|
||||
test("removes sections that would recursively render an ancestor", () => {
|
||||
const innerSections = [{ id: "safe" }, { id: "parent" }, { id: "root" }];
|
||||
|
||||
expect(filterRecursiveInnerSections(innerSections, new Set(["root", "parent"]))).toEqual([{ id: "safe" }]);
|
||||
});
|
||||
});
|
||||
@@ -12,16 +12,21 @@ import { useGridstack } from "./use-gridstack";
|
||||
|
||||
interface Props extends BoxProps {
|
||||
section: Exclude<Section, { kind: "dynamic" }> | DynamicSectionItem;
|
||||
ancestorSectionIds?: ReadonlySet<string>;
|
||||
}
|
||||
|
||||
export const GridStack = ({ section, ...props }: Props) => {
|
||||
export const GridStack = ({ section, ancestorSectionIds = new Set(), ...props }: Props) => {
|
||||
const { items, innerSections } = useSectionItems(section.id);
|
||||
const itemIds = [...items, ...innerSections].map((item) => item.id);
|
||||
const nextAncestorSectionIds = new Set(ancestorSectionIds).add(section.id);
|
||||
const safeInnerSections = filterRecursiveInnerSections(innerSections, nextAncestorSectionIds);
|
||||
const itemIds = [...items, ...safeInnerSections].map((item) => item.id);
|
||||
|
||||
const { refs } = useGridstack(section, itemIds);
|
||||
|
||||
return (
|
||||
<SectionProvider value={{ section, items, innerSections, refs }}>
|
||||
<SectionProvider
|
||||
value={{ section, ancestorSectionIds: nextAncestorSectionIds, items, innerSections: safeInnerSections, refs }}
|
||||
>
|
||||
<Box
|
||||
{...props}
|
||||
data-kind={section.kind}
|
||||
@@ -34,3 +39,8 @@ export const GridStack = ({ section, ...props }: Props) => {
|
||||
</SectionProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const filterRecursiveInnerSections = <T extends { id: string }>(
|
||||
innerSections: T[],
|
||||
ancestorSectionIds: ReadonlySet<string>,
|
||||
) => innerSections.filter((innerSection) => !ancestorSectionIds.has(innerSection.id));
|
||||
@@ -5,6 +5,7 @@ import type { UseGridstackRefs } from "./gridstack/use-gridstack";
|
||||
|
||||
interface SectionContextProps {
|
||||
section: Exclude<Section, { kind: "dynamic" }> | DynamicSectionItem;
|
||||
ancestorSectionIds: ReadonlySet<string>;
|
||||
innerSections: DynamicSectionItem[];
|
||||
items: SectionItem[];
|
||||
refs: UseGridstackRefs;
|
||||
|
||||
Reference in new issue
Block a user