feat: modernize dashboard widgets
This commit is contained in:
1 parent
1a5e083fd0
commit
6f2cd7fc2b
270 files changed
+10189
-3544
No files matched your search
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getBoardSaveScopeId, shouldRefetchAfterSaveError } from "./updater";
|
||||
|
||||
describe("board persistence coordination", () => {
|
||||
it("uses one mutation scope for every save of the same board", () => {
|
||||
expect(getBoardSaveScopeId("board-1")).toBe(getBoardSaveScopeId("board-1"));
|
||||
expect(getBoardSaveScopeId("board-1")).not.toBe(getBoardSaveScopeId("board-2"));
|
||||
});
|
||||
|
||||
it("only refetches after a failed save when no newer local board exists", () => {
|
||||
const failedBoard = { id: "board-1", items: ["first"] };
|
||||
const newerBoard = { id: "board-1", items: ["first", "second"] };
|
||||
|
||||
expect(shouldRefetchAfterSaveError(failedBoard, failedBoard)).toBe(true);
|
||||
expect(shouldRefetchAfterSaveError(newerBoard, failedBoard)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -5,13 +5,19 @@ import { useCallback } from "react";
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
|
||||
type Board = RouterOutputs["board"]["getHomeBoard"];
|
||||
type UpdateCallback = (previous: Board) => Board;
|
||||
|
||||
let boardName: string | null = null;
|
||||
|
||||
export const updateBoardName = (name: string | null) => {
|
||||
boardName = name;
|
||||
};
|
||||
|
||||
type UpdateCallback = (prev: RouterOutputs["board"]["getHomeBoard"]) => RouterOutputs["board"]["getHomeBoard"];
|
||||
export const getBoardSaveScopeId = (boardId: string) => `board-save:${boardId}`;
|
||||
|
||||
export const shouldRefetchAfterSaveError = <T>(currentBoard: T | undefined, failedBoard: T) =>
|
||||
currentBoard === failedBoard;
|
||||
|
||||
export const useUpdateBoard = () => {
|
||||
const utils = clientApi.useUtils();
|
||||
@@ -21,9 +27,13 @@ export const useUpdateBoard = () => {
|
||||
if (!boardName) {
|
||||
throw new Error("Board name is not set");
|
||||
}
|
||||
utils.board.getBoardByName.setData({ name: boardName }, (previous) =>
|
||||
previous ? updaterWithoutUndefined(previous) : previous,
|
||||
);
|
||||
let updatedBoard: Board | undefined;
|
||||
utils.board.getBoardByName.setData({ name: boardName }, (previous) => {
|
||||
if (!previous) return previous;
|
||||
updatedBoard = updaterWithoutUndefined(previous);
|
||||
return updatedBoard;
|
||||
});
|
||||
return updatedBoard;
|
||||
},
|
||||
[utils],
|
||||
);
|
||||
@@ -32,3 +42,37 @@ export const useUpdateBoard = () => {
|
||||
updateBoard,
|
||||
};
|
||||
};
|
||||
|
||||
interface PersistBoardOptions {
|
||||
onError?: (error: unknown) => void;
|
||||
}
|
||||
|
||||
export const usePersistBoard = ({ id, name }: Pick<Board, "id" | "name">) => {
|
||||
const utils = clientApi.useUtils();
|
||||
const { updateBoard } = useUpdateBoard();
|
||||
const { mutateAsync: saveBoardAsync } = clientApi.board.saveBoard.useMutation({
|
||||
scope: { id: getBoardSaveScopeId(id) },
|
||||
});
|
||||
|
||||
const updateAndPersistBoard = useCallback(
|
||||
(updater: UpdateCallback, options?: PersistBoardOptions) => {
|
||||
const updatedBoard = updateBoard(updater);
|
||||
if (!updatedBoard) return undefined;
|
||||
const persistedBoard = utils.board.getBoardByName.getData({ name });
|
||||
if (!persistedBoard) return undefined;
|
||||
|
||||
void saveBoardAsync(persistedBoard).catch((error: unknown) => {
|
||||
const currentBoard = utils.board.getBoardByName.getData({ name });
|
||||
if (shouldRefetchAfterSaveError(currentBoard, persistedBoard)) {
|
||||
void utils.board.getBoardByName.invalidate({ name });
|
||||
}
|
||||
options?.onError?.(error);
|
||||
});
|
||||
|
||||
return persistedBoard;
|
||||
},
|
||||
[name, saveBoardAsync, updateBoard, utils],
|
||||
);
|
||||
|
||||
return { updateAndPersistBoard };
|
||||
};
|
||||
Reference in new issue
Block a user