feat: added bordercolor option for dynamic section (#2334)
This commit is contained in:
@@ -9,6 +9,9 @@ export class DynamicSectionMockBuilder {
|
|||||||
this.section = {
|
this.section = {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
kind: "dynamic",
|
kind: "dynamic",
|
||||||
|
options: {
|
||||||
|
borderColor: "",
|
||||||
|
},
|
||||||
layouts: [],
|
layouts: [],
|
||||||
...section,
|
...section,
|
||||||
} satisfies DynamicSection;
|
} satisfies DynamicSection;
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ interface Props {
|
|||||||
export const BoardDynamicSection = ({ section }: Props) => {
|
export const BoardDynamicSection = ({ section }: Props) => {
|
||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
const currentLayoutId = useCurrentLayout();
|
const currentLayoutId = useCurrentLayout();
|
||||||
|
const options = section.options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="grid-stack-item-content">
|
<Box className="grid-stack-item-content">
|
||||||
<Card
|
<Card
|
||||||
@@ -25,6 +27,7 @@ export const BoardDynamicSection = ({ section }: Props) => {
|
|||||||
root: {
|
root: {
|
||||||
"--opacity": board.opacity / 100,
|
"--opacity": board.opacity / 100,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
"--border-color": options.borderColor !== "" ? options.borderColor : undefined,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
radius={board.itemRadius}
|
radius={board.itemRadius}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export const addDynamicSectionCallback = () => (board: Board) => {
|
|||||||
const newSection = {
|
const newSection = {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
kind: "dynamic",
|
kind: "dynamic",
|
||||||
|
options: {
|
||||||
|
borderColor: "",
|
||||||
|
},
|
||||||
layouts: createDynamicSectionLayouts(board, firstSection),
|
layouts: createDynamicSectionLayouts(board, firstSection),
|
||||||
} satisfies DynamicSection;
|
} satisfies DynamicSection;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
import { useUpdateBoard } from "@homarr/boards/updater";
|
import { useUpdateBoard } from "@homarr/boards/updater";
|
||||||
|
import type { dynamicSectionOptionsSchema } from "@homarr/validation";
|
||||||
|
|
||||||
import { addDynamicSectionCallback } from "./actions/add-dynamic-section";
|
import { addDynamicSectionCallback } from "./actions/add-dynamic-section";
|
||||||
import type { RemoveDynamicSectionInput } from "./actions/remove-dynamic-section";
|
import type { RemoveDynamicSectionInput } from "./actions/remove-dynamic-section";
|
||||||
import { removeDynamicSectionCallback } from "./actions/remove-dynamic-section";
|
import { removeDynamicSectionCallback } from "./actions/remove-dynamic-section";
|
||||||
|
|
||||||
|
interface UpdateDynamicOptions {
|
||||||
|
itemId: string;
|
||||||
|
newOptions: z.infer<typeof dynamicSectionOptionsSchema>;
|
||||||
|
}
|
||||||
|
|
||||||
export const useDynamicSectionActions = () => {
|
export const useDynamicSectionActions = () => {
|
||||||
const { updateBoard } = useUpdateBoard();
|
const { updateBoard } = useUpdateBoard();
|
||||||
|
|
||||||
@@ -13,6 +20,16 @@ export const useDynamicSectionActions = () => {
|
|||||||
updateBoard(addDynamicSectionCallback());
|
updateBoard(addDynamicSectionCallback());
|
||||||
}, [updateBoard]);
|
}, [updateBoard]);
|
||||||
|
|
||||||
|
const updateDynamicSection = useCallback(
|
||||||
|
({ itemId, newOptions }: UpdateDynamicOptions) => {
|
||||||
|
updateBoard((previous) => ({
|
||||||
|
...previous,
|
||||||
|
sections: previous.sections.map((item) => (item.id !== itemId ? item : { ...item, options: newOptions })),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[updateBoard],
|
||||||
|
);
|
||||||
|
|
||||||
const removeDynamicSection = useCallback(
|
const removeDynamicSection = useCallback(
|
||||||
(input: RemoveDynamicSectionInput) => {
|
(input: RemoveDynamicSectionInput) => {
|
||||||
updateBoard(removeDynamicSectionCallback(input));
|
updateBoard(removeDynamicSectionCallback(input));
|
||||||
@@ -22,6 +39,7 @@ export const useDynamicSectionActions = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
addDynamicSection,
|
addDynamicSection,
|
||||||
|
updateDynamicSection,
|
||||||
removeDynamicSection,
|
removeDynamicSection,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, CloseButton, ColorInput, Group, Stack, useMantineTheme } from "@mantine/core";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
import { useZodForm } from "@homarr/form";
|
||||||
|
import { createModal } from "@homarr/modals";
|
||||||
|
import { useI18n } from "@homarr/translation/client";
|
||||||
|
import { dynamicSectionOptionsSchema } from "@homarr/validation";
|
||||||
|
|
||||||
|
interface ModalProps {
|
||||||
|
value: z.infer<typeof dynamicSectionOptionsSchema>;
|
||||||
|
onSuccessfulEdit: (value: z.infer<typeof dynamicSectionOptionsSchema>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DynamicSectionEditModal = createModal<ModalProps>(({ actions, innerProps }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
|
||||||
|
const form = useZodForm(dynamicSectionOptionsSchema, {
|
||||||
|
mode: "controlled",
|
||||||
|
initialValues: { ...innerProps.value },
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
onSubmit={form.onSubmit((values) => {
|
||||||
|
innerProps.onSuccessfulEdit(values);
|
||||||
|
actions.closeModal();
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<ColorInput
|
||||||
|
label={t("section.dynamic.option.borderColor.label")}
|
||||||
|
format="hex"
|
||||||
|
swatches={Object.values(theme.colors).map((color) => color[6])}
|
||||||
|
rightSection={
|
||||||
|
<CloseButton
|
||||||
|
onClick={() => form.setFieldValue("borderColor", "")}
|
||||||
|
style={{ display: form.getInputProps("borderColor").value ? undefined : "none" }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...form.getInputProps("borderColor")}
|
||||||
|
/>
|
||||||
|
<Group justify="end">
|
||||||
|
<Group justify="end" w={{ base: "100%", xs: "auto" }}>
|
||||||
|
<Button onClick={actions.closeModal} variant="subtle" color="gray">
|
||||||
|
{t("common.action.cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" color="teal">
|
||||||
|
{t("common.action.saveChanges")}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}).withOptions({
|
||||||
|
defaultTitle(t) {
|
||||||
|
return t("item.edit.title");
|
||||||
|
},
|
||||||
|
size: "lg",
|
||||||
|
});
|
||||||
@@ -1,22 +1,37 @@
|
|||||||
import { ActionIcon, Menu } from "@mantine/core";
|
import { ActionIcon, Menu } from "@mantine/core";
|
||||||
import { IconDotsVertical, IconTrash } from "@tabler/icons-react";
|
import { IconDotsVertical, IconPencil, IconTrash } from "@tabler/icons-react";
|
||||||
|
|
||||||
import { useEditMode } from "@homarr/boards/edit-mode";
|
import { useEditMode } from "@homarr/boards/edit-mode";
|
||||||
import { useConfirmModal } from "@homarr/modals";
|
import { useConfirmModal, useModalAction } from "@homarr/modals";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
|
|
||||||
import type { DynamicSectionItem } from "~/app/[locale]/boards/_types";
|
import type { DynamicSectionItem } from "~/app/[locale]/boards/_types";
|
||||||
import { useDynamicSectionActions } from "./dynamic-actions";
|
import { useDynamicSectionActions } from "./dynamic-actions";
|
||||||
|
import { DynamicSectionEditModal } from "./dynamic-edit-modal";
|
||||||
|
|
||||||
export const BoardDynamicSectionMenu = ({ section }: { section: DynamicSectionItem }) => {
|
export const BoardDynamicSectionMenu = ({ section }: { section: DynamicSectionItem }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const tDynamic = useScopedI18n("section.dynamic");
|
const tDynamic = useScopedI18n("section.dynamic");
|
||||||
const { removeDynamicSection } = useDynamicSectionActions();
|
const tItem = useScopedI18n("item");
|
||||||
|
const { openModal } = useModalAction(DynamicSectionEditModal);
|
||||||
|
const { updateDynamicSection, removeDynamicSection } = useDynamicSectionActions();
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
const [isEditMode] = useEditMode();
|
const [isEditMode] = useEditMode();
|
||||||
|
|
||||||
if (!isEditMode) return null;
|
if (!isEditMode) return null;
|
||||||
|
|
||||||
|
const openEditModal = () => {
|
||||||
|
openModal({
|
||||||
|
value: section.options,
|
||||||
|
onSuccessfulEdit: (options) => {
|
||||||
|
updateDynamicSection({
|
||||||
|
itemId: section.id,
|
||||||
|
newOptions: options,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const openRemoveModal = () => {
|
const openRemoveModal = () => {
|
||||||
openConfirmModal({
|
openConfirmModal({
|
||||||
title: tDynamic("remove.title"),
|
title: tDynamic("remove.title"),
|
||||||
@@ -35,6 +50,11 @@ export const BoardDynamicSectionMenu = ({ section }: { section: DynamicSectionIt
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown miw={128}>
|
<Menu.Dropdown miw={128}>
|
||||||
|
<Menu.Label>{tItem("menu.label.settings")}</Menu.Label>
|
||||||
|
<Menu.Item leftSection={<IconPencil size={16} />} onClick={openEditModal}>
|
||||||
|
{tItem("action.edit")}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Divider />
|
||||||
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
|
<Menu.Label c="red.6">{t("common.dangerZone")}</Menu.Label>
|
||||||
<Menu.Item c="red.6" leftSection={<IconTrash size={16} />} onClick={openRemoveModal}>
|
<Menu.Item c="red.6" leftSection={<IconTrash size={16} />} onClick={openRemoveModal}>
|
||||||
{tDynamic("action.remove")}
|
{tDynamic("action.remove")}
|
||||||
|
|||||||
@@ -27,7 +27,13 @@ import {
|
|||||||
users,
|
users,
|
||||||
} from "@homarr/db/schema";
|
} from "@homarr/db/schema";
|
||||||
import type { WidgetKind } from "@homarr/definitions";
|
import type { WidgetKind } from "@homarr/definitions";
|
||||||
import { everyoneGroup, getPermissionsWithChildren, getPermissionsWithParents, widgetKinds } from "@homarr/definitions";
|
import {
|
||||||
|
emptySuperJSON,
|
||||||
|
everyoneGroup,
|
||||||
|
getPermissionsWithChildren,
|
||||||
|
getPermissionsWithParents,
|
||||||
|
widgetKinds,
|
||||||
|
} 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";
|
||||||
import { oldmarrConfigSchema } from "@homarr/old-schema";
|
import { oldmarrConfigSchema } from "@homarr/old-schema";
|
||||||
@@ -736,6 +742,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
kind: section.kind,
|
kind: section.kind,
|
||||||
yOffset: section.kind !== "dynamic" ? section.yOffset : null,
|
yOffset: section.kind !== "dynamic" ? section.yOffset : null,
|
||||||
xOffset: section.kind === "dynamic" ? null : 0,
|
xOffset: section.kind === "dynamic" ? null : 0,
|
||||||
|
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
|
||||||
name: "name" in section ? section.name : null,
|
name: "name" in section ? section.name : null,
|
||||||
boardId: dbBoard.id,
|
boardId: dbBoard.id,
|
||||||
})),
|
})),
|
||||||
@@ -861,6 +868,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
.set({
|
.set({
|
||||||
yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null,
|
yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null,
|
||||||
xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null,
|
xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null,
|
||||||
|
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
|
||||||
name: prev?.kind === "category" && "name" in section ? section.name : null,
|
name: prev?.kind === "category" && "name" in section ? section.name : null,
|
||||||
})
|
})
|
||||||
.where(eq(schema.sections.id, section.id));
|
.where(eq(schema.sections.id, section.id));
|
||||||
@@ -934,6 +942,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
kind: section.kind,
|
kind: section.kind,
|
||||||
yOffset: section.kind !== "dynamic" ? section.yOffset : null,
|
yOffset: section.kind !== "dynamic" ? section.yOffset : null,
|
||||||
xOffset: section.kind === "dynamic" ? null : 0,
|
xOffset: section.kind === "dynamic" ? null : 0,
|
||||||
|
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
|
||||||
name: "name" in section ? section.name : null,
|
name: "name" in section ? section.name : null,
|
||||||
boardId: dbBoard.id,
|
boardId: dbBoard.id,
|
||||||
})),
|
})),
|
||||||
@@ -1069,6 +1078,7 @@ export const boardRouter = createTRPCRouter({
|
|||||||
.set({
|
.set({
|
||||||
yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null,
|
yOffset: prev?.kind !== "dynamic" && "yOffset" in section ? section.yOffset : null,
|
||||||
xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null,
|
xOffset: prev?.kind !== "dynamic" && "yOffset" in section ? 0 : null,
|
||||||
|
options: section.kind === "dynamic" ? superjson.stringify(section.options) : emptySuperJSON,
|
||||||
name: prev?.kind === "category" && "name" in section ? section.name : null,
|
name: prev?.kind === "category" && "name" in section ? section.name : null,
|
||||||
})
|
})
|
||||||
.where(eq(sections.id, section.id))
|
.where(eq(sections.id, section.id))
|
||||||
@@ -1561,6 +1571,7 @@ const getFullBoardWithWhereAsync = async (db: Database, where: SQL<unknown>, use
|
|||||||
...section,
|
...section,
|
||||||
xOffset: section.xOffset,
|
xOffset: section.xOffset,
|
||||||
yOffset: section.yOffset,
|
yOffset: section.yOffset,
|
||||||
|
options: superjson.parse(section.options ?? emptySuperJSON),
|
||||||
layouts: section.layouts.map((layout) => ({
|
layouts: section.layouts.map((layout) => ({
|
||||||
xOffset: layout.xOffset,
|
xOffset: layout.xOffset,
|
||||||
yOffset: layout.yOffset,
|
yOffset: layout.yOffset,
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `section` ADD `options` text DEFAULT ('{"json": {}}');
|
||||||
2020
packages/db/migrations/mysql/meta/0031_snapshot.json
Normal file
2020
packages/db/migrations/mysql/meta/0031_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -218,6 +218,13 @@
|
|||||||
"when": 1740256006328,
|
"when": 1740256006328,
|
||||||
"tag": "0030_migrate_item_and_section_for_layouts",
|
"tag": "0030_migrate_item_and_section_for_layouts",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 31,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1740784837957,
|
||||||
|
"tag": "0031_add_dynamic_section_options",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `section` ADD `options` text DEFAULT '{"json": {}}';
|
||||||
1940
packages/db/migrations/sqlite/meta/0031_snapshot.json
Normal file
1940
packages/db/migrations/sqlite/meta/0031_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -218,6 +218,13 @@
|
|||||||
"when": 1740255968549,
|
"when": 1740255968549,
|
||||||
"tag": "0030_migrate_item_and_section_for_layouts",
|
"tag": "0030_migrate_item_and_section_for_layouts",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 31,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1740784849045,
|
||||||
|
"tag": "0031_add_dynamic_section_options",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ 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,
|
||||||
@@ -33,7 +39,6 @@ import type {
|
|||||||
SupportedAuthProvider,
|
SupportedAuthProvider,
|
||||||
WidgetKind,
|
WidgetKind,
|
||||||
} from "@homarr/definitions";
|
} from "@homarr/definitions";
|
||||||
import { backgroundImageAttachments, backgroundImageRepeats, backgroundImageSizes } from "@homarr/definitions";
|
|
||||||
|
|
||||||
const customBlob = customType<{ data: Buffer }>({
|
const customBlob = customType<{ data: Buffer }>({
|
||||||
dataType() {
|
dataType() {
|
||||||
@@ -388,6 +393,7 @@ export const sections = mysqlTable("section", {
|
|||||||
xOffset: int(),
|
xOffset: int(),
|
||||||
yOffset: int(),
|
yOffset: int(),
|
||||||
name: text(),
|
name: text(),
|
||||||
|
options: text().default(emptySuperJSON),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sectionCollapseStates = mysqlTable(
|
export const sectionCollapseStates = mysqlTable(
|
||||||
@@ -414,8 +420,8 @@ export const items = mysqlTable("item", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => boards.id, { onDelete: "cascade" }),
|
.references(() => boards.id, { onDelete: "cascade" }),
|
||||||
kind: text().$type<WidgetKind>().notNull(),
|
kind: text().$type<WidgetKind>().notNull(),
|
||||||
options: text().default('{"json": {}}').notNull(), // empty superjson object
|
options: text().default(emptySuperJSON).notNull(),
|
||||||
advancedOptions: text().default('{"json": {}}').notNull(), // empty superjson object
|
advancedOptions: text().default(emptySuperJSON).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apps = mysqlTable("app", {
|
export const apps = mysqlTable("app", {
|
||||||
@@ -461,7 +467,7 @@ export const iconRepositories = mysqlTable("iconRepository", {
|
|||||||
|
|
||||||
export const serverSettings = mysqlTable("serverSetting", {
|
export const serverSettings = mysqlTable("serverSetting", {
|
||||||
settingKey: varchar({ length: 64 }).notNull().unique().primaryKey(),
|
settingKey: varchar({ length: 64 }).notNull().unique().primaryKey(),
|
||||||
value: text().default('{"json": {}}').notNull(), // empty superjson object
|
value: text().default(emptySuperJSON).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ 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 } from "@homarr/definitions";
|
import {
|
||||||
|
backgroundImageAttachments,
|
||||||
|
backgroundImageRepeats,
|
||||||
|
backgroundImageSizes,
|
||||||
|
emptySuperJSON,
|
||||||
|
} from "@homarr/definitions";
|
||||||
import type {
|
import type {
|
||||||
BackgroundImageAttachment,
|
BackgroundImageAttachment,
|
||||||
BackgroundImageRepeat,
|
BackgroundImageRepeat,
|
||||||
@@ -373,6 +378,7 @@ export const sections = sqliteTable("section", {
|
|||||||
xOffset: int(),
|
xOffset: int(),
|
||||||
yOffset: int(),
|
yOffset: int(),
|
||||||
name: text(),
|
name: text(),
|
||||||
|
options: text().default(emptySuperJSON),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sectionCollapseStates = sqliteTable(
|
export const sectionCollapseStates = sqliteTable(
|
||||||
@@ -399,8 +405,8 @@ export const items = sqliteTable("item", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => boards.id, { onDelete: "cascade" }),
|
.references(() => boards.id, { onDelete: "cascade" }),
|
||||||
kind: text().$type<WidgetKind>().notNull(),
|
kind: text().$type<WidgetKind>().notNull(),
|
||||||
options: text().default('{"json": {}}').notNull(), // empty superjson object
|
options: text().default(emptySuperJSON).notNull(),
|
||||||
advancedOptions: text().default('{"json": {}}').notNull(), // empty superjson object
|
advancedOptions: text().default(emptySuperJSON).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apps = sqliteTable("app", {
|
export const apps = sqliteTable("app", {
|
||||||
@@ -446,7 +452,7 @@ export const iconRepositories = sqliteTable("iconRepository", {
|
|||||||
|
|
||||||
export const serverSettings = sqliteTable("serverSetting", {
|
export const serverSettings = sqliteTable("serverSetting", {
|
||||||
settingKey: text().notNull().unique().primaryKey(),
|
settingKey: text().notNull().unique().primaryKey(),
|
||||||
value: text().default('{"json": {}}').notNull(), // empty superjson object
|
value: text().default(emptySuperJSON).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
export const apiKeyRelations = relations(apiKeys, ({ one }) => ({
|
||||||
|
|||||||
1
packages/definitions/src/emptysuperjson.ts
Normal file
1
packages/definitions/src/emptysuperjson.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const emptySuperJSON = '{"json": {}}';
|
||||||
@@ -11,3 +11,4 @@ export * from "./docs";
|
|||||||
export * from "./cookie";
|
export * from "./cookie";
|
||||||
export * from "./search-engine";
|
export * from "./search-engine";
|
||||||
export * from "./onboarding";
|
export * from "./onboarding";
|
||||||
|
export * from "./emptysuperjson";
|
||||||
|
|||||||
@@ -972,6 +972,11 @@
|
|||||||
"create": "New dynamic section",
|
"create": "New dynamic section",
|
||||||
"remove": "Remove dynamic section"
|
"remove": "Remove dynamic section"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"borderColor": {
|
||||||
|
"label": "Border color"
|
||||||
|
}
|
||||||
|
},
|
||||||
"remove": {
|
"remove": {
|
||||||
"title": "Remove dynamic section",
|
"title": "Remove dynamic section",
|
||||||
"message": "Are you sure you want to remove this dynamic section? Items will be moved at the same location in the parent section."
|
"message": "Are you sure you want to remove this dynamic section? Items will be moved at the same location in the parent section."
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export {
|
|||||||
sectionSchema,
|
sectionSchema,
|
||||||
itemAdvancedOptionsSchema,
|
itemAdvancedOptionsSchema,
|
||||||
sharedItemSchema,
|
sharedItemSchema,
|
||||||
|
dynamicSectionOptionsSchema,
|
||||||
type BoardItemAdvancedOptions,
|
type BoardItemAdvancedOptions,
|
||||||
type BoardItemIntegration,
|
type BoardItemIntegration,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|||||||
@@ -58,9 +58,14 @@ const emptySectionSchema = z.object({
|
|||||||
xOffset: z.number(),
|
xOffset: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const dynamicSectionOptionsSchema = z.object({
|
||||||
|
borderColor: z.string().default(""),
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSectionSchema = z.object({
|
const dynamicSectionSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
kind: z.literal("dynamic"),
|
kind: z.literal("dynamic"),
|
||||||
|
options: dynamicSectionOptionsSchema,
|
||||||
layouts: z.array(
|
layouts: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
layoutId: z.string(),
|
layoutId: z.string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user