Merge branch 'dev' into feat/impeccable-uiux-fixes
This commit is contained in:
176 files changed
+3068
-6219
No files matched your search
@@ -1,5 +0,0 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
import reactConfig from "@homarr/eslint-config/react";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [...baseConfig, ...reactConfig];
|
||||
@@ -4,12 +4,6 @@
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./errors": "./src/errors/component.tsx",
|
||||
"./modals": "./src/modals/index.ts",
|
||||
"./prefetch": "./src/prefetch.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
@@ -17,13 +11,18 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./errors": "./src/errors/component.tsx",
|
||||
"./modals": "./src/modals/index.ts",
|
||||
"./prefetch": "./src/prefetch.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint",
|
||||
"format": "oxfmt --check .",
|
||||
"lint": "oxlint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "catalog:",
|
||||
"@dnd-kit/sortable": "catalog:",
|
||||
@@ -50,6 +49,7 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "catalog:",
|
||||
"@mantine/core": "catalog:",
|
||||
"@mantine/form": "catalog:",
|
||||
"@mantine/hooks": "catalog:",
|
||||
"@tabler/icons-react": "catalog:",
|
||||
"@tiptap/extension-color": "catalog:",
|
||||
@@ -70,7 +70,6 @@
|
||||
"@tiptap/starter-kit": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"mantine-form-zod-resolver": "catalog:",
|
||||
"mantine-react-table": "catalog:",
|
||||
"next": "catalog:",
|
||||
"react": "catalog:",
|
||||
@@ -82,11 +81,8 @@
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/video.js": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,22 @@ export const WidgetDynamicSelectInput = ({ property, kind, options }: CommonWidg
|
||||
const [search, setSearch] = useState("");
|
||||
const [debouncedSearch] = useDebouncedValue(search, 300);
|
||||
const { isPending, options: selectOptions } = options.useOptions(debouncedSearch, form.values.integrationIds);
|
||||
const value = inputProps.value as DynamicSelectOption | null;
|
||||
const currentOption = inputProps.value as DynamicSelectOption | null;
|
||||
const onChange = inputProps.onChange as (value: DynamicSelectOption | null) => void;
|
||||
|
||||
const getLabelForValue = (selectedValue: string) => {
|
||||
const matched = selectOptions.find((option) =>
|
||||
typeof option === "string" ? option === selectedValue : option.value === selectedValue,
|
||||
);
|
||||
if (!matched) {
|
||||
return selectedValue;
|
||||
}
|
||||
if (typeof matched === "string") {
|
||||
return matched;
|
||||
}
|
||||
return translateIfNecessary(t, matched.label) ?? selectedValue;
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={tWidget("label")}
|
||||
@@ -41,14 +54,13 @@ export const WidgetDynamicSelectInput = ({ property, kind, options }: CommonWidg
|
||||
},
|
||||
)
|
||||
.concat(
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
currentOption !== null &&
|
||||
!selectOptions.some((option) =>
|
||||
typeof option === "string" ? option === value.value : option.value === value.value,
|
||||
typeof option === "string" ? option === currentOption.value : option.value === currentOption.value,
|
||||
)
|
||||
? {
|
||||
value: value.value,
|
||||
label: value.label,
|
||||
value: currentOption.value,
|
||||
label: currentOption.label,
|
||||
}
|
||||
: [],
|
||||
)}
|
||||
@@ -74,19 +86,17 @@ export const WidgetDynamicSelectInput = ({ property, kind, options }: CommonWidg
|
||||
description={options.withDescription ? tWidget("description") : undefined}
|
||||
searchable
|
||||
{...inputProps}
|
||||
value={value?.value ?? null}
|
||||
onChange={(value) =>
|
||||
onChange(
|
||||
value
|
||||
? {
|
||||
value,
|
||||
label:
|
||||
selectOptions.filter((option) => typeof option === "object").find((option) => option.value === value)
|
||||
?.label ?? value,
|
||||
}
|
||||
: null,
|
||||
)
|
||||
}
|
||||
value={currentOption === null ? null : currentOption.value}
|
||||
onChange={(selectedValue: string | null) => {
|
||||
if (selectedValue === null) {
|
||||
onChange(null);
|
||||
return;
|
||||
}
|
||||
onChange({
|
||||
value: selectedValue,
|
||||
label: getLabelForValue(selectedValue),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -53,7 +53,7 @@ export const WidgetSelectInput = ({ property, kind, options }: CommonWidgetInput
|
||||
)}
|
||||
leftSection={CurrentIcon && <CurrentIcon size={16} stroke={1.5} />}
|
||||
renderOption={({ option, checked }) => {
|
||||
const Icon = getIconFor(options.options, option.value);
|
||||
const Icon = getIconFor(options.options, String(option.value));
|
||||
|
||||
return (
|
||||
<Group flex="1" gap="xs">
|
||||
|
||||
@@ -65,8 +65,8 @@ const columnsRatios: Record<keyof ExtendedDownloadClientItem, number> = {
|
||||
id: 1,
|
||||
index: 1,
|
||||
integration: 1,
|
||||
name: 8,
|
||||
progress: 4,
|
||||
name: 16,
|
||||
progress: 6,
|
||||
ratio: 2,
|
||||
received: 3,
|
||||
sent: 3,
|
||||
@@ -805,7 +805,7 @@ const ClientsControl = ({ clients, filters, setFilters, availableStatuses }: Cli
|
||||
<Chip.Group
|
||||
multiple
|
||||
value={filters.statuses}
|
||||
onChange={(statuses) => setFilters({ ...filters, statuses: statuses as typeof filters.statuses })}
|
||||
onChange={(statuses) => setFilters({ ...filters, statuses: statuses })}
|
||||
>
|
||||
{availableStatuses.map((status) => (
|
||||
<Chip key={status} value={status}>
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function FirewallWidget({ integrationIds, width, itemId }: Widget
|
||||
style={{ justifyContent: "flex-start" }}
|
||||
>
|
||||
<IconArrowBarUp size={isTiny ? "8" : "12"} color="lightgreen" />
|
||||
<Text size={isTiny ? "8px" : "xs"} color="lightgreen" style={{ textAlign: "left" }}>
|
||||
<Text size={isTiny ? "8px" : "xs"} c="lightgreen" style={{ textAlign: "left" }}>
|
||||
{formatBitsPerSec(transmit, 2)}
|
||||
</Text>
|
||||
</Flex>
|
||||
@@ -164,7 +164,7 @@ export default function FirewallWidget({ integrationIds, width, itemId }: Widget
|
||||
style={{ justifyContent: "flex-start" }}
|
||||
>
|
||||
<IconArrowBarDown size={isTiny ? "8" : "12"} color="yellow" />
|
||||
<Text size={isTiny ? "8px" : "xs"} color="yellow" style={{ textAlign: "left" }}>
|
||||
<Text size={isTiny ? "8px" : "xs"} c="yellow" style={{ textAlign: "left" }}>
|
||||
{formatBitsPerSec(receive, 2)}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function MediaServerWidget({
|
||||
<Text className="mediaRequests-stats-stats-title" fw="bold" ta="center" size={isTiny ? "xs" : "sm"}>
|
||||
{t("titles.stats.main")}
|
||||
</Text>
|
||||
<Grid className="mediaRequests-stats-stats-grid" gutter={4} w="100%">
|
||||
<Grid className="mediaRequests-stats-stats-grid" gap={4} w="100%">
|
||||
{data.map((stat) => (
|
||||
<Grid.Col
|
||||
className={combineClasses("mediaRequests-stats-stat-wrapper", `mediaRequests-stats-stat-${stat.name}`)}
|
||||
|
||||
@@ -17,6 +17,8 @@ import { WorkersPanel } from "./panels/workers.panel";
|
||||
|
||||
type View = (typeof views)[number];
|
||||
|
||||
const viewBySegmentValue = Object.fromEntries(views.map((view) => [view, view])) as Record<string, View>;
|
||||
|
||||
const viewIcons = {
|
||||
workers: IconCpu2,
|
||||
queue: IconClipboardList,
|
||||
@@ -83,7 +85,12 @@ export default function MediaTranscodingWidget({
|
||||
};
|
||||
})}
|
||||
value={view}
|
||||
onChange={(value) => setView(value as View)}
|
||||
onChange={(value) => {
|
||||
const nextView = viewBySegmentValue[value];
|
||||
if (nextView) {
|
||||
setView(nextView);
|
||||
}
|
||||
}}
|
||||
size="xs"
|
||||
/>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button, Group, Stack } from "@mantine/core";
|
||||
import { zod4Resolver } from "mantine-form-zod-resolver";
|
||||
import { schemaResolver } from "@mantine/form";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
@@ -50,7 +50,7 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
const form = useForm({
|
||||
mode: "controlled",
|
||||
initialValues: innerProps.value,
|
||||
validate: zod4Resolver(
|
||||
validate: schemaResolver(
|
||||
z.object({
|
||||
options: z.object(
|
||||
objectEntries(options).reduce(
|
||||
@@ -70,6 +70,7 @@ export const WidgetEditModal = createModal<ModalProps<WidgetKind>>(({ actions, i
|
||||
borderColor: z.string(),
|
||||
}),
|
||||
}),
|
||||
{ sync: true },
|
||||
),
|
||||
validateInputOnBlur: true,
|
||||
validateInputOnChange: true,
|
||||
|
||||
Reference in new issue
Block a user