Merge branch 'dev' into add-more-openapi
This commit is contained in:
323 files changed
+4555
-2926
No files matched your search
@@ -31,13 +31,13 @@
|
||||
"@homarr/auth": "workspace:^0.1.0",
|
||||
"@homarr/boards": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/docker": "workspace:^0.1.0",
|
||||
"@homarr/form": "workspace:^0.1.0",
|
||||
"@homarr/forms-collection": "workspace:^0.1.0",
|
||||
"@homarr/integrations": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/modals": "workspace:^0.1.0",
|
||||
"@homarr/modals-collection": "workspace:^0.1.0",
|
||||
"@homarr/notifications": "workspace:^0.1.0",
|
||||
@@ -48,33 +48,33 @@
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/ui": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@mantine/charts": "^8.3.9",
|
||||
"@mantine/core": "^8.3.9",
|
||||
"@mantine/hooks": "^8.3.9",
|
||||
"@mantine/charts": "^8.3.10",
|
||||
"@mantine/core": "^8.3.10",
|
||||
"@mantine/hooks": "^8.3.10",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tiptap/extension-color": "2.27.1",
|
||||
"@tiptap/extension-highlight": "2.27.1",
|
||||
"@tiptap/extension-image": "2.27.1",
|
||||
"@tiptap/extension-link": "^2.27.1",
|
||||
"@tiptap/extension-placeholder": "^2.27.1",
|
||||
"@tiptap/extension-table": "2.27.1",
|
||||
"@tiptap/extension-table-cell": "2.27.1",
|
||||
"@tiptap/extension-table-header": "2.27.1",
|
||||
"@tiptap/extension-table-row": "2.27.1",
|
||||
"@tiptap/extension-task-item": "2.27.1",
|
||||
"@tiptap/extension-task-list": "2.27.1",
|
||||
"@tiptap/extension-text-align": "2.27.1",
|
||||
"@tiptap/extension-text-style": "2.27.1",
|
||||
"@tiptap/extension-underline": "2.27.1",
|
||||
"@tiptap/react": "^2.27.1",
|
||||
"@tiptap/starter-kit": "^2.27.1",
|
||||
"@tiptap/extension-color": "3.13.0",
|
||||
"@tiptap/extension-highlight": "3.13.0",
|
||||
"@tiptap/extension-image": "3.13.0",
|
||||
"@tiptap/extension-link": "^3.13.0",
|
||||
"@tiptap/extension-placeholder": "^3.13.0",
|
||||
"@tiptap/extension-table": "3.13.0",
|
||||
"@tiptap/extension-table-cell": "3.13.0",
|
||||
"@tiptap/extension-table-header": "3.13.0",
|
||||
"@tiptap/extension-table-row": "3.13.0",
|
||||
"@tiptap/extension-task-item": "3.13.0",
|
||||
"@tiptap/extension-task-list": "3.13.0",
|
||||
"@tiptap/extension-text-align": "3.13.0",
|
||||
"@tiptap/extension-text-style": "3.13.0",
|
||||
"@tiptap/extension-underline": "3.13.0",
|
||||
"@tiptap/react": "^3.13.0",
|
||||
"@tiptap/starter-kit": "^3.13.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "16.0.7",
|
||||
"react": "19.2.1",
|
||||
"react-dom": "19.2.1",
|
||||
"next": "16.0.10",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.4",
|
||||
"video.js": "^8.23.4",
|
||||
@@ -85,7 +85,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/video.js": "^7.3.58",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint": "^9.39.2",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { trpc } from "@homarr/api/server";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { db, inArray } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { Prefetch } from "../definition";
|
||||
|
||||
const logger = createLogger({ module: "appWidgetPrefetch" });
|
||||
|
||||
const prefetchAllAsync: Prefetch<"app"> = async (queryClient, items) => {
|
||||
const appIds = items.map((item) => item.options.appId);
|
||||
const distinctAppIds = [...new Set(appIds)];
|
||||
@@ -17,7 +19,7 @@ const prefetchAllAsync: Prefetch<"app"> = async (queryClient, items) => {
|
||||
queryClient.setQueryData(trpc.app.byId.queryKey({ id: app.id }), app);
|
||||
}
|
||||
|
||||
logger.info(`Successfully prefetched ${dbApps.length} apps for app widget`);
|
||||
logger.info("Successfully prefetched apps for app widget", { count: dbApps.length });
|
||||
};
|
||||
|
||||
export default prefetchAllAsync;
|
||||
@@ -1,10 +1,12 @@
|
||||
import { trpc } from "@homarr/api/server";
|
||||
import { createLogger } from "@homarr/core/infrastructure/logs";
|
||||
import { db, inArray } from "@homarr/db";
|
||||
import { apps } from "@homarr/db/schema";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { Prefetch } from "../definition";
|
||||
|
||||
const logger = createLogger({ module: "bookmarksWidgetPrefetch" });
|
||||
|
||||
const prefetchAllAsync: Prefetch<"bookmarks"> = async (queryClient, items) => {
|
||||
const appIds = items.flatMap((item) => item.options.items);
|
||||
const distinctAppIds = [...new Set(appIds)];
|
||||
@@ -24,7 +26,7 @@ const prefetchAllAsync: Prefetch<"bookmarks"> = async (queryClient, items) => {
|
||||
);
|
||||
}
|
||||
|
||||
logger.info(`Successfully prefetched ${dbApps.length} apps for bookmarks`);
|
||||
logger.info("Successfully prefetched apps for bookmarks", { count: dbApps.length });
|
||||
};
|
||||
|
||||
export default prefetchAllAsync;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { Box, Container, Flex, Popover, Text, useMantineTheme } from "@mantine/core";
|
||||
import { useElementSize } from "@mantine/hooks";
|
||||
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import type { CalendarEvent } from "@homarr/integrations/types";
|
||||
@@ -17,6 +18,7 @@ interface CalendarDayProps {
|
||||
export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: CalendarDayProps) => {
|
||||
const [opened, setOpened] = useState(false);
|
||||
const { primaryColor } = useMantineTheme();
|
||||
const { ref, height } = useElementSize();
|
||||
const board = useRequiredBoard();
|
||||
const mantineTheme = useMantineTheme();
|
||||
const actualItemRadius = mantineTheme.radius[board.itemRadius];
|
||||
@@ -25,6 +27,8 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C
|
||||
const shouldScaleDown = minAxisSize < 350;
|
||||
const isSmall = rootHeight < 256;
|
||||
|
||||
const isTooSmallForIndicators = height < 30;
|
||||
|
||||
return (
|
||||
<Popover
|
||||
position="bottom"
|
||||
@@ -47,6 +51,7 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C
|
||||
pt={isSmall ? 0 : 10}
|
||||
pb={isSmall ? 0 : 10}
|
||||
m={0}
|
||||
ref={ref}
|
||||
bd={`2px solid ${opened && !disabled ? primaryColor : "transparent"}`}
|
||||
style={{
|
||||
alignContent: "center",
|
||||
@@ -62,7 +67,7 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C
|
||||
<Text ta={"center"} size={shouldScaleDown ? "xs" : "md"} lh={1}>
|
||||
{date.getDate()}
|
||||
</Text>
|
||||
<NotificationIndicator events={events} isSmall={isSmall} />
|
||||
{!isTooSmallForIndicators && <NotificationIndicator events={events} isSmall={isSmall} />}
|
||||
</Container>
|
||||
</Popover.Target>
|
||||
{/* Popover has some offset on the left side, padding is removed because of scrollarea paddings */}
|
||||
@@ -82,9 +87,19 @@ const NotificationIndicator = ({ events, isSmall }: NotificationIndicatorProps)
|
||||
const notificationEvents = [...new Set(events.map((event) => event.indicatorColor))].filter(String);
|
||||
/* position bottom is lower when small to not be on top of number*/
|
||||
return (
|
||||
<Flex w="75%" pos={"absolute"} bottom={isSmall ? 4 : 10} left={"12.5%"} p={0} direction={"row"} justify={"center"}>
|
||||
<Flex
|
||||
w="75%"
|
||||
align={"center"}
|
||||
pos={"absolute"}
|
||||
gap={3}
|
||||
bottom={isSmall ? 4 : 10}
|
||||
left={"12.5%"}
|
||||
p={0}
|
||||
direction={"row"}
|
||||
justify={"center"}
|
||||
>
|
||||
{notificationEvents.map((notificationEvent) => {
|
||||
return <Box key={notificationEvent} bg={notificationEvent} h={2} p={0} style={{ flex: 1, borderRadius: 5 }} />;
|
||||
return <Box key={notificationEvent} bg={notificationEvent} h={4} w={4} p={0} style={{ borderRadius: 999 }} />;
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -124,12 +124,17 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
|
||||
},
|
||||
monthCell: {
|
||||
textAlign: "center",
|
||||
position: "relative",
|
||||
},
|
||||
day: {
|
||||
borderRadius: actualItemRadius,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
month: {
|
||||
height: "100%",
|
||||
@@ -137,6 +142,9 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
|
||||
weekday: {
|
||||
padding: 0,
|
||||
},
|
||||
weekdaysRow: {
|
||||
height: 22,
|
||||
},
|
||||
}}
|
||||
renderDay={(tileDate) => {
|
||||
const eventsForDate = normalizedEvents
|
||||
|
||||
@@ -56,6 +56,7 @@ const createColumns = (
|
||||
t: ReturnType<typeof useScopedI18n<"docker">>,
|
||||
): MRT_ColumnDef<RouterOutputs["docker"]["getContainers"]["containers"][number]>[] => [
|
||||
{
|
||||
id: "name",
|
||||
accessorKey: "name",
|
||||
header: t("field.name.label"),
|
||||
Cell({ renderedCellValue, row }) {
|
||||
@@ -70,6 +71,7 @@ const createColumns = (
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "state",
|
||||
accessorKey: "state",
|
||||
size: 100,
|
||||
header: t("field.state.label"),
|
||||
@@ -78,6 +80,13 @@ const createColumns = (
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "cpuUsage",
|
||||
sortingFn: (rowA, rowB) => {
|
||||
const cpuUsageA = safeValue(rowA.original.cpuUsage);
|
||||
const cpuUsageB = safeValue(rowB.original.cpuUsage);
|
||||
|
||||
return cpuUsageA - cpuUsageB;
|
||||
},
|
||||
accessorKey: "cpuUsage",
|
||||
size: 80,
|
||||
header: t("field.stats.cpu.label"),
|
||||
@@ -92,6 +101,13 @@ const createColumns = (
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "memoryUsage",
|
||||
sortingFn: (rowA, rowB) => {
|
||||
const memoryUsageA = safeValue(rowA.original.memoryUsage);
|
||||
const memoryUsageB = safeValue(rowB.original.memoryUsage);
|
||||
|
||||
return memoryUsageA - memoryUsageB;
|
||||
},
|
||||
accessorKey: "memoryUsage",
|
||||
size: 80,
|
||||
header: t("field.stats.memory.label"),
|
||||
@@ -106,9 +122,11 @@ const createColumns = (
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
accessorKey: "actions",
|
||||
size: 80,
|
||||
header: t("action.title"),
|
||||
enableSorting: false,
|
||||
Cell({ row }) {
|
||||
const utils = clientApi.useUtils();
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@@ -168,7 +186,7 @@ const createColumns = (
|
||||
},
|
||||
];
|
||||
|
||||
export default function DockerWidget({ width }: WidgetComponentProps<"dockerContainers">) {
|
||||
export default function DockerWidget({ options, width, isEditMode }: WidgetComponentProps<"dockerContainers">) {
|
||||
const t = useScopedI18n("docker");
|
||||
const isTiny = width <= 256;
|
||||
|
||||
@@ -192,8 +210,8 @@ export default function DockerWidget({ width }: WidgetComponentProps<"dockerCont
|
||||
enablePagination: false,
|
||||
enableTopToolbar: false,
|
||||
enableBottomToolbar: false,
|
||||
enableSorting: false,
|
||||
enableColumnActions: false,
|
||||
enableSorting: options.enableRowSorting && !isEditMode,
|
||||
enableStickyHeader: false,
|
||||
enableColumnOrdering: false,
|
||||
enableRowSelection: false,
|
||||
@@ -203,6 +221,7 @@ export default function DockerWidget({ width }: WidgetComponentProps<"dockerCont
|
||||
enableFilters: false,
|
||||
enableHiding: false,
|
||||
initialState: {
|
||||
sorting: [{ id: options.defaultSort, desc: options.descendingDefaultSort }],
|
||||
density: "xs",
|
||||
},
|
||||
mantinePaperProps: {
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
import { IconBrandDocker, IconServerOff } from "@tabler/icons-react";
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
|
||||
import { createWidgetDefinition } from "../definition";
|
||||
import { optionsBuilder } from "../options";
|
||||
|
||||
const columnsList = [
|
||||
"name",
|
||||
"state",
|
||||
"cpuUsage",
|
||||
"memoryUsage",
|
||||
] as const satisfies (keyof RouterOutputs["docker"]["getContainers"]["containers"][number])[];
|
||||
|
||||
export const { definition, componentLoader } = createWidgetDefinition("dockerContainers", {
|
||||
icon: IconBrandDocker,
|
||||
createOptions() {
|
||||
return optionsBuilder.from(() => ({}));
|
||||
return optionsBuilder.from((factory) => ({
|
||||
enableRowSorting: factory.switch({
|
||||
defaultValue: false,
|
||||
}),
|
||||
defaultSort: factory.select({
|
||||
defaultValue: "name",
|
||||
options: columnsList.map((value) => ({
|
||||
value,
|
||||
label: (t) => t(`widget.dockerContainers.option.defaultSort.option.${value}`),
|
||||
})),
|
||||
}),
|
||||
descendingDefaultSort: factory.switch({
|
||||
defaultValue: false,
|
||||
}),
|
||||
}));
|
||||
},
|
||||
errors: {
|
||||
INTERNAL_SERVER_ERROR: {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import type { MRT_ColumnDef, MRT_VisibilityState } from "mantine-react-table";
|
||||
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
|
||||
|
||||
@@ -48,6 +49,8 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
interface QuickFilter {
|
||||
integrationKinds: string[];
|
||||
statuses: ExtendedDownloadClientItem["state"][];
|
||||
@@ -188,14 +191,14 @@ export default function DownloadClientsWidget({
|
||||
)
|
||||
//Add extrapolated data and actions if user is allowed interaction
|
||||
.map((item): ExtendedDownloadClientItem => {
|
||||
const received = Math.floor(item.size * item.progress);
|
||||
const received = item.received ?? Math.floor(item.size * item.progress);
|
||||
const integrationIds = [pair.integration.id];
|
||||
return {
|
||||
integration: pair.integration,
|
||||
...item,
|
||||
category: item.category !== undefined && item.category.length > 0 ? item.category : undefined,
|
||||
received,
|
||||
ratio: item.sent !== undefined ? item.sent / (received || 1) : undefined,
|
||||
ratio: item.sent !== undefined ? item.sent / item.size : undefined,
|
||||
//Only add if permission to use mutations
|
||||
actions: integrationsWithInteractions.includes(pair.integration.id)
|
||||
? {
|
||||
@@ -470,13 +473,16 @@ export default function DownloadClientsWidget({
|
||||
return (
|
||||
<Group align="center" gap="xs" wrap="nowrap" w="100%">
|
||||
<Text size="xs">
|
||||
{new Intl.NumberFormat("en", { style: "percent", notation: "compact", unitDisplay: "narrow" }).format(
|
||||
progress,
|
||||
)}
|
||||
{new Intl.NumberFormat("en", {
|
||||
style: "percent",
|
||||
notation: "compact",
|
||||
unitDisplay: "narrow",
|
||||
roundingMode: "floor",
|
||||
}).format(progress)}
|
||||
</Text>
|
||||
<Progress
|
||||
w="100%"
|
||||
value={progress * 100}
|
||||
value={Math.floor(progress * 100)}
|
||||
color={row.original.state === "paused" ? "yellow" : progress === 1 ? "green" : "blue"}
|
||||
radius="lg"
|
||||
/>
|
||||
@@ -714,7 +720,10 @@ const ItemInfoModal = ({ items, currentIndex, opened, onClose }: ItemInfoModalPr
|
||||
/>
|
||||
{item.type !== "miscellaneous" && <NormalizedLine itemKey="ratio" values={item.ratio} />}
|
||||
<NormalizedLine itemKey="added" values={item.added === undefined ? "unknown" : dayjs(item.added).format()} />
|
||||
<NormalizedLine itemKey="time" values={item.time !== 0 ? dayjs().add(item.time).format() : "∞"} />
|
||||
<NormalizedLine
|
||||
itemKey="time"
|
||||
values={item.time !== 0 ? dayjs().add(item.time, "milliseconds").fromNow() : "∞"}
|
||||
/>
|
||||
<NormalizedLine itemKey="category" values={item.category} />
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function IFrameWidget({ options, isEditMode }: WidgetComponentPro
|
||||
className={classes.iframe}
|
||||
src={embedUrl}
|
||||
title="widget iframe"
|
||||
allow={allowedPermissions.join(" ")}
|
||||
allow={allowedPermissions}
|
||||
scrolling={allowScrolling ? "yes" : "no"}
|
||||
sandbox={sandboxFlags.join(" ")}
|
||||
>
|
||||
@@ -77,9 +77,13 @@ const UnsupportedProtocol = () => {
|
||||
const getAllowedPermissions = (
|
||||
permissions: Omit<WidgetComponentProps<"iframe">["options"], "embedUrl" | "allowScrolling">,
|
||||
) => {
|
||||
return objectEntries(permissions)
|
||||
.filter(([_key, value]) => value)
|
||||
.map(([key]) => permissionMapping[key]);
|
||||
return (
|
||||
objectEntries(permissions)
|
||||
.filter(([_key, value]) => value)
|
||||
// * means it applies to all origins
|
||||
.map(([key]) => `${permissionMapping[key]} *`)
|
||||
.join("; ")
|
||||
);
|
||||
};
|
||||
|
||||
const getSandboxFlags = (
|
||||
|
||||
@@ -41,21 +41,21 @@ import {
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { Color } from "@tiptap/extension-color";
|
||||
import Highlight from "@tiptap/extension-highlight";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import Table from "@tiptap/extension-table";
|
||||
import TableCell from "@tiptap/extension-table-cell";
|
||||
import TableHeader from "@tiptap/extension-table-header";
|
||||
import TableRow from "@tiptap/extension-table-row";
|
||||
import TaskItem from "@tiptap/extension-task-item";
|
||||
import TaskList from "@tiptap/extension-task-list";
|
||||
import TextAlign from "@tiptap/extension-text-align";
|
||||
import TextStyle from "@tiptap/extension-text-style";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import { Highlight } from "@tiptap/extension-highlight";
|
||||
import { Image } from "@tiptap/extension-image";
|
||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||
import { Table } from "@tiptap/extension-table";
|
||||
import { TableCell } from "@tiptap/extension-table-cell";
|
||||
import { TableHeader } from "@tiptap/extension-table-header";
|
||||
import { TableRow } from "@tiptap/extension-table-row";
|
||||
import { TaskItem } from "@tiptap/extension-task-item";
|
||||
import { TaskList } from "@tiptap/extension-task-list";
|
||||
import { TextAlign } from "@tiptap/extension-text-align";
|
||||
import { TextStyle } from "@tiptap/extension-text-style";
|
||||
import type { Editor } from "@tiptap/react";
|
||||
import { BubbleMenu, useEditor } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import { useEditor } from "@tiptap/react";
|
||||
import { BubbleMenu } from "@tiptap/react/menus";
|
||||
import { StarterKit } from "@tiptap/starter-kit";
|
||||
import type { Node } from "prosemirror-model";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
@@ -133,7 +133,8 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
|
||||
};
|
||||
},
|
||||
}),
|
||||
StarterKit,
|
||||
// we use a custom link implementation from mantine
|
||||
StarterKit.configure({ link: false }),
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
lastColumnResizable: false,
|
||||
@@ -170,8 +171,9 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W
|
||||
TaskList.configure({ itemTypeName: "taskItem" }),
|
||||
TextAlign.configure({ types: ["heading", "paragraph"] }),
|
||||
TextStyle,
|
||||
Underline,
|
||||
],
|
||||
shouldRerenderOnTransaction: true,
|
||||
immediatelyRender: false,
|
||||
content,
|
||||
onUpdate: ({ editor }) => {
|
||||
setContent(editor.getHTML());
|
||||
|
||||
@@ -13,12 +13,12 @@ function round(value: number) {
|
||||
return Math.round(value * 100) / 100;
|
||||
}
|
||||
|
||||
function calculateChange(valueA: number, valueB: number) {
|
||||
return valueA - valueB;
|
||||
function calculateChange(currentPrice: number, previousClose: number) {
|
||||
return currentPrice - previousClose;
|
||||
}
|
||||
|
||||
function calculateChangePercentage(valueA: number, valueB: number) {
|
||||
return 100 * ((valueA - valueB) / valueA);
|
||||
function calculateChangePercentage(currentPrice: number, previousClose: number) {
|
||||
return 100 * ((currentPrice - previousClose) / previousClose);
|
||||
}
|
||||
|
||||
export default function StockPriceWidget({ options, width, height }: WidgetComponentProps<"stockPrice">) {
|
||||
@@ -26,9 +26,9 @@ export default function StockPriceWidget({ options, width, height }: WidgetCompo
|
||||
const theme = useMantineTheme();
|
||||
const [{ data }] = clientApi.widget.stockPrice.getPriceHistory.useSuspenseQuery(options);
|
||||
|
||||
const stockValuesChange = round(calculateChange(data.priceHistory.at(-1) ?? 0, data.priceHistory[0] ?? 0));
|
||||
const stockValuesChange = round(calculateChange(data.priceHistory.at(-1) ?? 0, data.previousClose));
|
||||
const stockValuesChangePercentage = round(
|
||||
calculateChangePercentage(data.priceHistory.at(-1) ?? 0, data.priceHistory[0] ?? 0),
|
||||
calculateChangePercentage(data.priceHistory.at(-1) ?? 0, data.previousClose),
|
||||
);
|
||||
|
||||
const stockValuesMin = Math.min(...data.priceHistory);
|
||||
|
||||
Reference in new issue
Block a user