From e097cffe596f26ed832f777aec515861633f0620 Mon Sep 17 00:00:00 2001 From: Meier Lukas Date: Wed, 22 May 2024 21:28:45 +0200 Subject: [PATCH] refactor: improve behaviour of edit mode when navigating away and back (#525) --- .../app/[locale]/boards/(content)/_context.tsx | 18 ++++++++++++++++-- .../boards/(content)/_header-actions.tsx | 8 +++----- apps/nextjs/src/components/board/editMode.ts | 3 --- .../board/sections/category/category-menu.tsx | 5 ++--- .../src/components/board/sections/content.tsx | 12 +++++------- .../board/sections/empty-section.tsx | 5 ++--- .../board/sections/gridstack/use-gridstack.ts | 6 ++---- 7 files changed, 30 insertions(+), 27 deletions(-) delete mode 100644 apps/nextjs/src/components/board/editMode.ts diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx index a5ab76c03..808759af1 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_context.tsx @@ -1,6 +1,6 @@ "use client"; -import type { PropsWithChildren } from "react"; +import type { Dispatch, PropsWithChildren, SetStateAction } from "react"; import { createContext, useCallback, useContext, useEffect, useState } from "react"; import { usePathname } from "next/navigation"; @@ -13,6 +13,8 @@ const BoardContext = createContext<{ board: RouterOutputs["board"]["getHomeBoard"]; isReady: boolean; markAsReady: (id: string) => void; + isEditMode: boolean; + setEditMode: Dispatch>; } | null>(null); export const BoardProvider = ({ @@ -24,11 +26,11 @@ export const BoardProvider = ({ const pathname = usePathname(); const utils = clientApi.useUtils(); const [readySections, setReadySections] = useState([]); + const [isEditMode, setEditMode] = useState(false); const { data } = clientApi.board.getBoardByName.useQuery( { name: initialBoard.name }, { initialData: initialBoard, - refetchOnMount: false, refetchOnWindowFocus: false, refetchOnReconnect: false, }, @@ -60,6 +62,8 @@ export const BoardProvider = ({ board: data, isReady: data.sections.length === readySections.length, markAsReady, + isEditMode, + setEditMode, }} > {children} @@ -102,3 +106,13 @@ export const useOptionalBoard = () => { return context?.board; }; + +export const useEditMode = () => { + const context = useContext(BoardContext); + + if (!context) { + throw new Error("Board is required"); + } + + return [context.isEditMode, context.setEditMode] as const; +}; diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index 79c019dad..9222b7d59 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -12,7 +12,6 @@ import { IconPlus, IconSettings, } from "@tabler/icons-react"; -import { useAtom, useAtomValue } from "jotai"; import { clientApi } from "@homarr/api/client"; import { useModalAction } from "@homarr/modals"; @@ -20,16 +19,15 @@ import { showErrorNotification, showSuccessNotification } from "@homarr/notifica import { useI18n, useScopedI18n } from "@homarr/translation/client"; import { revalidatePathActionAsync } from "~/app/revalidatePathAction"; -import { editModeAtom } from "~/components/board/editMode"; import { ItemSelectModal } from "~/components/board/items/item-select-modal"; import { useBoardPermissions } from "~/components/board/permissions/client"; import { useCategoryActions } from "~/components/board/sections/category/category-actions"; import { CategoryEditModal } from "~/components/board/sections/category/category-edit-modal"; import { HeaderButton } from "~/components/layout/header/button"; -import { useRequiredBoard } from "./_context"; +import { useEditMode, useRequiredBoard } from "./_context"; export const BoardContentHeaderActions = () => { - const isEditMode = useAtomValue(editModeAtom); + const [isEditMode] = useEditMode(); const board = useRequiredBoard(); const { hasChangeAccess } = useBoardPermissions(board); @@ -107,7 +105,7 @@ const AddMenu = () => { }; const EditModeMenu = () => { - const [isEditMode, setEditMode] = useAtom(editModeAtom); + const [isEditMode, setEditMode] = useEditMode(); const board = useRequiredBoard(); const utils = clientApi.useUtils(); const t = useScopedI18n("board.action.edit"); diff --git a/apps/nextjs/src/components/board/editMode.ts b/apps/nextjs/src/components/board/editMode.ts deleted file mode 100644 index 186b60a0f..000000000 --- a/apps/nextjs/src/components/board/editMode.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { atom } from "jotai"; - -export const editModeAtom = atom(false); diff --git a/apps/nextjs/src/components/board/sections/category/category-menu.tsx b/apps/nextjs/src/components/board/sections/category/category-menu.tsx index be67285ff..80e9cd96e 100644 --- a/apps/nextjs/src/components/board/sections/category/category-menu.tsx +++ b/apps/nextjs/src/components/board/sections/category/category-menu.tsx @@ -11,13 +11,12 @@ import { IconTransitionTop, IconTrash, } from "@tabler/icons-react"; -import { useAtomValue } from "jotai"; import { useScopedI18n } from "@homarr/translation/client"; import type { TablerIcon } from "@homarr/ui"; import type { CategorySection } from "~/app/[locale]/boards/_types"; -import { editModeAtom } from "../../editMode"; +import { useEditMode } from "~/app/[locale]/boards/(content)/_context"; import { useCategoryMenuActions } from "./category-menu-actions"; interface Props { @@ -56,7 +55,7 @@ export const CategoryMenu = ({ category }: Props) => { }; const useActions = (category: CategorySection) => { - const isEditMode = useAtomValue(editModeAtom); + const [isEditMode] = useEditMode(); const editModeActions = useEditModeActions(category); const nonEditModeActions = useNonEditModeActions(category); diff --git a/apps/nextjs/src/components/board/sections/content.tsx b/apps/nextjs/src/components/board/sections/content.tsx index f978ced0f..cdd71266f 100644 --- a/apps/nextjs/src/components/board/sections/content.tsx +++ b/apps/nextjs/src/components/board/sections/content.tsx @@ -1,13 +1,12 @@ /* eslint-disable react/no-unknown-property */ // Ignored because of gridstack attributes -import { useMemo } from "react"; import type { RefObject } from "react"; +import { useMemo } from "react"; import { ActionIcon, Card, Menu } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; import { IconDotsVertical, IconLayoutKanban, IconPencil, IconTrash } from "@tabler/icons-react"; import combineClasses from "clsx"; -import { useAtomValue } from "jotai"; import { clientApi } from "@homarr/api/client"; import { useConfirmModal, useModalAction } from "@homarr/modals"; @@ -21,8 +20,7 @@ import { } from "@homarr/widgets"; import type { Item } from "~/app/[locale]/boards/_types"; -import { useRequiredBoard } from "~/app/[locale]/boards/(content)/_context"; -import { editModeAtom } from "../editMode"; +import { useEditMode, useRequiredBoard } from "~/app/[locale]/boards/(content)/_context"; import { useItemActions } from "../items/item-actions"; import type { UseGridstackRefs } from "./gridstack/use-gridstack"; import classes from "./item.module.css"; @@ -97,7 +95,7 @@ interface ItemContentProps { const BoardItemContent = ({ item, ...dimensions }: ItemContentProps) => { const board = useRequiredBoard(); - const editMode = useAtomValue(editModeAtom); + const [isEditMode] = useEditMode(); const serverData = useServerDataFor(item.id); const Comp = loadWidgetDynamic(item.kind); const options = reduceWidgetOptionsWithDefaultValues(item.kind, item.options); @@ -112,7 +110,7 @@ const BoardItemContent = ({ item, ...dimensions }: ItemContentProps) => { options={options as never} integrations={item.integrations} serverData={serverData?.data as never} - isEditMode={editMode} + isEditMode={isEditMode} boardId={board.id} itemId={item.id} {...dimensions} @@ -126,7 +124,7 @@ const ItemMenu = ({ offset, item }: { offset: number; item: Item }) => { const t = useI18n(); const { openModal } = useModalAction(WidgetEditModal); const { openConfirmModal } = useConfirmModal(); - const isEditMode = useAtomValue(editModeAtom); + const [isEditMode] = useEditMode(); const { updateItemOptions, updateItemAdvancedOptions, updateItemIntegrations, removeItem } = useItemActions(); const { data: integrationData, isPending } = clientApi.integration.all.useQuery(); const currentDefinition = useMemo(() => widgetImports[item.kind].definition, [item.kind]); diff --git a/apps/nextjs/src/components/board/sections/empty-section.tsx b/apps/nextjs/src/components/board/sections/empty-section.tsx index 0f011c153..9dc2a844c 100644 --- a/apps/nextjs/src/components/board/sections/empty-section.tsx +++ b/apps/nextjs/src/components/board/sections/empty-section.tsx @@ -1,8 +1,7 @@ import type { RefObject } from "react"; -import { useAtomValue } from "jotai"; import type { EmptySection } from "~/app/[locale]/boards/_types"; -import { editModeAtom } from "../editMode"; +import { useEditMode } from "~/app/[locale]/boards/(content)/_context"; import { SectionContent } from "./content"; import { useGridstack } from "./gridstack/use-gridstack"; @@ -15,7 +14,7 @@ const defaultClasses = "grid-stack grid-stack-empty min-row"; export const BoardEmptySection = ({ section, mainRef }: Props) => { const { refs } = useGridstack({ section, mainRef }); - const isEditMode = useAtomValue(editModeAtom); + const [isEditMode] = useEditMode(); return (
{ - const isEditMode = useAtomValue(editModeAtom); + const [isEditMode] = useEditMode(); const markAsReady = useMarkSectionAsReady(); const { moveAndResizeItem, moveItemToSection } = useItemActions(); // define reference for wrapper - is used to calculate the width of the wrapper