fix(deps): update dependency eslint-plugin-react-hooks to v5 (#1280)
* fix(deps): update dependency eslint-plugin-react-hooks to v5 * fix: lint issues after reenabling hook rules * fix: format issues --------- Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ea43ed0ca4
commit
a87c937b69
@@ -46,7 +46,7 @@ export const WidgetLocationInput = ({ property, kind }: CommonWidgetInputProps<"
|
||||
form.clearFieldError(`options.${property}.latitude`);
|
||||
form.clearFieldError(`options.${property}.longitude`);
|
||||
},
|
||||
[handleChange],
|
||||
[form, handleChange, property],
|
||||
);
|
||||
|
||||
const onSearch = useCallback(() => {
|
||||
|
||||
@@ -39,7 +39,7 @@ export const WidgetMultiTextInput = ({ property, kind, options }: CommonWidgetIn
|
||||
success: validationResult.success,
|
||||
result: validationResult,
|
||||
};
|
||||
}, [search]);
|
||||
}, [options.validate, search]);
|
||||
|
||||
const error = React.useMemo(() => {
|
||||
/* hide the error when nothing is being typed since "" is not valid but is not an explicit error */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import "../widgets-common.css";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import type { MantineStyleProp } from "@mantine/core";
|
||||
import {
|
||||
ActionIcon,
|
||||
@@ -233,7 +233,19 @@ export default function DownloadClientsWidget({
|
||||
)
|
||||
//flatMap already sorts by integration by nature, add sorting by integration type (usenet | torrent)
|
||||
.sort(({ type: typeA }, { type: typeB }) => typeA.length - typeB.length),
|
||||
[currentItems, integrationIds, options],
|
||||
[
|
||||
currentItems,
|
||||
integrationIds,
|
||||
integrationsWithInteractions,
|
||||
mutateDeleteItem,
|
||||
mutatePauseItem,
|
||||
mutateResumeItem,
|
||||
options.activeTorrentThreshold,
|
||||
options.categoryFilter,
|
||||
options.filterIsWhitelist,
|
||||
options.showCompletedTorrent,
|
||||
options.showCompletedUsenet,
|
||||
],
|
||||
);
|
||||
|
||||
//Flatten Clients Array for which each elements has the integration and general client infos.
|
||||
@@ -278,7 +290,14 @@ export default function DownloadClientsWidget({
|
||||
({ status: statusA }, { status: statusB }) =>
|
||||
(statusA?.type.length ?? Infinity) - (statusB?.type.length ?? Infinity),
|
||||
),
|
||||
[currentItems, integrationIds, options],
|
||||
[
|
||||
currentItems,
|
||||
integrationIds,
|
||||
integrationsWithInteractions,
|
||||
options.applyFilterToRatio,
|
||||
options.categoryFilter,
|
||||
options.filterIsWhitelist,
|
||||
],
|
||||
);
|
||||
|
||||
//Check existing types between torrents and usenet
|
||||
@@ -333,37 +352,40 @@ export default function DownloadClientsWidget({
|
||||
};
|
||||
|
||||
//Base element in common with all columns
|
||||
const columnsDefBase = ({
|
||||
key,
|
||||
showHeader,
|
||||
align,
|
||||
}: {
|
||||
key: keyof ExtendedDownloadClientItem;
|
||||
showHeader: boolean;
|
||||
align?: "center" | "left" | "right" | "justify" | "char";
|
||||
}): MRT_ColumnDef<ExtendedDownloadClientItem> => {
|
||||
const style: MantineStyleProp = {
|
||||
minWidth: 0,
|
||||
width: "var(--column-width)",
|
||||
height: "var(--ratio-width)",
|
||||
padding: "var(--space-size)",
|
||||
transition: "unset",
|
||||
"--key-width": columnsRatios[key],
|
||||
"--column-width": "calc((var(--key-width)/var(--total-width) * 100cqw))",
|
||||
};
|
||||
return {
|
||||
id: key,
|
||||
accessorKey: key,
|
||||
header: key,
|
||||
size: columnsRatios[key],
|
||||
mantineTableBodyCellProps: { style, align },
|
||||
mantineTableHeadCellProps: {
|
||||
style,
|
||||
align: isEditMode ? "center" : align,
|
||||
},
|
||||
Header: () => (showHeader && !isEditMode ? <Text fw={700}>{t(`items.${key}.columnTitle`)}</Text> : ""),
|
||||
};
|
||||
};
|
||||
const columnsDefBase = useCallback(
|
||||
({
|
||||
key,
|
||||
showHeader,
|
||||
align,
|
||||
}: {
|
||||
key: keyof ExtendedDownloadClientItem;
|
||||
showHeader: boolean;
|
||||
align?: "center" | "left" | "right" | "justify" | "char";
|
||||
}): MRT_ColumnDef<ExtendedDownloadClientItem> => {
|
||||
const style: MantineStyleProp = {
|
||||
minWidth: 0,
|
||||
width: "var(--column-width)",
|
||||
height: "var(--ratio-width)",
|
||||
padding: "var(--space-size)",
|
||||
transition: "unset",
|
||||
"--key-width": columnsRatios[key],
|
||||
"--column-width": "calc((var(--key-width)/var(--total-width) * 100cqw))",
|
||||
};
|
||||
return {
|
||||
id: key,
|
||||
accessorKey: key,
|
||||
header: key,
|
||||
size: columnsRatios[key],
|
||||
mantineTableBodyCellProps: { style, align },
|
||||
mantineTableHeadCellProps: {
|
||||
style,
|
||||
align: isEditMode ? "center" : align,
|
||||
},
|
||||
Header: () => (showHeader && !isEditMode ? <Text fw={700}>{t(`items.${key}.columnTitle`)}</Text> : ""),
|
||||
};
|
||||
},
|
||||
[isEditMode, t],
|
||||
);
|
||||
|
||||
//Make columns and cell elements, Memoized to data with deps on data and EditMode
|
||||
const columns = useMemo<MRT_ColumnDef<ExtendedDownloadClientItem>[]>(
|
||||
@@ -580,7 +602,7 @@ export default function DownloadClientsWidget({
|
||||
},
|
||||
},
|
||||
],
|
||||
[clickedIndex, isEditMode, data, integrationIds, options],
|
||||
[columnsDefBase, t, tCommon],
|
||||
);
|
||||
|
||||
//Table build and config
|
||||
@@ -704,10 +726,7 @@ interface ItemInfoModalProps {
|
||||
}
|
||||
|
||||
const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalProps) => {
|
||||
const item = useMemo<ExtendedDownloadClientItem | undefined>(
|
||||
() => items[currentIndex],
|
||||
[items, currentIndex, opened],
|
||||
);
|
||||
const item = useMemo<ExtendedDownloadClientItem | undefined>(() => items[currentIndex], [items, currentIndex]);
|
||||
const t = useScopedI18n("widget.downloads.states");
|
||||
//The use case for "No item found" should be impossible, hence no translation
|
||||
return (
|
||||
|
||||
@@ -57,22 +57,19 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
||||
throw new NoIntegrationSelectedError();
|
||||
}
|
||||
return (
|
||||
<Box h="100%" className="health-monitoring">
|
||||
<Stack h="100%" gap="2.5cqmin" className="health-monitoring">
|
||||
{healthData.map(({ integrationId, integrationName, healthInfo }) => {
|
||||
const memoryUsage = formatMemoryUsage(healthInfo.memAvailable, healthInfo.memUsed);
|
||||
const disksData = matchFileSystemAndSmart(healthInfo.fileSystem, healthInfo.smart);
|
||||
const { ref, width } = useElementSize();
|
||||
const ringSize = width * 0.95;
|
||||
const ringThickness = width / 10;
|
||||
const progressSize = width * 0.2;
|
||||
|
||||
const memoryUsage = formatMemoryUsage(healthInfo.memAvailable, healthInfo.memUsed);
|
||||
return (
|
||||
<Box
|
||||
<Stack
|
||||
gap="2.5cqmin"
|
||||
key={integrationId}
|
||||
h="100%"
|
||||
className={`health-monitoring-information health-monitoring-${integrationName}`}
|
||||
p="2.5cqmin"
|
||||
>
|
||||
<Card className="health-monitoring-information-card" m="2.5cqmin" p="2.5cqmin" withBorder>
|
||||
<Card className="health-monitoring-information-card" p="2.5cqmin" withBorder>
|
||||
<Flex
|
||||
className="health-monitoring-information-card-elements"
|
||||
h="100%"
|
||||
@@ -155,95 +152,17 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Box>
|
||||
{options.cpu && (
|
||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
|
||||
<RingProgress
|
||||
className="health-monitoring-cpu-utilization"
|
||||
roundCaps
|
||||
size={ringSize}
|
||||
thickness={ringThickness}
|
||||
label={
|
||||
<Center style={{ flexDirection: "column" }}>
|
||||
<Text
|
||||
className="health-monitoring-cpu-utilization-value"
|
||||
size="3cqmin"
|
||||
>{`${healthInfo.cpuUtilization.toFixed(2)}%`}</Text>
|
||||
<IconCpu className="health-monitoring-cpu-utilization-icon" size="7cqmin" />
|
||||
</Center>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
value: Number(healthInfo.cpuUtilization.toFixed(2)),
|
||||
color: progressColor(Number(healthInfo.cpuUtilization.toFixed(2))),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{options.cpu && <CpuRing cpuUtilization={healthInfo.cpuUtilization} />}
|
||||
{healthInfo.cpuTemp && options.cpu && (
|
||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature">
|
||||
<RingProgress
|
||||
ref={ref}
|
||||
className="health-monitoring-cpu-temp"
|
||||
roundCaps
|
||||
size={ringSize}
|
||||
thickness={ringThickness}
|
||||
label={
|
||||
<Center style={{ flexDirection: "column" }}>
|
||||
<Text className="health-monitoring-cpu-temp-value" size="3cqmin">
|
||||
{options.fahrenheit
|
||||
? `${(healthInfo.cpuTemp * 1.8 + 32).toFixed(1)}°F`
|
||||
: `${healthInfo.cpuTemp}°C`}
|
||||
</Text>
|
||||
<IconCpu className="health-monitoring-cpu-temp-icon" size="7cqmin" />
|
||||
</Center>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
value: healthInfo.cpuTemp,
|
||||
color: progressColor(healthInfo.cpuTemp),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{options.memory && (
|
||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
|
||||
<RingProgress
|
||||
className="health-monitoring-memory-use"
|
||||
roundCaps
|
||||
size={ringSize}
|
||||
thickness={ringThickness}
|
||||
label={
|
||||
<Center style={{ flexDirection: "column" }}>
|
||||
<Text className="health-monitoring-memory-value" size="3cqmin">
|
||||
{memoryUsage.memUsed.GB}GiB
|
||||
</Text>
|
||||
<IconBrain className="health-monitoring-memory-icon" size="7cqmin" />
|
||||
</Center>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
value: Number(memoryUsage.memUsed.percent),
|
||||
color: progressColor(Number(memoryUsage.memUsed.percent)),
|
||||
tooltip: `${memoryUsage.memUsed.percent}%`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
<CpuTempRing fahrenheit={options.fahrenheit} cpuTemp={healthInfo.cpuTemp} />
|
||||
)}
|
||||
{options.memory && <MemoryRing available={healthInfo.memAvailable} used={healthInfo.memUsed} />}
|
||||
</Flex>
|
||||
</Card>
|
||||
{options.fileSystem &&
|
||||
disksData.map((disk) => {
|
||||
return (
|
||||
<Card
|
||||
className="health-monitoring-disk-card"
|
||||
key={disk.deviceName}
|
||||
m="2.5cqmin"
|
||||
p="2.5cqmin"
|
||||
withBorder
|
||||
>
|
||||
<Card className="health-monitoring-disk-card" key={disk.deviceName} p="2.5cqmin" withBorder>
|
||||
<Flex className="health-monitoring-disk-status" justify="space-between" align="center" m="1.5cqmin">
|
||||
<Group gap="1cqmin">
|
||||
<IconServer className="health-monitoring-disk-icon" size="5cqmin" />
|
||||
@@ -266,14 +185,14 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
||||
</Text>
|
||||
</Group>
|
||||
</Flex>
|
||||
<Progress.Root className="health-monitoring-disk-use" size={progressSize}>
|
||||
<Progress.Root className="health-monitoring-disk-use" h="6cqmin">
|
||||
<Tooltip label={disk.used}>
|
||||
<Progress.Section
|
||||
value={disk.percentage}
|
||||
color={progressColor(disk.percentage)}
|
||||
className="health-monitoring-disk-use-percentage"
|
||||
>
|
||||
<Progress.Label className="health-monitoring-disk-use-value">
|
||||
<Progress.Label className="health-monitoring-disk-use-value" fz="2.5cqmin">
|
||||
{t("widget.healthMonitoring.popover.used")}
|
||||
</Progress.Label>
|
||||
</Progress.Section>
|
||||
@@ -291,7 +210,7 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
||||
value={100 - disk.percentage}
|
||||
color="default"
|
||||
>
|
||||
<Progress.Label className="health-monitoring-disk-available-value">
|
||||
<Progress.Label className="health-monitoring-disk-available-value" fz="2.5cqmin">
|
||||
{t("widget.healthMonitoring.popover.diskAvailable")}
|
||||
</Progress.Label>
|
||||
</Progress.Section>
|
||||
@@ -300,10 +219,10 @@ export default function HealthMonitoringWidget({ options, integrationIds }: Widg
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -349,6 +268,95 @@ export const matchFileSystemAndSmart = (fileSystems: FileSystem[], smartData: Sm
|
||||
});
|
||||
};
|
||||
|
||||
const CpuRing = ({ cpuUtilization }: { cpuUtilization: number }) => {
|
||||
const { width, ref } = useElementSize();
|
||||
|
||||
return (
|
||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu">
|
||||
<RingProgress
|
||||
className="health-monitoring-cpu-utilization"
|
||||
roundCaps
|
||||
size={width * 0.95}
|
||||
thickness={width / 10}
|
||||
label={
|
||||
<Center style={{ flexDirection: "column" }}>
|
||||
<Text
|
||||
className="health-monitoring-cpu-utilization-value"
|
||||
size="3cqmin"
|
||||
>{`${cpuUtilization.toFixed(2)}%`}</Text>
|
||||
<IconCpu className="health-monitoring-cpu-utilization-icon" size="7cqmin" />
|
||||
</Center>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
value: Number(cpuUtilization.toFixed(2)),
|
||||
color: progressColor(Number(cpuUtilization.toFixed(2))),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const CpuTempRing = ({ fahrenheit, cpuTemp }: { fahrenheit: boolean; cpuTemp: number }) => {
|
||||
const { width, ref } = useElementSize();
|
||||
return (
|
||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-cpu-temperature">
|
||||
<RingProgress
|
||||
className="health-monitoring-cpu-temp"
|
||||
roundCaps
|
||||
size={width * 0.95}
|
||||
thickness={width / 10}
|
||||
label={
|
||||
<Center style={{ flexDirection: "column" }}>
|
||||
<Text className="health-monitoring-cpu-temp-value" size="3cqmin">
|
||||
{fahrenheit ? `${(cpuTemp * 1.8 + 32).toFixed(1)}°F` : `${cpuTemp}°C`}
|
||||
</Text>
|
||||
<IconCpu className="health-monitoring-cpu-temp-icon" size="7cqmin" />
|
||||
</Center>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
value: cpuTemp,
|
||||
color: progressColor(cpuTemp),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoryRing = ({ available, used }: { available: string; used: string }) => {
|
||||
const { width, ref } = useElementSize();
|
||||
const memoryUsage = formatMemoryUsage(available, used);
|
||||
|
||||
return (
|
||||
<Box ref={ref} w="100%" h="100%" className="health-monitoring-memory">
|
||||
<RingProgress
|
||||
className="health-monitoring-memory-use"
|
||||
roundCaps
|
||||
size={width * 0.95}
|
||||
thickness={width / 10}
|
||||
label={
|
||||
<Center style={{ flexDirection: "column" }}>
|
||||
<Text className="health-monitoring-memory-value" size="3cqmin">
|
||||
{memoryUsage.memUsed.GB}GiB
|
||||
</Text>
|
||||
<IconBrain className="health-monitoring-memory-icon" size="7cqmin" />
|
||||
</Center>
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
value: Number(memoryUsage.memUsed.percent),
|
||||
color: progressColor(Number(memoryUsage.memUsed.percent)),
|
||||
tooltip: `${memoryUsage.memUsed.percent}%`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const formatMemoryUsage = (memFree: string, memUsed: string) => {
|
||||
const memFreeBytes = Number(memFree);
|
||||
const memUsedBytes = Number(memUsed);
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function MediaServerWidget({
|
||||
}
|
||||
return 0;
|
||||
}),
|
||||
[mediaRequests, integrationIds],
|
||||
[mediaRequests],
|
||||
);
|
||||
|
||||
const { mutate: mutateRequestAnswer } = clientApi.widget.mediaRequests.answerRequest.useMutation();
|
||||
|
||||
@@ -189,17 +189,31 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
|
||||
|
||||
addEventListener("onReadOnlyCheck", handleOnReadOnlyCheck);
|
||||
|
||||
const handleEditToggleCallback = (previous: boolean) => {
|
||||
const current = !previous;
|
||||
if (!editor) return current;
|
||||
editor.setEditable(current);
|
||||
const handleContentUpdate = useCallback(
|
||||
(contentUpdate: string) => {
|
||||
setToSaveContent(contentUpdate);
|
||||
// This is not available in preview mode
|
||||
if (boardId && itemId) {
|
||||
void mutateAsync({ boardId, itemId, content: contentUpdate });
|
||||
}
|
||||
},
|
||||
[boardId, itemId, mutateAsync],
|
||||
);
|
||||
|
||||
handleContentUpdate(content);
|
||||
const handleEditToggleCallback = useCallback(
|
||||
(previous: boolean) => {
|
||||
const current = !previous;
|
||||
if (!editor) return current;
|
||||
editor.setEditable(current);
|
||||
|
||||
return current;
|
||||
};
|
||||
handleContentUpdate(content);
|
||||
|
||||
const handleEditCancelCallback = () => {
|
||||
return current;
|
||||
},
|
||||
[content, editor, handleContentUpdate],
|
||||
);
|
||||
|
||||
const handleEditCancelCallback = useCallback(() => {
|
||||
if (!editor) return false;
|
||||
editor.setEditable(false);
|
||||
|
||||
@@ -207,20 +221,12 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
|
||||
editor.commands.setContent(toSaveContent);
|
||||
|
||||
return false;
|
||||
};
|
||||
}, [editor, toSaveContent]);
|
||||
|
||||
const handleEditCancel = useCallback(() => {
|
||||
setIsEditing(handleEditCancelCallback);
|
||||
}, [setIsEditing, handleEditCancelCallback]);
|
||||
|
||||
const handleContentUpdate = (contentUpdate: string) => {
|
||||
setToSaveContent(contentUpdate);
|
||||
// This is not available in preview mode
|
||||
if (boardId && itemId) {
|
||||
void mutateAsync({ boardId, itemId, content: contentUpdate });
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditToggle = useCallback(() => {
|
||||
setIsEditing(handleEditToggleCallback);
|
||||
}, [setIsEditing, handleEditToggleCallback]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { Center, Stack, Text, UnstyledButton } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -38,7 +38,7 @@ export default function SmartHomeEntityStateWidget({
|
||||
|
||||
const attribute = options.entityUnit.length > 0 ? " " + options.entityUnit : "";
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
const handleClick = useCallback(() => {
|
||||
if (isEditMode) {
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export default function SmartHomeEntityStateWidget({
|
||||
entityId: options.entityId,
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
});
|
||||
}, []);
|
||||
}, [integrationIds, isEditMode, mutate, options.clickable, options.entityId]);
|
||||
|
||||
return (
|
||||
<UnstyledButton
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function SmartHomeTriggerAutomationWidget({
|
||||
automationId: options.automationId,
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
});
|
||||
}, [isEditMode]);
|
||||
}, [integrationIds, isEditMode, mutateAsync, options.automationId]);
|
||||
return (
|
||||
<UnstyledButton onClick={handleClick} style={{ cursor: !isEditMode ? "pointer" : "initial" }} w="100%" h="100%">
|
||||
{isShowSuccess && (
|
||||
|
||||
@@ -72,7 +72,7 @@ const Feed = ({ options }: Pick<WidgetComponentProps<"video">, "options">) => {
|
||||
() => undefined,
|
||||
);
|
||||
}
|
||||
}, [videoRef]);
|
||||
}, [options.hasAutoPlay, options.hasControls, options.isMuted, videoRef]);
|
||||
|
||||
return (
|
||||
<Group justify="center" w="100%" h="100%" pos="relative">
|
||||
|
||||
Reference in New Issue
Block a user