feat: add import for config files from oldmarr (#1019)

* wip: add oldmarr config import

* wip: add support for wrong amount of categories / sections with autofix, color mapping, position adjustments of wrappers

* fix: lockfile broken

* feat: add support for form data trpc requests

* wip: improve file upload

* refactor: restructure import, add import configuration

* wip: add configurations for import to modal

* refactor: move oldmarr import to old-import package

* fix: column count not respects screen size for board

* feat: add beta badge for oldmarr config import

* chore: address pull request feedback

* fix: format issues

* fix: inconsistent versions

* fix: deepsource issues

* fix: revert {} to Record<string, never> convertion to prevent typecheck issue

* fix: inconsistent zod version

* fix: format issue

* chore: address pull request feedback

* fix: wrong import

* fix: broken lock file

* fix: inconsistent versions

* fix: format issues
This commit is contained in:
Meier Lukas
2024-09-07 18:13:24 +02:00
committed by GitHub
parent fc1bff2110
commit 5404cebf5b
65 changed files with 2132 additions and 34 deletions

View File

@@ -5,7 +5,15 @@ import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
import { createWSClient, loggerLink, unstable_httpBatchStreamLink, wsLink } from "@trpc/client";
import {
createWSClient,
httpLink,
isNonJsonSerializable,
loggerLink,
splitLink,
unstable_httpBatchStreamLink,
wsLink,
} from "@trpc/client";
import superjson from "superjson";
import type { AppRouter } from "@homarr/api";
@@ -34,18 +42,29 @@ export function TRPCReactProvider(props: PropsWithChildren) {
enabled: (opts) =>
process.env.NODE_ENV === "development" || (opts.direction === "down" && opts.result instanceof Error),
}),
(args) => {
return ({ op, next }) => {
console.log("op", op.type, op.input, op.path, op.id);
if (op.type === "subscription") {
const link = wsLink<AppRouter>({
client: wsClient,
transformer: superjson,
});
return link(args)({ op, next });
}
return unstable_httpBatchStreamLink({
splitLink({
condition: ({ type }) => type === "subscription",
true: wsLink<AppRouter>({
client: wsClient,
transformer: superjson,
}),
false: splitLink({
condition: ({ input }) => isNonJsonSerializable(input),
true: httpLink({
/**
* We don't want to transform the data here as we want to use form data
*/
transformer: {
serialize(object: unknown) {
return object;
},
deserialize(data: unknown) {
return data;
},
},
url: `${getBaseUrl()}/api/trpc`,
}),
false: unstable_httpBatchStreamLink({
transformer: superjson,
url: `${getBaseUrl()}/api/trpc`,
headers() {
@@ -53,9 +72,9 @@ export function TRPCReactProvider(props: PropsWithChildren) {
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
})(args)({ op, next });
};
},
}),
}),
}),
],
});
});

View File

@@ -1,15 +1,17 @@
"use client";
import { useCallback } from "react";
import { IconCategoryPlus } from "@tabler/icons-react";
import { Affix, Button, Group, Menu } from "@mantine/core";
import { IconCategoryPlus, IconChevronDown, IconFileImport } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { useModalAction } from "@homarr/modals";
import { useI18n } from "@homarr/translation/client";
import { BetaBadge } from "@homarr/ui";
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
import { AddBoardModal } from "~/components/manage/boards/add-board-modal";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
import { ImportBoardModal } from "~/components/manage/boards/import-board-modal";
interface CreateBoardButtonProps {
boardNames: string[];
@@ -17,7 +19,8 @@ interface CreateBoardButtonProps {
export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
const t = useI18n();
const { openModal } = useModalAction(AddBoardModal);
const { openModal: openAddModal } = useModalAction(AddBoardModal);
const { openModal: openImportModal } = useModalAction(ImportBoardModal);
const { mutateAsync, isPending } = clientApi.board.createBoard.useMutation({
onSettled: async () => {
@@ -25,8 +28,8 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
},
});
const onClick = useCallback(() => {
openModal({
const onCreateClick = useCallback(() => {
openAddModal({
onSuccess: async (values) => {
await mutateAsync({
name: values.name,
@@ -36,11 +39,41 @@ export const CreateBoardButton = ({ boardNames }: CreateBoardButtonProps) => {
},
boardNames,
});
}, [mutateAsync, boardNames, openModal]);
}, [mutateAsync, boardNames, openAddModal]);
const onImportClick = useCallback(() => {
openImportModal({ boardNames });
}, [openImportModal, boardNames]);
const buttonGroupContent = (
<>
<Button leftSection={<IconCategoryPlus size="1rem" />} onClick={onCreateClick} loading={isPending}>
{t("management.page.board.action.new.label")}
</Button>
<Menu position="bottom-end">
<Menu.Target>
<Button px="xs" ms={1}>
<IconChevronDown size="1rem" />
</Button>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item onClick={onImportClick} leftSection={<IconFileImport size="1rem" />}>
<Group>
{t("board.action.oldImport.label")}
<BetaBadge size="xs" />
</Group>
</Menu.Item>
</Menu.Dropdown>
</Menu>
</>
);
return (
<MobileAffixButton leftSection={<IconCategoryPlus size="1rem" />} onClick={onClick} loading={isPending}>
{t("management.page.board.action.new.label")}
</MobileAffixButton>
<>
<Button.Group visibleFrom="md">{buttonGroupContent}</Button.Group>
<Affix hiddenFrom="md" position={{ bottom: 20, right: 20 }}>
<Button.Group>{buttonGroupContent}</Button.Group>
</Affix>
</>
);
};