fix: keep mobile board widgets unchanged
This commit is contained in:
1 parent
e2732dce2c
commit
b5ffefe707
43 files changed
+1669
-181
No files matched your search
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
|
||||
import { getBoardLayouts, getCurrentLayout, getDesktopLayout } from "./context";
|
||||
|
||||
const createBoard = () =>
|
||||
({
|
||||
layouts: [
|
||||
{ id: "small", name: "Small", columnCount: 4, breakpoint: 0 },
|
||||
{ id: "medium", name: "Medium", columnCount: 8, breakpoint: 768 },
|
||||
{ id: "large", name: "Large", columnCount: 12, breakpoint: 1200 },
|
||||
],
|
||||
}) as RouterOutputs["board"]["getBoardByName"];
|
||||
|
||||
describe("board layout selection", () => {
|
||||
test("automatic mode always uses the desktop layout", () => {
|
||||
const board = createBoard();
|
||||
|
||||
expect(getDesktopLayout(board).id).toBe("large");
|
||||
expect(getCurrentLayout(board, true, 390)).toBe("large");
|
||||
});
|
||||
|
||||
test.each([
|
||||
[390, "small"],
|
||||
[768, "medium"],
|
||||
[1024, "medium"],
|
||||
[1440, "large"],
|
||||
])("legacy mode selects the matching responsive layout at %spx", (viewportWidth, expectedLayoutId) => {
|
||||
expect(getCurrentLayout(createBoard(), false, viewportWidth)).toBe(expectedLayoutId);
|
||||
});
|
||||
|
||||
test("legacy mode falls back to the desktop layout before the client viewport is known", () => {
|
||||
expect(getCurrentLayout(createBoard(), false, undefined)).toBe("large");
|
||||
});
|
||||
|
||||
test("rejects boards without a layout", () => {
|
||||
const board = { ...createBoard(), layouts: [] };
|
||||
|
||||
expect(() => getDesktopLayout(board)).toThrow("Board must have a layout");
|
||||
expect(() => getCurrentLayout(board, false, 390)).toThrow("Board must have a layout");
|
||||
});
|
||||
|
||||
test("keeps every saved layout available for reversible mutations", () => {
|
||||
expect(getBoardLayouts(createBoard())).toEqual(["small", "medium", "large"]);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { createContext, useContext, useEffect } from "react";
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { getActiveQueryCacheBoardId, setActiveQueryCacheBoardId } from "@homarr/api/query-cache";
|
||||
import { useSettings } from "@homarr/settings";
|
||||
|
||||
import { updateBoardName } from "./updater";
|
||||
|
||||
@@ -73,10 +74,6 @@ export const useOptionalBoard = () => {
|
||||
return context?.board ?? null;
|
||||
};
|
||||
|
||||
export const getCurrentLayout = (board: RouterOutputs["board"]["getBoardByName"]) => {
|
||||
return getDesktopLayout(board).id;
|
||||
};
|
||||
|
||||
export const getDesktopLayout = (board: RouterOutputs["board"]["getBoardByName"]) => {
|
||||
const layout = board.layouts
|
||||
.toSorted(
|
||||
@@ -91,9 +88,57 @@ export const getDesktopLayout = (board: RouterOutputs["board"]["getBoardByName"]
|
||||
return layout;
|
||||
};
|
||||
|
||||
export const useCurrentLayout = () => getCurrentLayout(useRequiredBoard());
|
||||
export const getCurrentLayout = (
|
||||
board: RouterOutputs["board"]["getBoardByName"],
|
||||
enableAutomaticMobileLayout: boolean,
|
||||
viewportWidth?: number,
|
||||
) => {
|
||||
if (enableAutomaticMobileLayout || viewportWidth === undefined) {
|
||||
return getDesktopLayout(board).id;
|
||||
}
|
||||
|
||||
export const getBoardLayouts = (board: RouterOutputs["board"]["getBoardByName"]) => [getCurrentLayout(board)];
|
||||
const sortedLayouts = board.layouts.toSorted((layoutA, layoutB) => layoutB.breakpoint - layoutA.breakpoint);
|
||||
const layout = sortedLayouts.find((candidate) => candidate.breakpoint <= viewportWidth) ?? sortedLayouts.at(-1);
|
||||
|
||||
if (!layout) {
|
||||
throw new Error("Board must have a layout");
|
||||
}
|
||||
|
||||
return layout.id;
|
||||
};
|
||||
|
||||
export const useCurrentLayout = () => {
|
||||
const board = useRequiredBoard();
|
||||
const { enableAutomaticMobileLayout } = useSettings();
|
||||
const [currentLayout, setCurrentLayout] = useState(() =>
|
||||
getCurrentLayout(
|
||||
board,
|
||||
enableAutomaticMobileLayout,
|
||||
!enableAutomaticMobileLayout && typeof window !== "undefined" ? window.innerWidth : undefined,
|
||||
),
|
||||
);
|
||||
|
||||
const updateCurrentLayout = useCallback(() => {
|
||||
setCurrentLayout(getCurrentLayout(board, enableAutomaticMobileLayout, window.innerWidth));
|
||||
}, [board, enableAutomaticMobileLayout]);
|
||||
|
||||
useEffect(() => {
|
||||
updateCurrentLayout();
|
||||
|
||||
if (enableAutomaticMobileLayout) return;
|
||||
|
||||
window.addEventListener("resize", updateCurrentLayout);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", updateCurrentLayout);
|
||||
};
|
||||
}, [enableAutomaticMobileLayout, updateCurrentLayout]);
|
||||
|
||||
return currentLayout;
|
||||
};
|
||||
|
||||
export const getBoardLayouts = (board: RouterOutputs["board"]["getBoardByName"]) =>
|
||||
board.layouts.map((layout) => layout.id);
|
||||
|
||||
export const useLayouts = () => {
|
||||
const board = useRequiredBoard();
|
||||
|
||||
Reference in new issue
Block a user