feat: rebuild board grid experience
This commit is contained in:
1 parent
5dc4dbeaef
commit
0110a2ef63
122 files changed
+9582
-4063
No files matched your search
@@ -1,33 +1,19 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import type { GridItemHTMLElement } from "@homarr/gridstack";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useSession } from "@homarr/auth/client";
|
||||
import { useEditMode } from "@homarr/boards/edit-mode";
|
||||
|
||||
import type { DynamicSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
|
||||
import type { ContainerSectionItem, SectionItem } from "~/app/[locale]/boards/_types";
|
||||
import { BoardItemContent } from "../items/item-content";
|
||||
import { WidgetHoverOverlay } from "../items/widget-hover-overlay";
|
||||
import { BoardDynamicSection } from "./dynamic-section";
|
||||
import { GridStackItem } from "./gridstack/gridstack-item";
|
||||
import { BoardContainerSection } from "./container-section";
|
||||
import { FixedGridItem } from "./grid/fixed-grid-item";
|
||||
import { useSectionContext } from "./section-context";
|
||||
import { useSectionItems } from "./use-section-items";
|
||||
|
||||
export const SectionContent = () => {
|
||||
const { innerSections, items, refs } = useSectionContext();
|
||||
const { data: session } = useSession();
|
||||
const [isEditMode] = useEditMode();
|
||||
const { data: integrations } = clientApi.integration.all.useQuery(undefined, {
|
||||
enabled: Boolean(session) && isEditMode,
|
||||
});
|
||||
const { innerSections, integrations, items } = useSectionContext();
|
||||
|
||||
/**
|
||||
* IMPORTANT: THE ORDER OF THE BELOW ITEMS HAS TO MATCH THE ORDER OF
|
||||
* THE ITEMS RENDERED WITH GRIDSTACK, OTHERWISE THE ITEMS WILL BE MIXED UP
|
||||
* @see https://github.com/homarr-labs/homarr/pull/1770
|
||||
*/
|
||||
/** Keep portal content order deterministic across static and edit renderers. */
|
||||
const sortedItems = useMemo(() => {
|
||||
return [...items, ...innerSections].toSorted((itemA, itemB) => {
|
||||
if (itemA.yOffset === itemB.yOffset) {
|
||||
@@ -41,57 +27,39 @@ export const SectionContent = () => {
|
||||
return (
|
||||
<>
|
||||
{sortedItems.map((item) => (
|
||||
<Item key={item.id} item={item} innerRef={refs.items.current[item.id]} integrations={integrations} />
|
||||
<SectionContentItem key={item.id} item={item} integrations={integrations} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ItemProps {
|
||||
item: DynamicSectionItem | SectionItem;
|
||||
innerRef: React.RefObject<GridItemHTMLElement | null> | undefined;
|
||||
item: ContainerSectionItem | SectionItem;
|
||||
integrations: RouterOutputs["integration"]["all"] | undefined;
|
||||
}
|
||||
|
||||
const Item = ({ item, innerRef, integrations }: ItemProps) => {
|
||||
export const SectionContentItem = ({ item, integrations }: ItemProps) => {
|
||||
const minWidth = useMinSize(item, "x");
|
||||
const minHeight = useMinSize(item, "y");
|
||||
return (
|
||||
<GridStackItem
|
||||
key={item.id}
|
||||
innerRef={innerRef}
|
||||
width={item.width}
|
||||
height={item.height}
|
||||
xOffset={item.xOffset}
|
||||
yOffset={item.yOffset}
|
||||
kind={item.kind}
|
||||
id={item.id}
|
||||
type={item.type}
|
||||
minWidth={minWidth}
|
||||
minHeight={minHeight}
|
||||
>
|
||||
<FixedGridItem key={item.id} item={item} minWidth={minWidth} minHeight={minHeight}>
|
||||
{item.type === "item" && item.kind !== "app" && <WidgetHoverOverlay item={item} integrations={integrations} />}
|
||||
{item.type === "item" ? <BoardItemContent item={item} /> : <BoardDynamicSection section={item} />}
|
||||
</GridStackItem>
|
||||
{item.type === "item" ? <BoardItemContent item={item} /> : <BoardContainerSection section={item} />}
|
||||
</FixedGridItem>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the min width / height of a section by taking the maximum of
|
||||
* the sum of the offset and size of all items and dynamic sections inside.
|
||||
* @param direction either "x" or "y"
|
||||
* @param items items of the section
|
||||
* @param sections sections of the board to look for dynamic sections
|
||||
* @param parentSectionId the id of the section we want to calculate the min size for
|
||||
* @returns the min size
|
||||
* Calculates a container's minimum width or height from its direct content.
|
||||
*/
|
||||
const useMinSize = (item: DynamicSectionItem | SectionItem, direction: "x" | "y") => {
|
||||
export const useMinSize = (item: ContainerSectionItem | SectionItem, direction: "x" | "y") => {
|
||||
const { items, innerSections } = useSectionItems(item.id);
|
||||
if (item.type === "item") return undefined;
|
||||
|
||||
const size = direction === "x" ? "width" : "height";
|
||||
return Math.max(
|
||||
...items.map((item) => item[`${direction}Offset`] + item[size]),
|
||||
...innerSections.map((item) => item[`${direction}Offset`] + item[size]),
|
||||
1,
|
||||
...items.map((childItem) => childItem[`${direction}Offset`] + childItem[size]),
|
||||
...innerSections.map((childSection) => childSection[`${direction}Offset`] + childSection[size]),
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user