Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc087e6e60 | ||
|
|
d347d4455b | ||
|
|
10572ca962 | ||
|
|
d350e48998 | ||
|
|
2c48b18968 | ||
|
|
069ed0a267 | ||
|
|
a3e53ec291 | ||
|
|
38a1c9ca83 | ||
|
|
b40deba9ff | ||
|
|
d5d59dd256 | ||
|
|
3453482ed4 | ||
|
|
d2c1dc3cc1 | ||
|
|
59bcbc8b4d | ||
|
|
b2c46d33d6 | ||
|
|
ca01a31fe5 | ||
|
|
deb5e9d960 | ||
|
|
16f12d98e6 | ||
|
|
10bdd74b2b | ||
|
|
a91955a35b | ||
|
|
87deba417f | ||
|
|
220f68ee04 | ||
|
|
d6fc46df3b | ||
|
|
7a8c50fa4c | ||
|
|
59eecaf842 | ||
|
|
879a77ff2a | ||
|
|
b53bcb77e9 | ||
|
|
3062ddafbe | ||
|
|
7f782220a1 | ||
|
|
aa2ec5d221 | ||
|
|
e6db47aaab | ||
|
|
f3d2026746 |
No files matched your search
@@ -33,6 +33,8 @@ body:
|
|||||||
options:
|
options:
|
||||||
# The below comment is used to insert a new version with on-release.yml
|
# The below comment is used to insert a new version with on-release.yml
|
||||||
#NEXT_VERSION#
|
#NEXT_VERSION#
|
||||||
|
- 1.45.1
|
||||||
|
- 1.45.0
|
||||||
- 1.44.0
|
- 1.44.0
|
||||||
- 1.43.1
|
- 1.43.1
|
||||||
- 1.43.0
|
- 1.43.0
|
||||||
|
|||||||
@@ -1,3 +1,29 @@
|
|||||||
|
## [1.45.2](https://github.com/homarr-labs/homarr/compare/v1.45.1...v1.45.2) (2025-12-05)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add ipv6 support for internal nginx proxy ([2c48b18](https://github.com/homarr-labs/homarr/commit/2c48b18968a74cb7150c246ebb2505d07764f109))
|
||||||
|
* disable react-compiler due to issue with mantine-react-table data updates ([#4593](https://github.com/homarr-labs/homarr/issues/4593)) ([10572ca](https://github.com/homarr-labs/homarr/commit/10572ca962081961fb63b3ad9f164c610edf35e4))
|
||||||
|
|
||||||
|
## [1.45.1](https://github.com/homarr-labs/homarr/compare/v1.45.0...v1.45.1) (2025-12-03)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deps:** update dependency chroma-js to ^3.1.4 ([#4570](https://github.com/homarr-labs/homarr/issues/4570)) ([d6fc46d](https://github.com/homarr-labs/homarr/commit/d6fc46df3b5e2dd79ff6f9a3dd3a3e2050a0eeef))
|
||||||
|
* **deps:** update dependency chroma-js to ^3.2.0 ([#4585](https://github.com/homarr-labs/homarr/issues/4585)) ([b2c46d3](https://github.com/homarr-labs/homarr/commit/b2c46d33d6cc769e89e208fd29d3a1d143cf74c7))
|
||||||
|
* **deps:** update dependency isomorphic-dompurify to ^2.33.0 ([#4581](https://github.com/homarr-labs/homarr/issues/4581)) ([ca01a31](https://github.com/homarr-labs/homarr/commit/ca01a31fe5cbce53eb0e6bd1a943320c092161ef))
|
||||||
|
* **deps:** update dependency next-intl to v4.5.6 ([#4571](https://github.com/homarr-labs/homarr/issues/4571)) ([3062dda](https://github.com/homarr-labs/homarr/commit/3062ddafbe0bc1dbac9259af4036f0fa32d85237))
|
||||||
|
* **deps:** update dependency sass to ^1.94.2 ([#4572](https://github.com/homarr-labs/homarr/issues/4572)) ([7a8c50f](https://github.com/homarr-labs/homarr/commit/7a8c50fa4c5224dd74af3c17f56e2e5b8d6a4f43))
|
||||||
|
* **deps:** update dependency superjson to v2.2.6 ([#4573](https://github.com/homarr-labs/homarr/issues/4573)) ([deb5e9d](https://github.com/homarr-labs/homarr/commit/deb5e9d96092069e296ba473eaad58367b516f85))
|
||||||
|
* **deps:** update dependency swagger-ui-react to ^5.30.3 ([#4574](https://github.com/homarr-labs/homarr/issues/4574)) ([d2c1dc3](https://github.com/homarr-labs/homarr/commit/d2c1dc3cc1dd72fc9fc8ae9613467fa0b0b8d75a))
|
||||||
|
* **deps:** update dependency zod to ^4.1.13 ([#4575](https://github.com/homarr-labs/homarr/issues/4575)) ([879a77f](https://github.com/homarr-labs/homarr/commit/879a77ff2a81b0d0a17d1487eaf6d8539a955301))
|
||||||
|
* **deps:** update mantine monorepo to ^8.3.9 ([#4576](https://github.com/homarr-labs/homarr/issues/4576)) ([d5d59dd](https://github.com/homarr-labs/homarr/commit/d5d59dd2561f0be2f4f4c689f33c6a9a7840a21c))
|
||||||
|
* **deps:** update nextjs monorepo to v16.0.5 ([#4577](https://github.com/homarr-labs/homarr/issues/4577)) ([16f12d9](https://github.com/homarr-labs/homarr/commit/16f12d98e6cdae50fe2989db0911cb1350fcb93b))
|
||||||
|
* **deps:** update tanstack-query monorepo ([#4578](https://github.com/homarr-labs/homarr/issues/4578)) ([59bcbc8](https://github.com/homarr-labs/homarr/commit/59bcbc8b4de4a0a8cee8adf6336c7c10477c766a))
|
||||||
|
* **deps:** update testcontainers-node monorepo to ^11.9.0 ([#4583](https://github.com/homarr-labs/homarr/issues/4583)) ([10bdd74](https://github.com/homarr-labs/homarr/commit/10bdd74b2b287cb08ec0926147ac0e7fcfd492bb))
|
||||||
|
* **deps:** update trpc monorepo to ^11.7.2 ([#4579](https://github.com/homarr-labs/homarr/issues/4579)) ([3453482](https://github.com/homarr-labs/homarr/commit/3453482ed43644025c39dee8e20549e1beb86be0))
|
||||||
|
* **security:** rce vulnerability in nextjs ([b40deba](https://github.com/homarr-labs/homarr/commit/b40deba9ff1a3d629b6027565378d9f3ad2f1980))
|
||||||
|
|
||||||
## [1.45.0](https://github.com/homarr-labs/homarr/compare/v1.44.0...v1.45.0) (2025-11-28)
|
## [1.45.0](https://github.com/homarr-labs/homarr/compare/v1.44.0...v1.45.0) (2025-11-28)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ const withNextIntl = createNextIntlPlugin({
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
reactCompiler: true,
|
// react compiler breaks mantine-react-table, so disabled for now
|
||||||
|
//reactCompiler: true,
|
||||||
/** We already do typechecking as separate tasks in CI */
|
/** We already do typechecking as separate tasks in CI */
|
||||||
typescript: { ignoreBuildErrors: true },
|
typescript: { ignoreBuildErrors: true },
|
||||||
/**
|
/**
|
||||||
|
|||||||
+20
-20
@@ -50,46 +50,46 @@
|
|||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@homarr/widgets": "workspace:^0.1.0",
|
"@homarr/widgets": "workspace:^0.1.0",
|
||||||
"@mantine/colors-generator": "^8.3.8",
|
"@mantine/colors-generator": "^8.3.9",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@mantine/dropzone": "^8.3.8",
|
"@mantine/dropzone": "^8.3.9",
|
||||||
"@mantine/hooks": "^8.3.8",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"@mantine/modals": "^8.3.8",
|
"@mantine/modals": "^8.3.9",
|
||||||
"@mantine/tiptap": "^8.3.8",
|
"@mantine/tiptap": "^8.3.9",
|
||||||
"@million/lint": "1.0.14",
|
"@million/lint": "1.0.14",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"@tanstack/react-query": "^5.90.10",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
"@tanstack/react-query-devtools": "^5.90.2",
|
"@tanstack/react-query-devtools": "^5.91.1",
|
||||||
"@tanstack/react-query-next-experimental": "^5.91.0",
|
"@tanstack/react-query-next-experimental": "^5.91.0",
|
||||||
"@trpc/client": "^11.7.1",
|
"@trpc/client": "^11.7.2",
|
||||||
"@trpc/next": "^11.7.1",
|
"@trpc/next": "^11.7.2",
|
||||||
"@trpc/react-query": "^11.7.1",
|
"@trpc/react-query": "^11.7.2",
|
||||||
"@trpc/server": "^11.7.1",
|
"@trpc/server": "^11.7.2",
|
||||||
"@xterm/addon-canvas": "^0.7.0",
|
"@xterm/addon-canvas": "^0.7.0",
|
||||||
"@xterm/addon-fit": "0.10.0",
|
"@xterm/addon-fit": "0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"chroma-js": "^3.1.2",
|
"chroma-js": "^3.2.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"flag-icons": "^7.5.0",
|
"flag-icons": "^7.5.0",
|
||||||
"glob": "^12.0.0",
|
"glob": "^12.0.0",
|
||||||
"isomorphic-dompurify": "^2.32.0",
|
"isomorphic-dompurify": "^2.33.0",
|
||||||
"jotai": "^2.15.1",
|
"jotai": "^2.15.1",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"postcss-preset-mantine": "^1.18.0",
|
"postcss-preset-mantine": "^1.18.0",
|
||||||
"prismjs": "^1.30.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-error-boundary": "^6.0.0",
|
"react-error-boundary": "^6.0.0",
|
||||||
"react-simple-code-editor": "^0.14.1",
|
"react-simple-code-editor": "^0.14.1",
|
||||||
"sass": "^1.94.1",
|
"sass": "^1.94.2",
|
||||||
"superjson": "2.2.5",
|
"superjson": "2.2.6",
|
||||||
"swagger-ui-react": "^5.30.2",
|
"swagger-ui-react": "^5.30.3",
|
||||||
"use-deep-compare-effect": "^1.8.1",
|
"use-deep-compare-effect": "^1.8.1",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"@types/chroma-js": "3.1.2",
|
"@types/chroma-js": "3.1.2",
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
"@types/prismjs": "^1.26.5",
|
"@types/prismjs": "^1.26.5",
|
||||||
"@types/react": "19.2.6",
|
"@types/react": "19.2.7",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/swagger-ui-react": "^5.18.0",
|
"@types/swagger-ui-react": "^5.18.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
IconSettings,
|
IconSettings,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
import { applyAutoLayoutToBoard, clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { useSession } from "@homarr/auth/client";
|
import { useSession } from "@homarr/auth/client";
|
||||||
import { useRequiredBoard } from "@homarr/boards/context";
|
import { useRequiredBoard } from "@homarr/boards/context";
|
||||||
import { useEditMode } from "@homarr/boards/edit-mode";
|
import { useEditMode } from "@homarr/boards/edit-mode";
|
||||||
@@ -165,7 +165,7 @@ const EditModeMenu = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = useCallback(() => {
|
||||||
if (isEditMode) return saveBoard(applyAutoLayoutToBoard(board));
|
if (isEditMode) return saveBoard(board);
|
||||||
open();
|
open();
|
||||||
}, [board, isEditMode, saveBoard, open]);
|
}, [board, isEditMode, saveBoard, open]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { Button, Fieldset, Grid, Group, Input, NumberInput, Slider, Stack, Text, TextInput } from "@mantine/core";
|
||||||
Badge,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Chip,
|
|
||||||
ChipGroup,
|
|
||||||
Fieldset,
|
|
||||||
Grid,
|
|
||||||
Group,
|
|
||||||
Input,
|
|
||||||
InputWrapper,
|
|
||||||
NumberInput,
|
|
||||||
RadioGroup,
|
|
||||||
SimpleGrid,
|
|
||||||
Slider,
|
|
||||||
Stack,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
} from "@mantine/core";
|
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import { boardLayoutModes } from "@homarr/definitions";
|
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { CustomRadioCard } from "@homarr/ui";
|
|
||||||
import { boardSaveLayoutsSchema } from "@homarr/validation/board";
|
import { boardSaveLayoutsSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
import type { Board } from "../../_types";
|
import type { Board } from "../../_types";
|
||||||
@@ -45,8 +25,6 @@ export const LayoutSettingsContent = ({ board }: Props) => {
|
|||||||
const form = useZodForm(boardSaveLayoutsSchema.omit({ id: true }).required(), {
|
const form = useZodForm(boardSaveLayoutsSchema.omit({ id: true }).required(), {
|
||||||
initialValues: {
|
initialValues: {
|
||||||
layouts: board.layouts,
|
layouts: board.layouts,
|
||||||
layoutMode: board.layoutMode,
|
|
||||||
baseLayoutId: board.baseLayoutId,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,53 +38,6 @@ export const LayoutSettingsContent = ({ board }: Props) => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Stack>
|
<Stack>
|
||||||
<RadioGroup {...form.getInputProps("layoutMode")} label={t("board.field.layoutMode.label")}>
|
|
||||||
<SimpleGrid cols={{ sm: 2, xs: 1 }} spacing="md">
|
|
||||||
{boardLayoutModes.values.map((layoutMode) => (
|
|
||||||
<Box w="100%" key={layoutMode} pos="relative">
|
|
||||||
<CustomRadioCard
|
|
||||||
value={layoutMode}
|
|
||||||
label={t(`board.field.layoutMode.option.${layoutMode}.label`)}
|
|
||||||
description={t(`board.field.layoutMode.option.${layoutMode}.description`)}
|
|
||||||
/>
|
|
||||||
{layoutMode === boardLayoutModes.defaultValue && (
|
|
||||||
<Badge pos="absolute" top="8px" right="8px" variant="light" size="sm">
|
|
||||||
{t("common.select.badge.recommended")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
{form.values.layoutMode === "auto" && (
|
|
||||||
<InputWrapper label={t("board.field.baseLayout.label")} description={t("board.field.baseLayout.description")}>
|
|
||||||
<ChipGroup {...form.getInputProps("baseLayoutId")} multiple={false}>
|
|
||||||
<SimpleGrid
|
|
||||||
cols={{
|
|
||||||
lg: Math.min(6, form.values.layouts.length),
|
|
||||||
sm: Math.min(4, form.values.layouts.length),
|
|
||||||
xs: Math.min(2, form.values.layouts.length),
|
|
||||||
}}
|
|
||||||
spacing="md"
|
|
||||||
mt="sm"
|
|
||||||
>
|
|
||||||
{form.values.layouts.map((layout) => (
|
|
||||||
<Chip
|
|
||||||
key={layout.id}
|
|
||||||
value={layout.id}
|
|
||||||
w="100%"
|
|
||||||
size="sm"
|
|
||||||
styles={{ label: { width: "100%", justifyContent: "center" } }}
|
|
||||||
>
|
|
||||||
{layout.name}
|
|
||||||
</Chip>
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
</ChipGroup>
|
|
||||||
</InputWrapper>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Text fw={500}>{t("board.setting.section.layout.responsive.title")}</Text>
|
<Text fw={500}>{t("board.setting.section.layout.responsive.title")}</Text>
|
||||||
@@ -151,21 +82,18 @@ export const LayoutSettingsContent = ({ board }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
{form.values.layouts.length >= 2 && form.values.baseLayoutId !== layout.id && (
|
{form.values.layouts.length >= 2 && (
|
||||||
<Group justify="end">
|
<Group justify="end">
|
||||||
<Button
|
<Button
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
form.setValues((previous) => {
|
form.setValues((previous) =>
|
||||||
if (!previous.layouts) return previous;
|
previous.layouts !== undefined && previous.layouts.length >= 2
|
||||||
if (previous.layouts.length < 2) return previous;
|
? {
|
||||||
if (previous.baseLayoutId === layout.id) return previous;
|
layouts: form.values.layouts.filter((filteredLayout) => filteredLayout.id !== layout.id),
|
||||||
|
}
|
||||||
return {
|
: previous,
|
||||||
...previous,
|
);
|
||||||
layouts: previous.layouts.filter((filteredLayout) => filteredLayout.id !== layout.id),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("common.action.remove")}
|
{t("common.action.remove")}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export class BoardMockBuilder {
|
|||||||
private readonly board: Board;
|
private readonly board: Board;
|
||||||
|
|
||||||
constructor(board?: Partial<Omit<Board, "groupPermissions" | "userPermissions" | "sections" | "items" | "layouts">>) {
|
constructor(board?: Partial<Omit<Board, "groupPermissions" | "userPermissions" | "sections" | "items" | "layouts">>) {
|
||||||
const baseLayoutId = createId();
|
|
||||||
this.board = {
|
this.board = {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
backgroundImageRepeat: "no-repeat",
|
backgroundImageRepeat: "no-repeat",
|
||||||
@@ -42,14 +41,12 @@ export class BoardMockBuilder {
|
|||||||
items: [],
|
items: [],
|
||||||
layouts: [
|
layouts: [
|
||||||
{
|
{
|
||||||
id: baseLayoutId,
|
id: createId(),
|
||||||
name: "Base",
|
name: "Base",
|
||||||
columnCount: 12,
|
columnCount: 12,
|
||||||
breakpoint: 0,
|
breakpoint: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
baseLayoutId,
|
|
||||||
layoutMode: "auto",
|
|
||||||
...board,
|
...board,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"superjson": "2.2.5",
|
"superjson": "2.2.6",
|
||||||
"undici": "7.16.0"
|
"undici": "7.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ events {
|
|||||||
http {
|
http {
|
||||||
server {
|
server {
|
||||||
listen 7575;
|
listen 7575;
|
||||||
|
listen [::]:7575;
|
||||||
|
|
||||||
# Route websockets traffic to port 3001
|
# Route websockets traffic to port 3001
|
||||||
location /websockets {
|
location /websockets {
|
||||||
|
|||||||
+10
-10
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homarr",
|
"name": "homarr",
|
||||||
"version": "1.45.0",
|
"version": "1.45.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "cross-env CI=true turbo build",
|
"build": "cross-env CI=true turbo build",
|
||||||
@@ -42,27 +42,27 @@
|
|||||||
"@semantic-release/github": "^12.0.2",
|
"@semantic-release/github": "^12.0.2",
|
||||||
"@semantic-release/npm": "^13.1.2",
|
"@semantic-release/npm": "^13.1.2",
|
||||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
||||||
"@testcontainers/redis": "^11.8.1",
|
"@testcontainers/redis": "^11.9.0",
|
||||||
"@turbo/gen": "^2.6.1",
|
"@turbo/gen": "^2.6.1",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"@vitest/coverage-v8": "^4.0.10",
|
"@vitest/coverage-v8": "^4.0.14",
|
||||||
"@vitest/ui": "^4.0.10",
|
"@vitest/ui": "^4.0.14",
|
||||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"jsdom": "^27.2.0",
|
"jsdom": "^27.2.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"semantic-release": "^25.0.2",
|
"semantic-release": "^25.0.2",
|
||||||
"testcontainers": "^11.8.1",
|
"testcontainers": "^11.9.0",
|
||||||
"turbo": "^2.6.1",
|
"turbo": "^2.6.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"vitest": "^4.0.10"
|
"vitest": "^4.0.14"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.22.0",
|
"packageManager": "pnpm@10.24.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.11.1",
|
"node": ">=24.11.1",
|
||||||
"pnpm": ">=10.22.0"
|
"pnpm": ">=10.24.0"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
||||||
"esbuild@<=0.24.2": ">=0.27.0",
|
"esbuild@<=0.24.2": ">=0.27.0",
|
||||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
||||||
"hono@<4.6.5": ">=4.10.6",
|
"hono@<4.6.5": ">=4.10.7",
|
||||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
|
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
|
||||||
"prismjs@<1.30.0": ">=1.30.0",
|
"prismjs@<1.30.0": ">=1.30.0",
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
|
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
|
||||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
|
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
|
||||||
"tmp@<=0.2.3": ">=0.2.5",
|
"tmp@<=0.2.3": ">=0.2.5",
|
||||||
"vite@>=5.0.0 <=5.4.18": ">=7.2.2"
|
"vite@>=5.0.0 <=5.4.18": ">=7.2.4"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@umami/node": "^0.4.0",
|
"@umami/node": "^0.4.0",
|
||||||
"superjson": "2.2.5"
|
"superjson": "2.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -43,18 +43,18 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@kubernetes/client-node": "^1.4.0",
|
"@kubernetes/client-node": "^1.4.0",
|
||||||
"@tanstack/react-query": "^5.90.10",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
"@trpc/client": "^11.7.1",
|
"@trpc/client": "^11.7.2",
|
||||||
"@trpc/react-query": "^11.7.1",
|
"@trpc/react-query": "^11.7.2",
|
||||||
"@trpc/server": "^11.7.1",
|
"@trpc/server": "^11.7.2",
|
||||||
"@trpc/tanstack-react-query": "^11.7.1",
|
"@trpc/tanstack-react-query": "^11.7.2",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"superjson": "2.2.5",
|
"superjson": "2.2.6",
|
||||||
"trpc-to-openapi": "^3.1.0",
|
"trpc-to-openapi": "^3.1.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -17,5 +17,3 @@ export const fetchApi = createTRPCClient<AppRouter>({
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export { applyAutoLayoutToBoard } from "./router/board/board-layout";
|
|
||||||
@@ -5,7 +5,7 @@ import { z } from "zod/v4";
|
|||||||
import { constructBoardPermissions } from "@homarr/auth/shared";
|
import { constructBoardPermissions } from "@homarr/auth/shared";
|
||||||
import { createId } from "@homarr/common";
|
import { createId } from "@homarr/common";
|
||||||
import type { DeviceType } from "@homarr/common/server";
|
import type { DeviceType } from "@homarr/common/server";
|
||||||
import type { Database, InferInsertModel, InferSelectModel } from "@homarr/db";
|
import type { Database, InferInsertModel, InferSelectModel, SQL } from "@homarr/db";
|
||||||
import { and, asc, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db";
|
import { and, asc, eq, handleTransactionsAsync, inArray, isNull, like, not, or, sql } from "@homarr/db";
|
||||||
import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection";
|
import { createDbInsertCollectionWithoutTransaction } from "@homarr/db/collection";
|
||||||
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
|
||||||
@@ -27,11 +27,13 @@ import {
|
|||||||
sections,
|
sections,
|
||||||
users,
|
users,
|
||||||
} from "@homarr/db/schema";
|
} from "@homarr/db/schema";
|
||||||
|
import type { WidgetKind } from "@homarr/definitions";
|
||||||
import {
|
import {
|
||||||
emptySuperJSON,
|
emptySuperJSON,
|
||||||
everyoneGroup,
|
everyoneGroup,
|
||||||
getPermissionsWithChildren,
|
getPermissionsWithChildren,
|
||||||
getPermissionsWithParents,
|
getPermissionsWithParents,
|
||||||
|
widgetKinds,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import { importOldmarrAsync } from "@homarr/old-import";
|
import { importOldmarrAsync } from "@homarr/old-import";
|
||||||
import { importJsonFileSchema } from "@homarr/old-import/shared";
|
import { importJsonFileSchema } from "@homarr/old-import/shared";
|
||||||
@@ -48,11 +50,13 @@ import {
|
|||||||
boardSaveSchema,
|
boardSaveSchema,
|
||||||
} from "@homarr/validation/board";
|
} from "@homarr/validation/board";
|
||||||
import { byIdSchema } from "@homarr/validation/common";
|
import { byIdSchema } from "@homarr/validation/common";
|
||||||
|
import { zodUnionFromArray } from "@homarr/validation/enums";
|
||||||
|
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
||||||
|
import { sectionSchema, sharedItemSchema } from "@homarr/validation/shared";
|
||||||
|
|
||||||
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, permissionRequiredProcedure, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
import { throwIfActionForbiddenAsync } from "./board/board-access";
|
||||||
import { applyAutoLayoutToBoard, getUpdatedBoardLayout } from "./board/board-layout";
|
import { generateResponsiveGridFor } from "./board/grid-algorithm";
|
||||||
import { getFullBoardWithWhereAsync } from "./board/full-board-query";
|
|
||||||
|
|
||||||
export const boardRouter = createTRPCRouter({
|
export const boardRouter = createTRPCRouter({
|
||||||
exists: permissionRequiredProcedure
|
exists: permissionRequiredProcedure
|
||||||
@@ -275,7 +279,6 @@ export const boardRouter = createTRPCRouter({
|
|||||||
name: input.name,
|
name: input.name,
|
||||||
isPublic: input.isPublic,
|
isPublic: input.isPublic,
|
||||||
creatorId: ctx.session.user.id,
|
creatorId: ctx.session.user.id,
|
||||||
layoutMode: input.layoutMode,
|
|
||||||
});
|
});
|
||||||
createBoardCollection.sections.push({
|
createBoardCollection.sections.push({
|
||||||
id: createId(),
|
id: createId(),
|
||||||
@@ -284,9 +287,8 @@ export const boardRouter = createTRPCRouter({
|
|||||||
yOffset: 0,
|
yOffset: 0,
|
||||||
boardId,
|
boardId,
|
||||||
});
|
});
|
||||||
const baseLayoutId = createId();
|
|
||||||
createBoardCollection.layouts.push({
|
createBoardCollection.layouts.push({
|
||||||
id: baseLayoutId,
|
id: createId(),
|
||||||
name: "Base",
|
name: "Base",
|
||||||
columnCount: input.columnCount,
|
columnCount: input.columnCount,
|
||||||
breakpoint: 0,
|
breakpoint: 0,
|
||||||
@@ -294,10 +296,6 @@ export const boardRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await createBoardCollection.insertAllAsync(ctx.db);
|
await createBoardCollection.insertAllAsync(ctx.db);
|
||||||
// Define the base layout (only for automatic layout mode)
|
|
||||||
if (input.layoutMode === "auto") {
|
|
||||||
await ctx.db.update(boards).set({ baseLayoutId }).where(eq(boards.id, boardId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user?.homeBoardId) {
|
if (!user?.homeBoardId) {
|
||||||
await ctx.db.update(users).set({ homeBoardId: boardId }).where(eq(users.id, ctx.session.user.id));
|
await ctx.db.update(users).set({ homeBoardId: boardId }).where(eq(users.id, ctx.session.user.id));
|
||||||
@@ -584,15 +582,13 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
||||||
|
|
||||||
const board = await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||||
return applyAutoLayoutToBoard(board);
|
|
||||||
}),
|
}),
|
||||||
getBoardByName: publicProcedure.input(boardByNameSchema).query(async ({ input, ctx }) => {
|
getBoardByName: publicProcedure.input(boardByNameSchema).query(async ({ input, ctx }) => {
|
||||||
const boardWhere = eq(sql`UPPER(${boards.name})`, input.name.toUpperCase());
|
const boardWhere = eq(sql`UPPER(${boards.name})`, input.name.toUpperCase());
|
||||||
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
await throwIfActionForbiddenAsync(ctx, boardWhere, "view");
|
||||||
|
|
||||||
const board = await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
return await getFullBoardWithWhereAsync(ctx.db, boardWhere, ctx.session?.user.id ?? null);
|
||||||
return applyAutoLayoutToBoard(board);
|
|
||||||
}),
|
}),
|
||||||
saveLayouts: protectedProcedure.input(boardSaveLayoutsSchema).mutation(async ({ ctx, input }) => {
|
saveLayouts: protectedProcedure.input(boardSaveLayoutsSchema).mutation(async ({ ctx, input }) => {
|
||||||
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify");
|
||||||
@@ -600,32 +596,13 @@ export const boardRouter = createTRPCRouter({
|
|||||||
const board = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
const board = await getFullBoardWithWhereAsync(ctx.db, eq(boards.id, input.id), ctx.session.user.id);
|
||||||
|
|
||||||
const addedLayouts = filterAddedItems(input.layouts, board.layouts);
|
const addedLayouts = filterAddedItems(input.layouts, board.layouts);
|
||||||
const removedLayouts = filterRemovedItems(input.layouts, board.layouts);
|
|
||||||
|
|
||||||
if (input.layoutMode === "auto" && input.baseLayoutId === null) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Base layout must be set when using automatic layout mode",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removedLayouts.some((layout) => layout.id === input.baseLayoutId)) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
message: "Cannot remove base layout",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const layoutsToInsert: InferInsertModel<typeof layouts>[] = [];
|
const layoutsToInsert: InferInsertModel<typeof layouts>[] = [];
|
||||||
const itemSectionLayoutsToInsert: InferInsertModel<typeof itemLayouts>[] = [];
|
const itemSectionLayoutsToInsert: InferInsertModel<typeof itemLayouts>[] = [];
|
||||||
const sectionLayoutsToInsert: InferInsertModel<typeof sectionLayouts>[] = [];
|
const sectionLayoutsToInsert: InferInsertModel<typeof sectionLayouts>[] = [];
|
||||||
|
|
||||||
let baseLayoutId = input.baseLayoutId;
|
|
||||||
for (const addedLayout of addedLayouts) {
|
for (const addedLayout of addedLayouts) {
|
||||||
const layoutId = createId();
|
const layoutId = createId();
|
||||||
if (addedLayout.id === input.baseLayoutId) {
|
|
||||||
baseLayoutId = layoutId;
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutsToInsert.push({
|
layoutsToInsert.push({
|
||||||
id: layoutId,
|
id: layoutId,
|
||||||
@@ -732,18 +709,11 @@ export const boardRouter = createTRPCRouter({
|
|||||||
.where(eq(layouts.id, updatedLayout.id));
|
.where(eq(layouts.id, updatedLayout.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const removedLayouts = filterRemovedItems(input.layouts, board.layouts);
|
||||||
const removedLayoutIds = removedLayouts.map((layout) => layout.id);
|
const removedLayoutIds = removedLayouts.map((layout) => layout.id);
|
||||||
if (removedLayoutIds.length > 0) {
|
if (removedLayoutIds.length > 0) {
|
||||||
await ctx.db.delete(layouts).where(inArray(layouts.id, removedLayoutIds));
|
await ctx.db.delete(layouts).where(inArray(layouts.id, removedLayoutIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.db
|
|
||||||
.update(boards)
|
|
||||||
.set({
|
|
||||||
baseLayoutId,
|
|
||||||
layoutMode: input.layoutMode,
|
|
||||||
})
|
|
||||||
.where(eq(boards.id, input.id));
|
|
||||||
}),
|
}),
|
||||||
savePartialBoardSettings: protectedProcedure
|
savePartialBoardSettings: protectedProcedure
|
||||||
.input(boardSavePartialSettingsSchema.and(z.object({ id: z.string() })))
|
.input(boardSavePartialSettingsSchema.and(z.object({ id: z.string() })))
|
||||||
@@ -1438,6 +1408,244 @@ const noBoardWithSimilarNameAsync = async (db: Database, name: string, ignoredId
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUpdatedBoardLayout = (
|
||||||
|
board: Awaited<ReturnType<typeof getFullBoardWithWhereAsync>>,
|
||||||
|
options: {
|
||||||
|
previous: {
|
||||||
|
layoutId: string;
|
||||||
|
columnCount: number;
|
||||||
|
};
|
||||||
|
current: {
|
||||||
|
layoutId: string;
|
||||||
|
columnCount: number;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const itemSectionLayoutsCollection: InferInsertModel<typeof itemLayouts>[] = [];
|
||||||
|
const sectionLayoutsCollection: InferInsertModel<typeof sectionLayouts>[] = [];
|
||||||
|
|
||||||
|
const elements = getElementsForLayout(board, options.previous.layoutId);
|
||||||
|
const rootSections = board.sections.filter((section) => section.kind !== "dynamic");
|
||||||
|
|
||||||
|
for (const rootSection of rootSections) {
|
||||||
|
const result = generateResponsiveGridFor({
|
||||||
|
items: elements,
|
||||||
|
previousWidth: options.previous.columnCount,
|
||||||
|
width: options.current.columnCount,
|
||||||
|
sectionId: rootSection.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
itemSectionLayoutsCollection.push(
|
||||||
|
...board.items
|
||||||
|
.map((item): InferInsertModel<typeof itemLayouts> | null => {
|
||||||
|
const currentElement = result.items.find((element) => element.type === "item" && element.id === item.id);
|
||||||
|
|
||||||
|
if (!currentElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
itemId: item.id,
|
||||||
|
layoutId: options.current.layoutId,
|
||||||
|
sectionId: currentElement.sectionId,
|
||||||
|
height: currentElement.height,
|
||||||
|
width: currentElement.width,
|
||||||
|
xOffset: currentElement.xOffset,
|
||||||
|
yOffset: currentElement.yOffset,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item) => item !== null),
|
||||||
|
);
|
||||||
|
|
||||||
|
sectionLayoutsCollection.push(
|
||||||
|
...board.sections
|
||||||
|
.filter((section) => section.kind === "dynamic")
|
||||||
|
.map((section): InferInsertModel<typeof sectionLayouts> | null => {
|
||||||
|
const currentElement = result.items.find(
|
||||||
|
(element) => element.type === "section" && element.id === section.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
layoutId: options.current.layoutId,
|
||||||
|
sectionId: section.id,
|
||||||
|
parentSectionId: currentElement.sectionId,
|
||||||
|
height: currentElement.height,
|
||||||
|
width: currentElement.width,
|
||||||
|
xOffset: currentElement.xOffset,
|
||||||
|
yOffset: currentElement.yOffset,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((section) => section !== null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
itemSectionLayouts: itemSectionLayoutsCollection,
|
||||||
|
sectionLayouts: sectionLayoutsCollection,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getElementsForLayout = (board: Awaited<ReturnType<typeof getFullBoardWithWhereAsync>>, layoutId: string) => {
|
||||||
|
const sectionElements = board.sections
|
||||||
|
.filter((section) => section.kind === "dynamic")
|
||||||
|
.map((section) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const clonedLayout = section.layouts.find((sectionLayout) => sectionLayout.layoutId === layoutId)!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: section.id,
|
||||||
|
type: "section" as const,
|
||||||
|
height: clonedLayout.height,
|
||||||
|
width: clonedLayout.width,
|
||||||
|
xOffset: clonedLayout.xOffset,
|
||||||
|
yOffset: clonedLayout.yOffset,
|
||||||
|
sectionId: clonedLayout.parentSectionId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemElements = board.items.map((item) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const clonedLayout = item.layouts.find((itemLayout) => itemLayout.layoutId === layoutId)!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
type: "item" as const,
|
||||||
|
height: clonedLayout.height,
|
||||||
|
width: clonedLayout.width,
|
||||||
|
xOffset: clonedLayout.xOffset,
|
||||||
|
yOffset: clonedLayout.yOffset,
|
||||||
|
sectionId: clonedLayout.sectionId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...itemElements, ...sectionElements];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, userId: string | null) => {
|
||||||
|
const groupsOfCurrentUser = await db.query.groupMembers.findMany({
|
||||||
|
where: eq(groupMembers.userId, userId ?? ""),
|
||||||
|
});
|
||||||
|
const board = await db.query.boards.findFirst({
|
||||||
|
where,
|
||||||
|
with: {
|
||||||
|
creator: {
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
image: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
with: {
|
||||||
|
collapseStates: {
|
||||||
|
where: eq(sectionCollapseStates.userId, userId ?? ""),
|
||||||
|
},
|
||||||
|
layouts: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
with: {
|
||||||
|
integrations: {
|
||||||
|
with: {
|
||||||
|
integration: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layouts: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layouts: true,
|
||||||
|
userPermissions: {
|
||||||
|
where: eq(boardUserPermissions.userId, userId ?? ""),
|
||||||
|
columns: {
|
||||||
|
permission: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
groupPermissions: {
|
||||||
|
where: inArray(boardGroupPermissions.groupId, groupsOfCurrentUser.map((group) => group.groupId).concat("")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!board) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Board not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { sections, items, layouts, ...otherBoardProperties } = board;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...otherBoardProperties,
|
||||||
|
layouts: layouts
|
||||||
|
.map(({ boardId: _, ...layout }) => layout)
|
||||||
|
.sort((layoutA, layoutB) => layoutA.breakpoint - layoutB.breakpoint),
|
||||||
|
sections: sections.map(({ collapseStates, ...section }) =>
|
||||||
|
parseSection({
|
||||||
|
...section,
|
||||||
|
xOffset: section.xOffset,
|
||||||
|
yOffset: section.yOffset,
|
||||||
|
options: superjson.parse(section.options ?? emptySuperJSON),
|
||||||
|
layouts: section.layouts.map((layout) => ({
|
||||||
|
xOffset: layout.xOffset,
|
||||||
|
yOffset: layout.yOffset,
|
||||||
|
width: layout.width,
|
||||||
|
height: layout.height,
|
||||||
|
parentSectionId: layout.parentSectionId,
|
||||||
|
layoutId: layout.layoutId,
|
||||||
|
})),
|
||||||
|
collapsed: collapseStates.at(0)?.collapsed ?? false,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
items: items.map(({ integrations: itemIntegrations, ...item }) =>
|
||||||
|
parseItem({
|
||||||
|
...item,
|
||||||
|
layouts: item.layouts.map((layout) => ({
|
||||||
|
xOffset: layout.xOffset,
|
||||||
|
yOffset: layout.yOffset,
|
||||||
|
width: layout.width,
|
||||||
|
height: layout.height,
|
||||||
|
layoutId: layout.layoutId,
|
||||||
|
sectionId: layout.sectionId,
|
||||||
|
})),
|
||||||
|
integrationIds: itemIntegrations.map((item) => item.integration.id),
|
||||||
|
advancedOptions: superjson.parse<BoardItemAdvancedOptions>(item.advancedOptions),
|
||||||
|
options: superjson.parse<Record<string, unknown>>(item.options),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const forKind = <T extends WidgetKind>(kind: T) =>
|
||||||
|
z.object({
|
||||||
|
kind: z.literal(kind),
|
||||||
|
options: z.record(z.string(), z.unknown()),
|
||||||
|
});
|
||||||
|
|
||||||
|
const outputItemSchema = zodUnionFromArray(widgetKinds.map((kind) => forKind(kind))).and(sharedItemSchema);
|
||||||
|
|
||||||
|
const parseItem = (item: unknown) => {
|
||||||
|
const result = outputItemSchema.safeParse(item);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error.message);
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseSection = (section: unknown) => {
|
||||||
|
const result = sectionSchema.safeParse(section);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error.message);
|
||||||
|
}
|
||||||
|
return result.data;
|
||||||
|
};
|
||||||
|
|
||||||
const filterAddedItems = <TInput extends { id: string }>(inputArray: TInput[], dbArray: TInput[]) =>
|
const filterAddedItems = <TInput extends { id: string }>(inputArray: TInput[], dbArray: TInput[]) =>
|
||||||
inputArray.filter((inputItem) => !dbArray.some((dbItem) => dbItem.id === inputItem.id));
|
inputArray.filter((inputItem) => !dbArray.some((dbItem) => dbItem.id === inputItem.id));
|
||||||
|
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
import type { Modify } from "@homarr/common/types";
|
|
||||||
import type { InferInsertModel } from "@homarr/db";
|
|
||||||
import type { itemLayouts, sectionLayouts } from "@homarr/db/schema";
|
|
||||||
|
|
||||||
import type { getFullBoardWithWhereAsync } from "./full-board-query";
|
|
||||||
import { generateResponsiveGridFor } from "./grid-algorithm";
|
|
||||||
|
|
||||||
type DynamicSectionLayout = Modify<InferInsertModel<typeof sectionLayouts>, { parentSectionId: string }>;
|
|
||||||
|
|
||||||
export const getUpdatedBoardLayout = (
|
|
||||||
board: Awaited<ReturnType<typeof getFullBoardWithWhereAsync>>,
|
|
||||||
options: {
|
|
||||||
previous: {
|
|
||||||
layoutId: string;
|
|
||||||
columnCount: number;
|
|
||||||
};
|
|
||||||
current: {
|
|
||||||
layoutId: string;
|
|
||||||
columnCount: number;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const itemSectionLayoutsCollection: InferInsertModel<typeof itemLayouts>[] = [];
|
|
||||||
const sectionLayoutsCollection: DynamicSectionLayout[] = [];
|
|
||||||
|
|
||||||
const elements = getElementsForLayout(board, options.previous.layoutId);
|
|
||||||
const rootSections = board.sections.filter((section) => section.kind !== "dynamic");
|
|
||||||
|
|
||||||
for (const rootSection of rootSections) {
|
|
||||||
const result = generateResponsiveGridFor({
|
|
||||||
items: elements,
|
|
||||||
previousWidth: options.previous.columnCount,
|
|
||||||
width: options.current.columnCount,
|
|
||||||
sectionId: rootSection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
itemSectionLayoutsCollection.push(
|
|
||||||
...board.items
|
|
||||||
.map((item): InferInsertModel<typeof itemLayouts> | null => {
|
|
||||||
const currentElement = result.items.find((element) => element.type === "item" && element.id === item.id);
|
|
||||||
|
|
||||||
if (!currentElement) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
itemId: item.id,
|
|
||||||
layoutId: options.current.layoutId,
|
|
||||||
sectionId: currentElement.sectionId,
|
|
||||||
height: currentElement.height,
|
|
||||||
width: currentElement.width,
|
|
||||||
xOffset: currentElement.xOffset,
|
|
||||||
yOffset: currentElement.yOffset,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((item) => item !== null),
|
|
||||||
);
|
|
||||||
|
|
||||||
sectionLayoutsCollection.push(
|
|
||||||
...board.sections
|
|
||||||
.filter((section) => section.kind === "dynamic")
|
|
||||||
.map((section): DynamicSectionLayout | null => {
|
|
||||||
const currentElement = result.items.find(
|
|
||||||
(element) => element.type === "section" && element.id === section.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!currentElement) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
layoutId: options.current.layoutId,
|
|
||||||
sectionId: section.id,
|
|
||||||
parentSectionId: currentElement.sectionId,
|
|
||||||
height: currentElement.height,
|
|
||||||
width: currentElement.width,
|
|
||||||
xOffset: currentElement.xOffset,
|
|
||||||
yOffset: currentElement.yOffset,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((section) => section !== null),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
itemSectionLayouts: itemSectionLayoutsCollection,
|
|
||||||
sectionLayouts: sectionLayoutsCollection,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getElementsForLayout = (board: Awaited<ReturnType<typeof getFullBoardWithWhereAsync>>, layoutId: string) => {
|
|
||||||
const sectionElements = board.sections
|
|
||||||
.filter((section) => section.kind === "dynamic")
|
|
||||||
.map((section) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const clonedLayout = section.layouts.find((sectionLayout) => sectionLayout.layoutId === layoutId)!;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: section.id,
|
|
||||||
type: "section" as const,
|
|
||||||
height: clonedLayout.height,
|
|
||||||
width: clonedLayout.width,
|
|
||||||
xOffset: clonedLayout.xOffset,
|
|
||||||
yOffset: clonedLayout.yOffset,
|
|
||||||
sectionId: clonedLayout.parentSectionId,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const itemElements = board.items.map((item) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const clonedLayout = item.layouts.find((itemLayout) => itemLayout.layoutId === layoutId)!;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
type: "item" as const,
|
|
||||||
height: clonedLayout.height,
|
|
||||||
width: clonedLayout.width,
|
|
||||||
xOffset: clonedLayout.xOffset,
|
|
||||||
yOffset: clonedLayout.yOffset,
|
|
||||||
sectionId: clonedLayout.sectionId,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return [...itemElements, ...sectionElements];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applyAutoLayoutToBoard = (
|
|
||||||
board: Awaited<ReturnType<typeof getFullBoardWithWhereAsync>>,
|
|
||||||
): Awaited<ReturnType<typeof getFullBoardWithWhereAsync>> => {
|
|
||||||
if (board.layoutMode === "custom") return board;
|
|
||||||
|
|
||||||
const baseLayout = board.layouts.find((layout) => layout.id === board.baseLayoutId) ?? null;
|
|
||||||
if (!baseLayout) return board;
|
|
||||||
|
|
||||||
const updatedLayoutMap = new Map<string, ReturnType<typeof getUpdatedBoardLayout>>();
|
|
||||||
for (const layout of board.layouts) {
|
|
||||||
if (layout.id === baseLayout.id) continue;
|
|
||||||
|
|
||||||
updatedLayoutMap.set(
|
|
||||||
layout.id,
|
|
||||||
getUpdatedBoardLayout(board, {
|
|
||||||
previous: {
|
|
||||||
layoutId: baseLayout.id,
|
|
||||||
columnCount: baseLayout.columnCount,
|
|
||||||
},
|
|
||||||
current: {
|
|
||||||
layoutId: layout.id,
|
|
||||||
columnCount: layout.columnCount,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...board,
|
|
||||||
items: board.items.map((item) => ({
|
|
||||||
...item,
|
|
||||||
layouts: item.layouts.map(
|
|
||||||
(layoutSection) =>
|
|
||||||
updatedLayoutMap
|
|
||||||
.get(layoutSection.layoutId)
|
|
||||||
?.itemSectionLayouts.find(
|
|
||||||
(updatedLayoutSection) =>
|
|
||||||
updatedLayoutSection.itemId === item.id && updatedLayoutSection.layoutId === layoutSection.layoutId,
|
|
||||||
) ?? layoutSection,
|
|
||||||
),
|
|
||||||
})),
|
|
||||||
sections: board.sections.map((section) =>
|
|
||||||
"layouts" in section
|
|
||||||
? {
|
|
||||||
...section,
|
|
||||||
layouts: section.layouts.map(
|
|
||||||
(sectionLayout) =>
|
|
||||||
updatedLayoutMap
|
|
||||||
.get(sectionLayout.layoutId)
|
|
||||||
?.sectionLayouts.find(
|
|
||||||
(updatedSectionLayout) =>
|
|
||||||
updatedSectionLayout.sectionId === section.id &&
|
|
||||||
updatedSectionLayout.layoutId === sectionLayout.layoutId,
|
|
||||||
) ?? sectionLayout,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: section,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import { TRPCError } from "@trpc/server";
|
|
||||||
import superjson from "superjson";
|
|
||||||
import z from "zod";
|
|
||||||
|
|
||||||
import type { Database, SQL } from "@homarr/db";
|
|
||||||
import { eq, inArray } from "@homarr/db";
|
|
||||||
import { boardGroupPermissions, boardUserPermissions, groupMembers, sectionCollapseStates } from "@homarr/db/schema";
|
|
||||||
import type { WidgetKind } from "@homarr/definitions";
|
|
||||||
import { emptySuperJSON, widgetKinds } from "@homarr/definitions";
|
|
||||||
import { zodUnionFromArray } from "@homarr/validation/enums";
|
|
||||||
import { sectionSchema, sharedItemSchema } from "@homarr/validation/shared";
|
|
||||||
import type { BoardItemAdvancedOptions } from "@homarr/validation/shared";
|
|
||||||
|
|
||||||
export const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, userId: string | null) => {
|
|
||||||
const groupsOfCurrentUser = await db.query.groupMembers.findMany({
|
|
||||||
where: eq(groupMembers.userId, userId ?? ""),
|
|
||||||
});
|
|
||||||
const board = await db.query.boards.findFirst({
|
|
||||||
where,
|
|
||||||
with: {
|
|
||||||
creator: {
|
|
||||||
columns: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
image: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sections: {
|
|
||||||
with: {
|
|
||||||
collapseStates: {
|
|
||||||
where: eq(sectionCollapseStates.userId, userId ?? ""),
|
|
||||||
},
|
|
||||||
layouts: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
with: {
|
|
||||||
integrations: {
|
|
||||||
with: {
|
|
||||||
integration: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
layouts: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
layouts: true,
|
|
||||||
userPermissions: {
|
|
||||||
where: eq(boardUserPermissions.userId, userId ?? ""),
|
|
||||||
columns: {
|
|
||||||
permission: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
groupPermissions: {
|
|
||||||
where: inArray(boardGroupPermissions.groupId, groupsOfCurrentUser.map((group) => group.groupId).concat("")),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!board) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "NOT_FOUND",
|
|
||||||
message: "Board not found",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { sections, items, layouts, ...otherBoardProperties } = board;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...otherBoardProperties,
|
|
||||||
layouts: layouts
|
|
||||||
.map(({ boardId: _, ...layout }) => layout)
|
|
||||||
.sort((layoutA, layoutB) => layoutA.breakpoint - layoutB.breakpoint),
|
|
||||||
sections: sections.map(({ collapseStates, ...section }) =>
|
|
||||||
parseSection({
|
|
||||||
...section,
|
|
||||||
xOffset: section.xOffset,
|
|
||||||
yOffset: section.yOffset,
|
|
||||||
options: superjson.parse(section.options ?? emptySuperJSON),
|
|
||||||
layouts: section.layouts.map((layout) => ({
|
|
||||||
xOffset: layout.xOffset,
|
|
||||||
yOffset: layout.yOffset,
|
|
||||||
width: layout.width,
|
|
||||||
height: layout.height,
|
|
||||||
parentSectionId: layout.parentSectionId,
|
|
||||||
layoutId: layout.layoutId,
|
|
||||||
})),
|
|
||||||
collapsed: collapseStates.at(0)?.collapsed ?? false,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
items: items.map(({ integrations: itemIntegrations, ...item }) =>
|
|
||||||
parseItem({
|
|
||||||
...item,
|
|
||||||
layouts: item.layouts.map((layout) => ({
|
|
||||||
xOffset: layout.xOffset,
|
|
||||||
yOffset: layout.yOffset,
|
|
||||||
width: layout.width,
|
|
||||||
height: layout.height,
|
|
||||||
layoutId: layout.layoutId,
|
|
||||||
sectionId: layout.sectionId,
|
|
||||||
})),
|
|
||||||
integrationIds: itemIntegrations.map((item) => item.integration.id),
|
|
||||||
advancedOptions: superjson.parse<BoardItemAdvancedOptions>(item.advancedOptions),
|
|
||||||
options: superjson.parse<Record<string, unknown>>(item.options),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const forKind = <T extends WidgetKind>(kind: T) =>
|
|
||||||
z.object({
|
|
||||||
kind: z.literal(kind),
|
|
||||||
options: z.record(z.string(), z.unknown()),
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputItemSchema = zodUnionFromArray(widgetKinds.map((kind) => forKind(kind))).and(sharedItemSchema);
|
|
||||||
|
|
||||||
const parseItem = (item: unknown) => {
|
|
||||||
const result = outputItemSchema.safeParse(item);
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error.message);
|
|
||||||
}
|
|
||||||
return result.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseSection = (section: unknown) => {
|
|
||||||
const result = sectionSchema.safeParse(section);
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
throw new Error(result.error.message);
|
|
||||||
}
|
|
||||||
return result.data;
|
|
||||||
};
|
|
||||||
@@ -56,7 +56,8 @@ export const generateResponsiveGridFor = ({
|
|||||||
dynamicSectionHeightMap.set(dynamicSection.id, result.height);
|
dynamicSectionHeightMap.set(dynamicSection.id, result.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width === previousWidth) {
|
// Return same positions for items in the current section
|
||||||
|
if (width >= previousWidth) {
|
||||||
return {
|
return {
|
||||||
height: Math.max(...itemsOfCurrentSection.map((item) => item.yOffset + item.height)),
|
height: Math.max(...itemsOfCurrentSection.map((item) => item.yOffset + item.height)),
|
||||||
items: newItems.concat(normalizedItems),
|
items: newItems.concat(normalizedItems),
|
||||||
|
|||||||
@@ -99,12 +99,11 @@ qlllmmmmmnnrrrst
|
|||||||
ulllmmmmmnnrrrvw
|
ulllmmmmmnnrrrvw
|
||||||
xyz äö`;
|
xyz äö`;
|
||||||
|
|
||||||
const eighteenColumns = `
|
// Just add two empty columns to the right
|
||||||
abbccccddddeeefghi
|
const eighteenColumns = sixteenColumns
|
||||||
jbbccccddddeeeklll
|
.split("\n")
|
||||||
mmmmmnnopqseeetlll
|
.map((line, index) => (index === 0 ? line : `${line} `))
|
||||||
mmmmmnnrrruvwxylll
|
.join("\n");
|
||||||
mmmmmnnrrrzäö `;
|
|
||||||
|
|
||||||
const tenColumns = `
|
const tenColumns = `
|
||||||
abbcccceee
|
abbcccceee
|
||||||
|
|||||||
@@ -35,11 +35,11 @@
|
|||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"ldapts": "8.0.9",
|
"ldapts": "8.0.9",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"next-auth": "5.0.0-beta.30",
|
"next-auth": "5.0.0-beta.30",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { usePathname } from "next/navigation";
|
|||||||
import type { RouterOutputs } from "@homarr/api";
|
import type { RouterOutputs } from "@homarr/api";
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
|
|
||||||
import { useEditMode } from "./edit-mode";
|
|
||||||
import { updateBoardName } from "./updater";
|
import { updateBoardName } from "./updater";
|
||||||
|
|
||||||
const BoardContext = createContext<{
|
const BoardContext = createContext<{
|
||||||
@@ -84,12 +83,10 @@ export const getCurrentLayout = (board: RouterOutputs["board"]["getBoardByName"]
|
|||||||
export const useCurrentLayout = () => {
|
export const useCurrentLayout = () => {
|
||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
const [currentLayout, setCurrentLayout] = useState(getCurrentLayout(board));
|
const [currentLayout, setCurrentLayout] = useState(getCurrentLayout(board));
|
||||||
const [isEditMode] = useEditMode();
|
|
||||||
|
|
||||||
const onResize = useCallback(() => {
|
const onResize = useCallback(() => {
|
||||||
if (isEditMode && board.layoutMode === "auto") return;
|
|
||||||
setCurrentLayout(getCurrentLayout(board));
|
setCurrentLayout(getCurrentLayout(board));
|
||||||
}, [board, isEditMode]);
|
}, [board]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
@@ -100,10 +97,6 @@ export const useCurrentLayout = () => {
|
|||||||
};
|
};
|
||||||
}, [onResize]);
|
}, [onResize]);
|
||||||
|
|
||||||
if (board.layoutMode === "auto" && isEditMode && board.baseLayoutId !== null) {
|
|
||||||
return board.baseLayoutId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentLayout;
|
return currentLayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,12 @@
|
|||||||
"@paralleldrive/cuid2": "^3.1.0",
|
"@paralleldrive/cuid2": "^3.1.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"dns-caching": "^0.2.7",
|
"dns-caching": "^0.2.7",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"octokit": "^5.0.5",
|
"octokit": "^5.0.5",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"undici": "7.16.0",
|
"undici": "7.16.0",
|
||||||
"zod": "^4.1.12",
|
"zod": "^4.1.13",
|
||||||
"zod-validation-error": "^5.0.0"
|
"zod-validation-error": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@t3-oss/env-nextjs": "^0.13.8",
|
"@t3-oss/env-nextjs": "^0.13.8",
|
||||||
"ioredis": "5.8.2",
|
"ioredis": "5.8.2",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -29,20 +29,20 @@
|
|||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@tanstack/react-query": "^5.90.10",
|
"@tanstack/react-query": "^5.90.11",
|
||||||
"@trpc/client": "^11.7.1",
|
"@trpc/client": "^11.7.2",
|
||||||
"@trpc/server": "^11.7.1",
|
"@trpc/server": "^11.7.2",
|
||||||
"@trpc/tanstack-react-query": "^11.7.1",
|
"@trpc/tanstack-react-query": "^11.7.2",
|
||||||
"node-cron": "^4.2.1",
|
"node-cron": "^4.2.1",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/react": "19.2.6",
|
"@types/react": "19.2.7",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE `board` ADD `layout_mode` varchar(32) DEFAULT 'custom' NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE `board` ADD `base_layout_id` varchar(64);--> statement-breakpoint
|
|
||||||
ALTER TABLE `board` ADD CONSTRAINT `board_base_layout_id_layout_id_fk` FOREIGN KEY (`base_layout_id`) REFERENCES `layout`(`id`) ON DELETE set null ON UPDATE no action;
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -260,13 +260,6 @@
|
|||||||
"when": 1760968518445,
|
"when": 1760968518445,
|
||||||
"tag": "0036_add_app_reference_to_integration",
|
"tag": "0036_add_app_reference_to_integration",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 37,
|
|
||||||
"version": "5",
|
|
||||||
"when": 1764190331048,
|
|
||||||
"tag": "0037_add_auto_layout_mode",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE "board" ADD COLUMN "layout_mode" varchar(32) DEFAULT 'custom' NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "board" ADD COLUMN "base_layout_id" varchar(64);--> statement-breakpoint
|
|
||||||
ALTER TABLE "board" ADD CONSTRAINT "board_base_layout_id_layout_id_fk" FOREIGN KEY ("base_layout_id") REFERENCES "public"."layout"("id") ON DELETE set null ON UPDATE no action;
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -22,13 +22,6 @@
|
|||||||
"when": 1760968530084,
|
"when": 1760968530084,
|
||||||
"tag": "0002_add_app_reference_to_integration",
|
"tag": "0002_add_app_reference_to_integration",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 3,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1764190350662,
|
|
||||||
"tag": "0003_add_auto_layout_mode",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE `board` ADD `layout_mode` text DEFAULT 'custom' NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE `board` ADD `base_layout_id` text REFERENCES layout(id);
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -246,13 +246,6 @@
|
|||||||
"when": 1760968503571,
|
"when": 1760968503571,
|
||||||
"tag": "0034_add_app_reference_to_integration",
|
"tag": "0034_add_app_reference_to_integration",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 35,
|
|
||||||
"version": "6",
|
|
||||||
"when": 1764190313293,
|
|
||||||
"tag": "0035_add_auto_layout_mode",
|
|
||||||
"breakpoints": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -49,10 +49,10 @@
|
|||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@paralleldrive/cuid2": "^3.1.0",
|
"@paralleldrive/cuid2": "^3.1.0",
|
||||||
"@testcontainers/mysql": "^11.8.1",
|
"@testcontainers/mysql": "^11.9.0",
|
||||||
"@testcontainers/postgresql": "^11.8.1",
|
"@testcontainers/postgresql": "^11.9.0",
|
||||||
"better-sqlite3": "^12.4.1",
|
"better-sqlite3": "^12.4.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"drizzle-kit": "^0.31.7",
|
"drizzle-kit": "^0.31.7",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"drizzle-zod": "^0.8.3",
|
"drizzle-zod": "^0.8.3",
|
||||||
"mysql2": "3.15.3",
|
"mysql2": "3.15.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"superjson": "2.2.5"
|
"superjson": "2.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -17,11 +17,16 @@ import {
|
|||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/mysql-core";
|
} from "drizzle-orm/mysql-core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
backgroundImageAttachments,
|
||||||
|
backgroundImageRepeats,
|
||||||
|
backgroundImageSizes,
|
||||||
|
emptySuperJSON,
|
||||||
|
} from "@homarr/definitions";
|
||||||
import type {
|
import type {
|
||||||
BackgroundImageAttachment,
|
BackgroundImageAttachment,
|
||||||
BackgroundImageRepeat,
|
BackgroundImageRepeat,
|
||||||
BackgroundImageSize,
|
BackgroundImageSize,
|
||||||
BoardLayoutMode,
|
|
||||||
BoardPermission,
|
BoardPermission,
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
GroupPermissionKey,
|
GroupPermissionKey,
|
||||||
@@ -34,12 +39,6 @@ import type {
|
|||||||
SupportedAuthProvider,
|
SupportedAuthProvider,
|
||||||
WidgetKind,
|
WidgetKind,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import {
|
|
||||||
backgroundImageAttachments,
|
|
||||||
backgroundImageRepeats,
|
|
||||||
backgroundImageSizes,
|
|
||||||
emptySuperJSON,
|
|
||||||
} from "@homarr/definitions";
|
|
||||||
|
|
||||||
const customBlob = customType<{ data: Buffer }>({
|
const customBlob = customType<{ data: Buffer }>({
|
||||||
dataType() {
|
dataType() {
|
||||||
@@ -290,11 +289,6 @@ export const boards = mysqlTable("board", {
|
|||||||
iconColor: text(),
|
iconColor: text(),
|
||||||
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
||||||
disableStatus: boolean().default(false).notNull(),
|
disableStatus: boolean().default(false).notNull(),
|
||||||
// This has to be custom as the previous behaviour was custom
|
|
||||||
layoutMode: varchar({ length: 32 }).$type<BoardLayoutMode>().default("custom").notNull(),
|
|
||||||
baseLayoutId: varchar({ length: 64 }).references((): AnyMySqlColumn => layouts.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const boardUserPermissions = mysqlTable(
|
export const boardUserPermissions = mysqlTable(
|
||||||
@@ -684,10 +678,6 @@ export const boardRelations = relations(boards, ({ many, one }) => ({
|
|||||||
userPermissions: many(boardUserPermissions),
|
userPermissions: many(boardUserPermissions),
|
||||||
groupPermissions: many(boardGroupPermissions),
|
groupPermissions: many(boardGroupPermissions),
|
||||||
layouts: many(layouts),
|
layouts: many(layouts),
|
||||||
baseLayout: one(layouts, {
|
|
||||||
fields: [boards.baseLayoutId],
|
|
||||||
references: [layouts.id],
|
|
||||||
}),
|
|
||||||
groupHomes: many(groups, {
|
groupHomes: many(groups, {
|
||||||
relationName: "groupRelations__board__homeBoardId",
|
relationName: "groupRelations__board__homeBoardId",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -16,11 +16,16 @@ import {
|
|||||||
varchar,
|
varchar,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
backgroundImageAttachments,
|
||||||
|
backgroundImageRepeats,
|
||||||
|
backgroundImageSizes,
|
||||||
|
emptySuperJSON,
|
||||||
|
} from "@homarr/definitions";
|
||||||
import type {
|
import type {
|
||||||
BackgroundImageAttachment,
|
BackgroundImageAttachment,
|
||||||
BackgroundImageRepeat,
|
BackgroundImageRepeat,
|
||||||
BackgroundImageSize,
|
BackgroundImageSize,
|
||||||
BoardLayoutMode,
|
|
||||||
BoardPermission,
|
BoardPermission,
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
GroupPermissionKey,
|
GroupPermissionKey,
|
||||||
@@ -33,12 +38,6 @@ import type {
|
|||||||
SupportedAuthProvider,
|
SupportedAuthProvider,
|
||||||
WidgetKind,
|
WidgetKind,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import {
|
|
||||||
backgroundImageAttachments,
|
|
||||||
backgroundImageRepeats,
|
|
||||||
backgroundImageSizes,
|
|
||||||
emptySuperJSON,
|
|
||||||
} from "@homarr/definitions";
|
|
||||||
|
|
||||||
const customBlob = customType<{ data: Buffer }>({
|
const customBlob = customType<{ data: Buffer }>({
|
||||||
dataType() {
|
dataType() {
|
||||||
@@ -289,11 +288,6 @@ export const boards = pgTable("board", {
|
|||||||
iconColor: text(),
|
iconColor: text(),
|
||||||
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
||||||
disableStatus: boolean().default(false).notNull(),
|
disableStatus: boolean().default(false).notNull(),
|
||||||
// This has to be custom as the previous behaviour was custom
|
|
||||||
layoutMode: varchar({ length: 32 }).$type<BoardLayoutMode>().default("custom").notNull(),
|
|
||||||
baseLayoutId: varchar({ length: 64 }).references((): AnyPgColumn => layouts.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const boardUserPermissions = pgTable(
|
export const boardUserPermissions = pgTable(
|
||||||
@@ -683,10 +677,6 @@ export const boardRelations = relations(boards, ({ many, one }) => ({
|
|||||||
userPermissions: many(boardUserPermissions),
|
userPermissions: many(boardUserPermissions),
|
||||||
groupPermissions: many(boardGroupPermissions),
|
groupPermissions: many(boardGroupPermissions),
|
||||||
layouts: many(layouts),
|
layouts: many(layouts),
|
||||||
baseLayout: one(layouts, {
|
|
||||||
fields: [boards.baseLayoutId],
|
|
||||||
references: [layouts.id],
|
|
||||||
}),
|
|
||||||
groupHomes: many(groups, {
|
groupHomes: many(groups, {
|
||||||
relationName: "groupRelations__board__homeBoardId",
|
relationName: "groupRelations__board__homeBoardId",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ import { relations, sql } from "drizzle-orm";
|
|||||||
import type { AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
import type { AnySQLiteColumn } from "drizzle-orm/sqlite-core";
|
||||||
import { blob, index, int, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
import { blob, index, int, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
backgroundImageAttachments,
|
||||||
|
backgroundImageRepeats,
|
||||||
|
backgroundImageSizes,
|
||||||
|
emptySuperJSON,
|
||||||
|
} from "@homarr/definitions";
|
||||||
import type {
|
import type {
|
||||||
BackgroundImageAttachment,
|
BackgroundImageAttachment,
|
||||||
BackgroundImageRepeat,
|
BackgroundImageRepeat,
|
||||||
BackgroundImageSize,
|
BackgroundImageSize,
|
||||||
BoardLayoutMode,
|
|
||||||
BoardPermission,
|
BoardPermission,
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
GroupPermissionKey,
|
GroupPermissionKey,
|
||||||
@@ -22,12 +27,6 @@ import type {
|
|||||||
SupportedAuthProvider,
|
SupportedAuthProvider,
|
||||||
WidgetKind,
|
WidgetKind,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import {
|
|
||||||
backgroundImageAttachments,
|
|
||||||
backgroundImageRepeats,
|
|
||||||
backgroundImageSizes,
|
|
||||||
emptySuperJSON,
|
|
||||||
} from "@homarr/definitions";
|
|
||||||
|
|
||||||
export const apiKeys = sqliteTable("apiKey", {
|
export const apiKeys = sqliteTable("apiKey", {
|
||||||
id: text().notNull().primaryKey(),
|
id: text().notNull().primaryKey(),
|
||||||
@@ -275,11 +274,6 @@ export const boards = sqliteTable("board", {
|
|||||||
iconColor: text(),
|
iconColor: text(),
|
||||||
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
itemRadius: text().$type<MantineSize>().default("lg").notNull(),
|
||||||
disableStatus: int({ mode: "boolean" }).default(false).notNull(),
|
disableStatus: int({ mode: "boolean" }).default(false).notNull(),
|
||||||
// This has to be custom as the previous behaviour was custom
|
|
||||||
layoutMode: text().$type<BoardLayoutMode>().default("custom").notNull(),
|
|
||||||
baseLayoutId: text().references((): AnySQLiteColumn => layouts.id, {
|
|
||||||
onDelete: "set null",
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const boardUserPermissions = sqliteTable(
|
export const boardUserPermissions = sqliteTable(
|
||||||
@@ -669,10 +663,6 @@ export const boardRelations = relations(boards, ({ many, one }) => ({
|
|||||||
userPermissions: many(boardUserPermissions),
|
userPermissions: many(boardUserPermissions),
|
||||||
groupPermissions: many(boardGroupPermissions),
|
groupPermissions: many(boardGroupPermissions),
|
||||||
layouts: many(layouts),
|
layouts: many(layouts),
|
||||||
baseLayout: one(layouts, {
|
|
||||||
fields: [boards.baseLayoutId],
|
|
||||||
references: [layouts.id],
|
|
||||||
}),
|
|
||||||
groupHomes: many(groups, {
|
groupHomes: many(groups, {
|
||||||
relationName: "groupRelations__board__homeBoardId",
|
relationName: "groupRelations__board__homeBoardId",
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"fast-xml-parser": "^5.3.2",
|
"fast-xml-parser": "^5.3.2",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ export const backgroundImageRepeats = createDefinition(["repeat", "repeat-x", "r
|
|||||||
export const backgroundImageSizes = createDefinition(["cover", "contain"], {
|
export const backgroundImageSizes = createDefinition(["cover", "contain"], {
|
||||||
defaultValue: "cover",
|
defaultValue: "cover",
|
||||||
});
|
});
|
||||||
export const boardLayoutModes = createDefinition(["auto", "custom"], { defaultValue: "auto" });
|
|
||||||
|
|
||||||
export type BackgroundImageAttachment = inferDefinitionType<typeof backgroundImageAttachments>;
|
export type BackgroundImageAttachment = inferDefinitionType<typeof backgroundImageAttachments>;
|
||||||
export type BackgroundImageRepeat = inferDefinitionType<typeof backgroundImageRepeats>;
|
export type BackgroundImageRepeat = inferDefinitionType<typeof backgroundImageRepeats>;
|
||||||
export type BackgroundImageSize = inferDefinitionType<typeof backgroundImageSizes>;
|
export type BackgroundImageSize = inferDefinitionType<typeof backgroundImageSizes>;
|
||||||
export type BoardLayoutMode = inferDefinitionType<typeof boardLayoutModes>;
|
|
||||||
@@ -144,6 +144,7 @@ export type HomarrDocumentationPath =
|
|||||||
| "/docs/getting-started/installation/proxmox"
|
| "/docs/getting-started/installation/proxmox"
|
||||||
| "/docs/getting-started/installation/qnap"
|
| "/docs/getting-started/installation/qnap"
|
||||||
| "/docs/getting-started/installation/railway"
|
| "/docs/getting-started/installation/railway"
|
||||||
|
| "/docs/getting-started/installation/runtipi"
|
||||||
| "/docs/getting-started/installation/saltbox"
|
| "/docs/getting-started/installation/saltbox"
|
||||||
| "/docs/getting-started/installation/source"
|
| "/docs/getting-started/installation/source"
|
||||||
| "/docs/getting-started/installation/synology"
|
| "/docs/getting-started/installation/synology"
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/form": "^8.3.8",
|
"@mantine/form": "^8.3.9",
|
||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "^1.3.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -30,9 +30,9 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"tsdav": "^2.1.6",
|
"tsdav": "^2.1.6",
|
||||||
"undici": "7.16.0",
|
"undici": "7.16.0",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/core": "workspace:^0.1.0",
|
"@homarr/core": "workspace:^0.1.0",
|
||||||
"superjson": "2.2.5",
|
"superjson": "2.2.6",
|
||||||
"winston": "3.18.3",
|
"winston": "3.18.3",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/api": "workspace:^0.1.0",
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
|
||||||
"@homarr/form": "workspace:^0.1.0",
|
"@homarr/form": "workspace:^0.1.0",
|
||||||
"@homarr/forms-collection": "workspace:^0.1.0",
|
"@homarr/forms-collection": "workspace:^0.1.0",
|
||||||
"@homarr/modals": "workspace:^0.1.0",
|
"@homarr/modals": "workspace:^0.1.0",
|
||||||
@@ -34,13 +33,13 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -1,28 +1,13 @@
|
|||||||
import {
|
import { Button, Group, InputWrapper, Slider, Stack, Switch, TextInput } from "@mantine/core";
|
||||||
Badge,
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Fieldset,
|
|
||||||
Group,
|
|
||||||
InputWrapper,
|
|
||||||
RadioGroup,
|
|
||||||
SimpleGrid,
|
|
||||||
Slider,
|
|
||||||
Stack,
|
|
||||||
Switch,
|
|
||||||
TextInput,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { useDebouncedValue } from "@mantine/hooks";
|
import { useDebouncedValue } from "@mantine/hooks";
|
||||||
import { IconAlertTriangle, IconCircleCheck } from "@tabler/icons-react";
|
import { IconAlertTriangle, IconCircleCheck } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { boardLayoutModes } from "@homarr/definitions";
|
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
import { createModal } from "@homarr/modals";
|
import { createModal } from "@homarr/modals";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useI18n } from "@homarr/translation/client";
|
import { useI18n } from "@homarr/translation/client";
|
||||||
import { CustomRadioCard } from "@homarr/ui";
|
|
||||||
import { boardColumnCountSchema, boardCreateSchema, boardNameSchema } from "@homarr/validation/board";
|
import { boardColumnCountSchema, boardCreateSchema, boardNameSchema } from "@homarr/validation/board";
|
||||||
|
|
||||||
export const AddBoardModal = createModal(({ actions }) => {
|
export const AddBoardModal = createModal(({ actions }) => {
|
||||||
@@ -33,7 +18,6 @@ export const AddBoardModal = createModal(({ actions }) => {
|
|||||||
name: "",
|
name: "",
|
||||||
columnCount: 10,
|
columnCount: 10,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
layoutMode: boardLayoutModes.defaultValue,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { mutate, isPending } = clientApi.board.createBoard.useMutation({
|
const { mutate, isPending } = clientApi.board.createBoard.useMutation({
|
||||||
@@ -80,38 +64,14 @@ export const AddBoardModal = createModal(({ actions }) => {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<InputWrapper label={t("board.field.columnCount.label")} {...form.getInputProps("columnCount")}>
|
||||||
<Fieldset legend={t("board.setting.section.layout.title")}>
|
<Slider
|
||||||
<Stack gap="sm">
|
min={boardColumnCountSchema.minValue ?? undefined}
|
||||||
<RadioGroup {...form.getInputProps("layoutMode")}>
|
max={boardColumnCountSchema.maxValue ?? undefined}
|
||||||
<SimpleGrid cols={{ sm: 2, xs: 1 }} spacing="md">
|
step={1}
|
||||||
{boardLayoutModes.values.map((mode) => (
|
{...form.getInputProps("columnCount")}
|
||||||
<Box w="100%" key={mode} pos="relative">
|
/>
|
||||||
<CustomRadioCard
|
</InputWrapper>
|
||||||
value={mode}
|
|
||||||
label={t(`board.field.layoutMode.option.${mode}.label`)}
|
|
||||||
description={t(`board.field.layoutMode.option.${mode}.description`)}
|
|
||||||
/>
|
|
||||||
{mode === boardLayoutModes.defaultValue && (
|
|
||||||
<Badge pos="absolute" top="8px" right="8px" variant="light" size="sm">
|
|
||||||
{t("common.select.badge.recommended")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
<InputWrapper label={t("board.field.columnCount.label")} {...form.getInputProps("columnCount")}>
|
|
||||||
<Slider
|
|
||||||
min={boardColumnCountSchema.minValue ?? undefined}
|
|
||||||
max={boardColumnCountSchema.maxValue ?? undefined}
|
|
||||||
step={1}
|
|
||||||
{...form.getInputProps("columnCount")}
|
|
||||||
/>
|
|
||||||
</InputWrapper>
|
|
||||||
</Stack>
|
|
||||||
</Fieldset>
|
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
label={t("board.field.isPublic.label")}
|
label={t("board.field.isPublic.label")}
|
||||||
@@ -132,7 +92,6 @@ export const AddBoardModal = createModal(({ actions }) => {
|
|||||||
);
|
);
|
||||||
}).withOptions({
|
}).withOptions({
|
||||||
defaultTitle: (t) => t("management.page.board.action.new.label"),
|
defaultTitle: (t) => t("management.page.board.action.new.label"),
|
||||||
size: "lg",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useBoardNameStatus = (name: string) => {
|
export const useBoardNameStatus = (name: string) => {
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@mantine/hooks": "^8.3.8",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"react": "19.2.0"
|
"react": "19.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/notifications": "^8.3.8",
|
"@mantine/notifications": "^8.3.9",
|
||||||
"@tabler/icons-react": "^3.35.0"
|
"@tabler/icons-react": "^3.35.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -37,14 +37,14 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@mantine/hooks": "^8.3.8",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"adm-zip": "0.5.16",
|
"adm-zip": "0.5.16",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"superjson": "2.2.5",
|
"superjson": "2.2.6",
|
||||||
"zod": "^4.1.12",
|
"zod": "^4.1.13",
|
||||||
"zod-form-data": "^3.0.1"
|
"zod-form-data": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/common": "workspace:^0.1.0",
|
"@homarr/common": "workspace:^0.1.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"@homarr/definitions": "workspace:^",
|
"@homarr/definitions": "workspace:^",
|
||||||
"@homarr/log": "workspace:^",
|
"@homarr/log": "workspace:^",
|
||||||
"ioredis": "5.8.2",
|
"ioredis": "5.8.2",
|
||||||
"superjson": "2.2.5"
|
"superjson": "2.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -32,9 +32,9 @@
|
|||||||
"@homarr/redis": "workspace:^0.1.0",
|
"@homarr/redis": "workspace:^0.1.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"octokit": "^5.0.5",
|
"octokit": "^5.0.5",
|
||||||
"superjson": "2.2.5",
|
"superjson": "2.2.6",
|
||||||
"undici": "7.16.0",
|
"undici": "7.16.0",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
"@homarr/api": "workspace:^0.1.0",
|
"@homarr/api": "workspace:^0.1.0",
|
||||||
"@homarr/db": "workspace:^0.1.0",
|
"@homarr/db": "workspace:^0.1.0",
|
||||||
"@homarr/server-settings": "workspace:^0.1.0",
|
"@homarr/server-settings": "workspace:^0.1.0",
|
||||||
"@mantine/dates": "^8.3.8",
|
"@mantine/dates": "^8.3.9",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0"
|
"react-dom": "19.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,12 +33,12 @@
|
|||||||
"@homarr/settings": "workspace:^0.1.0",
|
"@homarr/settings": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@mantine/hooks": "^8.3.8",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"@mantine/spotlight": "^8.3.8",
|
"@mantine/spotlight": "^8.3.9",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"jotai": "^2.15.1",
|
"jotai": "^2.15.1",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"use-deep-compare-effect": "^1.8.1"
|
"use-deep-compare-effect": "^1.8.1"
|
||||||
|
|||||||
@@ -32,8 +32,8 @@
|
|||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"next-intl": "4.5.5",
|
"next-intl": "4.5.6",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0"
|
"react-dom": "19.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2793,23 +2793,6 @@
|
|||||||
"label": "Disable app status",
|
"label": "Disable app status",
|
||||||
"description": "Disables the status check for all apps on this board"
|
"description": "Disables the status check for all apps on this board"
|
||||||
},
|
},
|
||||||
"layoutMode": {
|
|
||||||
"label": "Layout mode",
|
|
||||||
"option": {
|
|
||||||
"auto": {
|
|
||||||
"label": "Auto",
|
|
||||||
"description": "Position and size of all layouts are calculated from the base layout."
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"label": "Custom",
|
|
||||||
"description": "Position and size must be configured for all layouts separately."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"baseLayout": {
|
|
||||||
"label": "Base layout",
|
|
||||||
"description": "The layout will be used to calculate positions and sizes for other layouts."
|
|
||||||
},
|
|
||||||
"columnCount": {
|
"columnCount": {
|
||||||
"label": "Column count"
|
"label": "Column count"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2232,7 +2232,7 @@
|
|||||||
"unknown": "Desconocido",
|
"unknown": "Desconocido",
|
||||||
"pending": "Pendiente",
|
"pending": "Pendiente",
|
||||||
"processing": "Procesando",
|
"processing": "Procesando",
|
||||||
"requested": "",
|
"requested": "Solicitado",
|
||||||
"partiallyAvailable": "Parcial",
|
"partiallyAvailable": "Parcial",
|
||||||
"available": "Disponible",
|
"available": "Disponible",
|
||||||
"blacklisted": "Lista negra",
|
"blacklisted": "Lista negra",
|
||||||
|
|||||||
@@ -30,12 +30,12 @@
|
|||||||
"@homarr/log": "workspace:^0.1.0",
|
"@homarr/log": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@mantine/dates": "^8.3.8",
|
"@mantine/dates": "^8.3.9",
|
||||||
"@mantine/hooks": "^8.3.8",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"svgson": "^5.3.1"
|
"svgson": "^5.3.1"
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ export { MaskedImage } from "./masked-image";
|
|||||||
export { MaskedOrNormalImage } from "./masked-or-normal-image";
|
export { MaskedOrNormalImage } from "./masked-or-normal-image";
|
||||||
export { LanguageIcon } from "./language-icon";
|
export { LanguageIcon } from "./language-icon";
|
||||||
export { Link } from "./link";
|
export { Link } from "./link";
|
||||||
export * from "./radio";
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
.root {
|
|
||||||
position: relative;
|
|
||||||
padding: var(--mantine-spacing-md);
|
|
||||||
transition: border-color 150ms ease;
|
|
||||||
|
|
||||||
&[data-checked] {
|
|
||||||
border-color: var(--mantine-primary-color-filled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin hover {
|
|
||||||
@mixin light {
|
|
||||||
background-color: var(--mantine-color-gray-0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin dark {
|
|
||||||
background-color: var(--mantine-color-dark-6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-family: var(--mantine-font-family-monospace);
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: var(--mantine-font-size-md);
|
|
||||||
line-height: 1.3;
|
|
||||||
color: var(--mantine-color-bright);
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
margin-top: 8px;
|
|
||||||
color: var(--mantine-color-dimmed);
|
|
||||||
font-size: var(--mantine-font-size-xs);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import type { RadioCardProps } from "@mantine/core";
|
|
||||||
import { Group, RadioCard, RadioIndicator, Text } from "@mantine/core";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import classes from "./radio.module.css";
|
|
||||||
|
|
||||||
interface CustomRadioCardProps extends RadioCardProps {
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomRadioCard = (props: CustomRadioCardProps) => {
|
|
||||||
return (
|
|
||||||
<RadioCard className={clsx(classes.root, props.className)} {...props}>
|
|
||||||
<Group wrap="nowrap" align="flex-start">
|
|
||||||
<RadioIndicator />
|
|
||||||
<div>
|
|
||||||
<Text className={classes.label}>{props.label}</Text>
|
|
||||||
<Text className={classes.description}>{props.description}</Text>
|
|
||||||
</div>
|
|
||||||
</Group>
|
|
||||||
</RadioCard>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@homarr/definitions": "workspace:^0.1.0",
|
"@homarr/definitions": "workspace:^0.1.0",
|
||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"zod": "^4.1.12",
|
"zod": "^4.1.13",
|
||||||
"zod-form-data": "^3.0.1"
|
"zod-form-data": "^3.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
backgroundImageAttachments,
|
backgroundImageAttachments,
|
||||||
backgroundImageRepeats,
|
backgroundImageRepeats,
|
||||||
backgroundImageSizes,
|
backgroundImageSizes,
|
||||||
boardLayoutModes,
|
|
||||||
boardPermissions,
|
boardPermissions,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
|
|
||||||
@@ -80,9 +79,6 @@ export const boardSaveLayoutsSchema = z.object({
|
|||||||
breakpoint: z.number().min(0).max(32767),
|
breakpoint: z.number().min(0).max(32767),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
// If not send it should fallback to the legacy behavior which is "custom"
|
|
||||||
layoutMode: z.enum(boardLayoutModes.values).default("custom"),
|
|
||||||
baseLayoutId: z.string().nullish(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const boardSaveSchema = z.object({
|
export const boardSaveSchema = z.object({
|
||||||
@@ -94,7 +90,6 @@ export const boardSaveSchema = z.object({
|
|||||||
export const boardCreateSchema = z.object({
|
export const boardCreateSchema = z.object({
|
||||||
name: boardNameSchema,
|
name: boardNameSchema,
|
||||||
columnCount: boardColumnCountSchema,
|
columnCount: boardColumnCountSchema,
|
||||||
layoutMode: z.enum(boardLayoutModes.values).default(boardLayoutModes.defaultValue),
|
|
||||||
isPublic: z.boolean(),
|
isPublic: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,9 @@
|
|||||||
"@homarr/translation": "workspace:^0.1.0",
|
"@homarr/translation": "workspace:^0.1.0",
|
||||||
"@homarr/ui": "workspace:^0.1.0",
|
"@homarr/ui": "workspace:^0.1.0",
|
||||||
"@homarr/validation": "workspace:^0.1.0",
|
"@homarr/validation": "workspace:^0.1.0",
|
||||||
"@mantine/charts": "^8.3.8",
|
"@mantine/charts": "^8.3.9",
|
||||||
"@mantine/core": "^8.3.8",
|
"@mantine/core": "^8.3.9",
|
||||||
"@mantine/hooks": "^8.3.8",
|
"@mantine/hooks": "^8.3.9",
|
||||||
"@tabler/icons-react": "^3.35.0",
|
"@tabler/icons-react": "^3.35.0",
|
||||||
"@tiptap/extension-color": "2.27.1",
|
"@tiptap/extension-color": "2.27.1",
|
||||||
"@tiptap/extension-highlight": "2.27.1",
|
"@tiptap/extension-highlight": "2.27.1",
|
||||||
@@ -72,13 +72,13 @@
|
|||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
"mantine-form-zod-resolver": "^1.3.0",
|
"mantine-form-zod-resolver": "^1.3.0",
|
||||||
"mantine-react-table": "2.0.0-beta.9",
|
"mantine-react-table": "2.0.0-beta.9",
|
||||||
"next": "16.0.3",
|
"next": "16.0.7",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"recharts": "^2.15.4",
|
"recharts": "^2.15.4",
|
||||||
"video.js": "^8.23.4",
|
"video.js": "^8.23.4",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||||
|
|||||||
Generated
+599
-652
File diff suppressed because it is too large.
Load diff
+3
-1
@@ -2,5 +2,7 @@ packages:
|
|||||||
- apps/*
|
- apps/*
|
||||||
- packages/*
|
- packages/*
|
||||||
- tooling/*
|
- tooling/*
|
||||||
|
|
||||||
minimumReleaseAge: 4320 # Only download deps if they are at least 3 days old (in minutes)
|
minimumReleaseAge: 4320 # Only download deps if they are at least 3 days old (in minutes)
|
||||||
|
minimumReleaseAgeExclude:
|
||||||
|
- next
|
||||||
|
- "@next/eslint-plugin-next"
|
||||||
@@ -1 +1 @@
|
|||||||
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/uLine truncated
|
[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"tancak","avatar_url":"https://avatars.githubusercontent.com/u/11760749?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"alotlikebeans","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/uLine truncated
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"prettier": "@homarr/prettier-config",
|
"prettier": "@homarr/prettier-config",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/eslint-plugin-next": "16.0.3",
|
"@next/eslint-plugin-next": "16.0.7",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-config-turbo": "^2.6.1",
|
"eslint-config-turbo": "^2.6.1",
|
||||||
"eslint-plugin-import": "^2.32.0",
|
"eslint-plugin-import": "^2.32.0",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||||
"prettier-plugin-packagejson": "^2.5.19",
|
"prettier-plugin-packagejson": "^2.5.20",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user