fix: polish widget failure feedback
This commit is contained in:
7 files changed
+33
-22
No files matched your search
@@ -37,4 +37,4 @@ import streetDepartures from "./img/street-departures.png";
|
||||
|
||||
For security, all custom base URLs must resolve to a public IP address whenever Homarr uses them. Private and local hosts are rejected.
|
||||
|
||||
If a timetable refresh fails after departures have loaded, the widget keeps the previous departures visible and marks them with their last update time.
|
||||
If a timetable refresh fails after departures have loaded, the widget keeps the previous departures visible and marks them as stale. The advanced view also shows their last update time.
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CSSProperties, KeyboardEvent, MutableRefObject, PointerEvent } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { ActionIcon, Badge, Box, Card, FocusTrap, Text } from "@mantine/core";
|
||||
import { useElementSize, useMergedRef, useViewportSize } from "@mantine/hooks";
|
||||
import { useElementSize, useViewportSize } from "@mantine/hooks";
|
||||
import { IconMaximize, IconPin, IconX } from "@tabler/icons-react";
|
||||
import { QueryErrorResetBoundary, useQueryClient } from "@tanstack/react-query";
|
||||
import combineClasses from "clsx";
|
||||
@@ -37,9 +37,8 @@ const getOverflowFromKind = (kind: SectionItem["kind"]) => {
|
||||
};
|
||||
|
||||
export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
||||
const { ref, width, height } = useElementSize<HTMLDivElement>();
|
||||
const { ref: contentRef, width, height } = useElementSize<HTMLDivElement>();
|
||||
const sourceRef = useRef<HTMLDivElement>(null);
|
||||
const mergedRef = useMergedRef(ref, sourceRef);
|
||||
const { width: viewportWidth, height: viewportHeight } = useViewportSize();
|
||||
const board = useRequiredBoard();
|
||||
const t = useI18n();
|
||||
@@ -86,7 +85,7 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
||||
<>
|
||||
<WidgetContextMenu item={item} widgetStateRef={widgetStateRef} sourceRef={sourceRef}>
|
||||
<Box
|
||||
ref={mergedRef}
|
||||
ref={sourceRef}
|
||||
tabIndex={isEditMode ? undefined : 0}
|
||||
role={isEditMode ? undefined : "group"}
|
||||
aria-label={isEditMode ? undefined : `${widgetName}: ${t("item.advancedFocus.open")}`}
|
||||
@@ -192,13 +191,15 @@ export const BoardItemContent = ({ item }: BoardItemContentProps) => {
|
||||
</ActionIcon>
|
||||
)}
|
||||
</div>
|
||||
<InnerContent
|
||||
item={item}
|
||||
width={advancedRect?.width ?? width}
|
||||
height={advancedRect?.height ?? height}
|
||||
widgetStateRef={widgetStateRef}
|
||||
displayMode={isAdvanced ? "advanced" : "compact"}
|
||||
/>
|
||||
<Box ref={contentRef} w="100%" h="100%" mih={0}>
|
||||
<InnerContent
|
||||
item={item}
|
||||
width={width}
|
||||
height={height}
|
||||
widgetStateRef={widgetStateRef}
|
||||
displayMode={isAdvanced ? "advanced" : "compact"}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
</FocusTrap>
|
||||
</div>
|
||||
|
||||
@@ -2003,7 +2003,8 @@
|
||||
"stationSearch": "Unable to search stations. Check the custom URL and your board access."
|
||||
},
|
||||
"warning": {
|
||||
"stale": "The timetable could not be refreshed. Showing departures last updated at {time}."
|
||||
"stale": "The timetable could not be refreshed. Showing departures last updated at {time}.",
|
||||
"staleCompact": "Stale data"
|
||||
}
|
||||
},
|
||||
"notebook": {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function IntegrationErrorIndicator({ results }: IntegrationErrorIndicator
|
||||
const label = `${t("error")}: ${names}`;
|
||||
return (
|
||||
<Tooltip label={label} position="left" withArrow>
|
||||
<ActionIcon variant="transparent" color="orange" size={18} aria-label={label} style={{ cursor: "help" }}>
|
||||
<ActionIcon variant="transparent" color="orange" size={28} aria-label={label} style={{ cursor: "help" }}>
|
||||
<IconAlertTriangle aria-hidden size={14} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
@@ -325,7 +325,7 @@ export default function MediaServerWidget({
|
||||
</Group>
|
||||
)}
|
||||
{!isAdvanced && hasFailedIntegrations && (
|
||||
<Group h={18} px="xs" justify="flex-end">
|
||||
<Group px="xs" justify="flex-end">
|
||||
<IntegrationErrorIndicator results={currentStreams} />
|
||||
</Group>
|
||||
)}
|
||||
|
||||
@@ -60,8 +60,8 @@ export default function RssFeed({ options, width, displayMode }: WidgetComponent
|
||||
return (
|
||||
<ScrollArea className="scroll-area-w100" w="100%" h="100%" p={isAdvanced ? "md" : "xs"}>
|
||||
{warning && (
|
||||
<Alert role="status" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs" mb="xs">
|
||||
{warning}
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs" mb="xs">
|
||||
<output>{warning}</output>
|
||||
</Alert>
|
||||
)}
|
||||
<SimpleGrid cols={columns} w="100%" spacing={isAdvanced ? "md" : "xs"} verticalSpacing={isAdvanced ? "md" : "xs"}>
|
||||
|
||||
@@ -51,15 +51,24 @@ const TimetableWidgetInner = ({ station, baseUrl, itemId, displayMode, width }:
|
||||
if (error && timetable === undefined) throw error;
|
||||
|
||||
const entries = timetable?.entries ?? [];
|
||||
const staleWarning =
|
||||
error && timetable ? t("warning.stale", { time: dayjs(timetable.timestamp).format("HH:mm:ss") }) : undefined;
|
||||
const staleTime = timetable ? dayjs(timetable.timestamp).format("HH:mm:ss") : undefined;
|
||||
const staleWarning = error && staleTime ? t("warning.stale", { time: staleTime }) : undefined;
|
||||
const compactStaleWarning = error && staleTime ? t("warning.staleCompact") : undefined;
|
||||
|
||||
return (
|
||||
<Stack w="100%" h="100%" gap="xs" p="sm">
|
||||
<Text fw="bold">{t("title", { station: station.label })}</Text>
|
||||
{staleWarning && (
|
||||
<Alert role="status" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs">
|
||||
{staleWarning}
|
||||
{compactStaleWarning && displayMode === "compact" && (
|
||||
<Group gap={2} wrap="nowrap">
|
||||
<IconAlertTriangle aria-hidden size={12} color="var(--mantine-color-orange-6)" />
|
||||
<Text component="output" size="xs" c="orange" style={{ whiteSpace: "nowrap" }}>
|
||||
{compactStaleWarning}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{staleWarning && displayMode === "advanced" && (
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs">
|
||||
<output>{staleWarning}</output>
|
||||
</Alert>
|
||||
)}
|
||||
<ScrollArea style={{ flex: 1, minHeight: 0 }}>
|
||||
|
||||
Reference in new issue
Block a user