fix(media widget): text size fix and added season + Episode info (#6605)
Co-authored-by: Thomas <thomas@ajnart.fr>
This commit is contained in:
1 parent
e842177d9b
commit
ebdfeb724b
61 files changed
+254
-182
No files matched your search
@@ -20,7 +20,9 @@ const sessionSchema = z.object({
|
||||
SeriesName: z.string().nullish(),
|
||||
Name: z.string().nullish(),
|
||||
SeasonName: z.string().nullish(),
|
||||
ParentIndexNumber: z.number().nullish(),
|
||||
EpisodeTitle: z.string().nullish(),
|
||||
IndexNumber: z.number().nullish(),
|
||||
Album: z.string().nullish(),
|
||||
EpisodeCount: z.number().nullish(),
|
||||
})
|
||||
@@ -114,7 +116,9 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
|
||||
type: convertJellyfinType(sessionInfo.NowPlayingItem.Type),
|
||||
name: sessionInfo.NowPlayingItem.SeriesName ?? sessionInfo.NowPlayingItem.Name ?? "",
|
||||
seasonName: sessionInfo.NowPlayingItem.SeasonName ?? "",
|
||||
seasonNumber: sessionInfo.NowPlayingItem.ParentIndexNumber ?? null,
|
||||
episodeName: sessionInfo.NowPlayingItem.EpisodeTitle,
|
||||
episodeNumber: sessionInfo.NowPlayingItem.IndexNumber ?? null,
|
||||
albumName: sessionInfo.NowPlayingItem.Album ?? "",
|
||||
episodeCount: sessionInfo.NowPlayingItem.EpisodeCount,
|
||||
playback: {
|
||||
|
||||
@@ -10,7 +10,9 @@ export interface StreamSession {
|
||||
type: "audio" | "video" | "tv" | "movie";
|
||||
name: string;
|
||||
seasonName: string | undefined;
|
||||
seasonNumber?: number | null;
|
||||
episodeName?: string | null;
|
||||
episodeNumber?: number | null;
|
||||
albumName?: string | null;
|
||||
episodeCount?: number | null;
|
||||
playback: {
|
||||
|
||||
@@ -64,7 +64,9 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
|
||||
type: convertJellyfinType(sessionInfo.NowPlayingItem.Type),
|
||||
name: sessionInfo.NowPlayingItem.SeriesName ?? sessionInfo.NowPlayingItem.Name ?? "",
|
||||
seasonName: sessionInfo.NowPlayingItem.SeasonName ?? "",
|
||||
seasonNumber: sessionInfo.NowPlayingItem.ParentIndexNumber ?? null,
|
||||
episodeName: sessionInfo.NowPlayingItem.EpisodeTitle,
|
||||
episodeNumber: sessionInfo.NowPlayingItem.IndexNumber ?? null,
|
||||
albumName: sessionInfo.NowPlayingItem.Album ?? "",
|
||||
episodeCount: sessionInfo.NowPlayingItem.EpisodeCount,
|
||||
playback: {
|
||||
|
||||
@@ -26,7 +26,9 @@ const sessions: StreamSession[] = [
|
||||
type: "video",
|
||||
name: "Breaking Bad",
|
||||
seasonName: "Season 5",
|
||||
seasonNumber: 5,
|
||||
episodeName: "Ozymandias",
|
||||
episodeNumber: 14,
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
playback: { state: null, positionMs: null, durationMs: null },
|
||||
@@ -58,7 +60,9 @@ const sessions: StreamSession[] = [
|
||||
type: "video",
|
||||
name: "Severance",
|
||||
seasonName: "Season 2",
|
||||
seasonNumber: 2,
|
||||
episodeName: "The We We Are",
|
||||
episodeNumber: 10,
|
||||
albumName: null,
|
||||
episodeCount: null,
|
||||
playback: { state: null, positionMs: null, durationMs: null },
|
||||
|
||||
@@ -80,8 +80,9 @@ interface Session {
|
||||
$: {
|
||||
grandparentTitle?: string;
|
||||
parentTitle?: string;
|
||||
parentIndex?: string;
|
||||
title?: string;
|
||||
index?: number;
|
||||
index?: string;
|
||||
type: string;
|
||||
live?: string;
|
||||
viewOffset?: string;
|
||||
|
||||
@@ -115,9 +115,11 @@ export class PlexIntegration extends Integration implements IMediaServerIntegrat
|
||||
type: mediaElement.$.live === "1" ? "tv" : PlexIntegration.getCurrentlyPlayingType(mediaElement.$.type),
|
||||
name: mediaElement.$.grandparentTitle ?? mediaElement.$.title ?? "Unknown",
|
||||
seasonName: mediaElement.$.parentTitle,
|
||||
seasonNumber: parseOptionalNumber(mediaElement.$.parentIndex),
|
||||
episodeName: mediaElement.$.title ?? null,
|
||||
episodeNumber: parseOptionalNumber(mediaElement.$.index),
|
||||
albumName: mediaElement.$.type === "track" ? (mediaElement.$.parentTitle ?? null) : null,
|
||||
episodeCount: mediaElement.$.index ?? null,
|
||||
episodeCount: parseOptionalNumber(mediaElement.$.index),
|
||||
playback: {
|
||||
state: playbackState,
|
||||
positionMs,
|
||||
|
||||
@@ -4130,6 +4130,7 @@
|
||||
"transcoding": "Transcoding",
|
||||
"paused": "Paused",
|
||||
"remaining": "{minutes} min left",
|
||||
"seasonEpisode": "S{season} E{episode}",
|
||||
"metadata": {
|
||||
"title": "Stats for nerds",
|
||||
"video": {
|
||||
|
||||
@@ -314,7 +314,7 @@ export function JsonPathTreePicker({
|
||||
{hasData && (
|
||||
<Combobox.Header>
|
||||
<Group gap="xs" px="xs" pt="xs" pb={4}>
|
||||
<Badge size="sm" color="teal" variant="light" leftSection={<IconCircleCheck size={12} />}>
|
||||
<Badge size="sm" color="teal" variant="light" leftSection={<IconCircleCheck size="var(--mantine-font-size-xs)" />}>
|
||||
{loadedHint}
|
||||
</Badge>
|
||||
<Text size="xs" c="dimmed">
|
||||
@@ -353,7 +353,7 @@ function TreeNodeOption({ node, selected, isSearching }: { node: FlatNode; selec
|
||||
<Group gap={4} wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
{node.hasChildren && (
|
||||
<IconChevronDown
|
||||
size={14}
|
||||
size="var(--mantine-font-size-sm)"
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
color: "var(--mantine-color-dimmed)",
|
||||
@@ -362,7 +362,7 @@ function TreeNodeOption({ node, selected, isSearching }: { node: FlatNode; selec
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<TypeIcon size={14} style={{ flexShrink: 0, opacity: 0.5 }} />
|
||||
<TypeIcon size="var(--mantine-font-size-sm)" style={{ flexShrink: 0, opacity: 0.5 }} />
|
||||
<Text
|
||||
size="sm"
|
||||
fw={selected ? 600 : 400}
|
||||
|
||||
@@ -76,7 +76,7 @@ export const WidgetCustomWidgetConfigurationInput = ({
|
||||
return (
|
||||
<Stack gap="md">
|
||||
{issues[0] && (
|
||||
<Alert color="yellow" variant="light" icon={<IconAlertTriangle size={16} />}>
|
||||
<Alert color="yellow" variant="light" icon={<IconAlertTriangle size="var(--mantine-font-size-md)" />}>
|
||||
<Text size="sm">{labels("needsAttention", { message: issues[0].message })}</Text>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -115,7 +115,7 @@ export const WidgetCustomWidgetSelectInput = ({
|
||||
>
|
||||
<Group wrap="nowrap" gap="sm">
|
||||
<Avatar src={def.iconUrl} size={36} radius="md" color="blue">
|
||||
<IconApi size={18} />
|
||||
<IconApi size="var(--mantine-font-size-lg)" />
|
||||
</Avatar>
|
||||
<Stack gap={1} style={{ minWidth: 0 }}>
|
||||
<Text size="sm" fw={600} truncate>
|
||||
@@ -152,7 +152,7 @@ export const WidgetCustomWidgetSelectInput = ({
|
||||
<Alert
|
||||
color="yellow"
|
||||
variant="light"
|
||||
icon={<IconAlertTriangle size={18} />}
|
||||
icon={<IconAlertTriangle size="var(--mantine-font-size-lg)" />}
|
||||
title={labels("migrationRequired")}
|
||||
>
|
||||
<Text size="xs">{labels("migrationDescription")}</Text>
|
||||
|
||||
@@ -89,7 +89,7 @@ export const WidgetDynamicSelectInput = ({
|
||||
style={{ marginInlineStart: "auto" }}
|
||||
color="currentColor"
|
||||
opacity={0.6}
|
||||
size={18}
|
||||
size="var(--mantine-font-size-lg)"
|
||||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -97,7 +97,7 @@ export const WidgetLocationInput = ({ property, kind, options }: CommonWidgetInp
|
||||
disabled={!selectionEnabled}
|
||||
onClick={onSearch}
|
||||
variant="light"
|
||||
leftSection={<IconListSearch size={16} />}
|
||||
leftSection={<IconListSearch size="var(--mantine-font-size-md)" />}
|
||||
>
|
||||
{tLocation("search")}
|
||||
</Button>
|
||||
@@ -257,7 +257,7 @@ const LocationSelectTableRow = ({ city, onLocationSelect, closeModal }: Location
|
||||
})}
|
||||
>
|
||||
<ActionIcon color="red" variant="subtle" onClick={onSelect}>
|
||||
<IconClick size={16} />
|
||||
<IconClick size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Table.Td>
|
||||
|
||||
@@ -229,19 +229,19 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
||||
})
|
||||
}
|
||||
variant="light"
|
||||
leftSection={<IconEdit size={15} />}
|
||||
leftSection={<IconEdit size="var(--mantine-font-size-sm)" />}
|
||||
size="xs"
|
||||
>
|
||||
{tRepository("edit.label")}
|
||||
</Button>
|
||||
|
||||
<ActionIcon variant="transparent" color="red" onClick={() => onRepositoryRemove(index)}>
|
||||
<IconTrash size={15} />
|
||||
<IconTrash size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
{Object.keys(form.errors).filter((key) => key.startsWith(`options.${property}.${index}.`)).length > 0 && (
|
||||
<Group align="center" justify="center" gap="xs" bg="red.1">
|
||||
<IconTriangleFilled size={15} color="var(--mantine-color-red-filled)" />
|
||||
<IconTriangleFilled size="var(--mantine-font-size-sm)" color="var(--mantine-color-red-filled)" />
|
||||
<Text size="sm" c="red">
|
||||
{tRepository("invalid")}
|
||||
</Text>
|
||||
@@ -371,7 +371,7 @@ const ProviderTokenInput = ({
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
size="xs"
|
||||
leftSection={<IconKey size={14} />}
|
||||
leftSection={<IconKey size="var(--mantine-font-size-sm)" />}
|
||||
/>
|
||||
{editing && value.trim() && (
|
||||
<Button size="xs" onClick={handleSave} loading={saving}>
|
||||
@@ -380,7 +380,7 @@ const ProviderTokenInput = ({
|
||||
)}
|
||||
{hasToken && !editing && (
|
||||
<ActionIcon variant="light" color="red" size="sm" onClick={handleDelete} loading={saving}>
|
||||
<IconTrash size={14} />
|
||||
<IconTrash size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
@@ -51,20 +51,20 @@ export const WidgetSelectInput = ({ property, kind, options }: CommonWidgetInput
|
||||
label: translateIfNecessary(t, option.label) ?? option.value,
|
||||
},
|
||||
)}
|
||||
leftSection={CurrentIcon && <CurrentIcon size={16} stroke={1.5} />}
|
||||
leftSection={CurrentIcon && <CurrentIcon size="var(--mantine-font-size-md)" stroke={1.5} />}
|
||||
renderOption={({ option, checked }) => {
|
||||
const Icon = getIconFor(options.options, String(option.value));
|
||||
|
||||
return (
|
||||
<Group flex="1" gap="xs">
|
||||
{Icon && <Icon color="currentColor" opacity={0.6} size={18} stroke={1.5} />}
|
||||
{Icon && <Icon color="currentColor" opacity={0.6} size="var(--mantine-font-size-lg)" stroke={1.5} />}
|
||||
{option.label}
|
||||
{checked && (
|
||||
<IconCheck
|
||||
style={{ marginInlineStart: "auto" }}
|
||||
color="currentColor"
|
||||
opacity={0.6}
|
||||
size={18}
|
||||
size="var(--mantine-font-size-lg)"
|
||||
stroke={1.5}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -263,7 +263,7 @@ const AnchorNoteWidgetContent = ({
|
||||
onClick={handleEdit}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
<IconEdit size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function AssistantWidget(props: WidgetComponentProps<"assistant">
|
||||
<Center h="100%" p="md">
|
||||
<Stack align="center" gap="xs" ta="center">
|
||||
<ThemeIcon variant="light" color="red" radius="xl" size="lg">
|
||||
<IconRobot size={20} />
|
||||
<IconRobot size="var(--mantine-font-size-xl)" />
|
||||
</ThemeIcon>
|
||||
<Text size="sm" fw={600}>
|
||||
{t("title")}
|
||||
|
||||
@@ -201,7 +201,7 @@ export default function BeszelSystemStatsWidget({
|
||||
<Button
|
||||
variant="default"
|
||||
size="compact-xs"
|
||||
leftSection={<IconServer size={14} />}
|
||||
leftSection={<IconServer size="var(--mantine-font-size-sm)" />}
|
||||
className={classes.beszelStatsSystemToggle}
|
||||
disabled={isSelectionSavePending}
|
||||
>
|
||||
|
||||
@@ -49,7 +49,7 @@ export const { definition, componentLoader } = createWidgetDefinition("bookmarks
|
||||
</Group>
|
||||
|
||||
<ActionIcon variant="transparent" color="red" onClick={removeItem}>
|
||||
<IconX size={20} />
|
||||
<IconX size="var(--mantine-font-size-xl)" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
);
|
||||
|
||||
@@ -303,7 +303,7 @@ const CalendarAgenda = ({
|
||||
disabled={isEditMode}
|
||||
onClick={() => setMonth(moveCalendarMonth(month, -1))}
|
||||
>
|
||||
<IconChevronLeft size={18} aria-hidden />
|
||||
<IconChevronLeft size="var(--mantine-font-size-lg)" aria-hidden />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Stack gap={0} miw={0}>
|
||||
@@ -323,7 +323,7 @@ const CalendarAgenda = ({
|
||||
disabled={isEditMode}
|
||||
onClick={() => setMonth(moveCalendarMonth(month, 1))}
|
||||
>
|
||||
<IconChevronRight size={18} aria-hidden />
|
||||
<IconChevronRight size="var(--mantine-font-size-lg)" aria-hidden />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function IntegrationErrorIndicator({ results }: IntegrationErrorIndicator
|
||||
return (
|
||||
<Tooltip label={label} position="left" withArrow>
|
||||
<ActionIcon variant="transparent" color="orange" size={28} aria-label={label} style={{ cursor: "help" }}>
|
||||
<IconAlertTriangle aria-hidden size={14} />
|
||||
<IconAlertTriangle aria-hidden size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function WidgetQueryErrorIndicator({ error, label }: WidgetQueryErrorIndi
|
||||
tabIndex={0}
|
||||
style={{ cursor: "help" }}
|
||||
>
|
||||
<IconAlertTriangle aria-hidden size={14} />
|
||||
<IconAlertTriangle aria-hidden size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,7 @@ export function ApplicationsSection({ applications, baseUrl, isTiny }: Applicati
|
||||
|
||||
return (
|
||||
<Accordion.Item value="applications">
|
||||
<Accordion.Control icon={isTiny ? null : <IconCloud size={16} />}>
|
||||
<Accordion.Control icon={isTiny ? null : <IconCloud size="var(--mantine-font-size-md)" />}>
|
||||
<Group gap="xs">
|
||||
<Text size="xs">{tCommon("applications")}</Text>
|
||||
<Badge variant="dot" color={getBadgeColor(runningApps, applications.length)} size="xs">
|
||||
|
||||
@@ -106,7 +106,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
>
|
||||
<IconLink size={12} />
|
||||
<IconLink size="var(--mantine-font-size-xs)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
{!isTiny && logsUrl && (
|
||||
@@ -121,7 +121,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
>
|
||||
<IconFileText size={12} />
|
||||
<IconFileText size="var(--mantine-font-size-xs)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
{!isTiny && (
|
||||
|
||||
@@ -43,7 +43,7 @@ export function ServersSection({
|
||||
return (
|
||||
<Accordion.Item value="servers">
|
||||
<Group gap={0} wrap="nowrap">
|
||||
<Accordion.Control icon={isTiny ? null : <IconServer size={16} />} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Accordion.Control icon={isTiny ? null : <IconServer size="var(--mantine-font-size-md)" />} style={{ flex: 1, minWidth: 0 }}>
|
||||
<Group gap="xs">
|
||||
<Text size="xs">{tCommon("servers")}</Text>
|
||||
<Badge variant="dot" color={getBadgeColor(onlineServers, servers.length)} size="xs">
|
||||
@@ -61,7 +61,7 @@ export function ServersSection({
|
||||
mr="xs"
|
||||
onClick={onToggleIp}
|
||||
>
|
||||
{showIp ? <IconEye size={12} /> : <IconEyeOff size={12} />}
|
||||
{showIp ? <IconEye size="var(--mantine-font-size-xs)" /> : <IconEyeOff size="var(--mantine-font-size-xs)" />}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Accordion.Panel p={4}>
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ServicesSection({ services, baseUrl, isTiny, isAdvanced }: Servi
|
||||
|
||||
return (
|
||||
<Accordion.Item value="services">
|
||||
<Accordion.Control icon={isTiny ? null : <IconStack2 size={16} />}>
|
||||
<Accordion.Control icon={isTiny ? null : <IconStack2 size="var(--mantine-font-size-md)" />}>
|
||||
<Group gap="xs">
|
||||
<Text size="xs">{tCommon("services")}</Text>
|
||||
<Badge variant="dot" color={getBadgeColor(runningServices, services.length)} size="xs">
|
||||
|
||||
@@ -90,7 +90,7 @@ const TimerModal = ({ opened, close, selectedIntegrationIds, disableDns }: Timer
|
||||
<Button
|
||||
variant="light"
|
||||
color="red"
|
||||
leftSection={<IconClockPause size={20} />}
|
||||
leftSection={<IconClockPause size="var(--mantine-font-size-xl)" />}
|
||||
h="2rem"
|
||||
w="12rem"
|
||||
onClick={handleSetTimer}
|
||||
|
||||
@@ -184,7 +184,7 @@ export default function DnsHoleControlsWidget({
|
||||
radius={board.itemRadius}
|
||||
flex={1}
|
||||
>
|
||||
<IconPlayerPlay className="dns-hole-controls-enable-all-icon" size={16} />
|
||||
<IconPlayerPlay className="dns-hole-controls-enable-all-icon" size="var(--mantine-font-size-md)" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -205,7 +205,7 @@ export default function DnsHoleControlsWidget({
|
||||
radius={board.itemRadius}
|
||||
flex={1}
|
||||
>
|
||||
<IconClockPause className="dns-hole-controls-timer-all-icon" size={16} />
|
||||
<IconClockPause className="dns-hole-controls-timer-all-icon" size="var(--mantine-font-size-md)" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@@ -223,7 +223,7 @@ export default function DnsHoleControlsWidget({
|
||||
radius={board.itemRadius}
|
||||
flex={1}
|
||||
>
|
||||
<IconPlayerStop className="dns-hole-controls-disable-all-icon" size={16} />
|
||||
<IconPlayerStop className="dns-hole-controls-disable-all-icon" size="var(--mantine-font-size-md)" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
@@ -378,7 +378,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
color="green"
|
||||
variant="light"
|
||||
>
|
||||
<IconPlayerPlay size={12} />
|
||||
<IconPlayerPlay size="var(--mantine-font-size-xs)" />
|
||||
</ActionIcon>
|
||||
) : (
|
||||
<ActionIcon
|
||||
@@ -390,7 +390,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
color="red"
|
||||
variant="light"
|
||||
>
|
||||
<IconPlayerStop size={12} />
|
||||
<IconPlayerStop size="var(--mantine-font-size-xs)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ActionIcon
|
||||
@@ -406,7 +406,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
display={isInteractPermitted ? undefined : "none"}
|
||||
disabled={!controlEnabled || !isEnabled}
|
||||
>
|
||||
<IconClockPause size={12} />
|
||||
<IconClockPause size="var(--mantine-font-size-xs)" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
)}
|
||||
@@ -436,7 +436,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
<IconCircleFilled
|
||||
className="dns-hole-controls-item-status-icon"
|
||||
color={dnsLightStatus(isEnabled)}
|
||||
size={16}
|
||||
size="var(--mantine-font-size-md)"
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -474,7 +474,7 @@ const ControlsCard: React.FC<ControlsCardProps> = ({
|
||||
open();
|
||||
}}
|
||||
>
|
||||
<IconClockPause className="dns-hole-controls-item-timer-icon" size={20} />
|
||||
<IconClockPause className="dns-hole-controls-item-timer-icon" size="var(--mantine-font-size-xl)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -430,7 +430,7 @@ export default function DockerWidget({
|
||||
wrap="nowrap"
|
||||
>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconBrandDocker size={20} style={{ flexShrink: 0 }} />
|
||||
<IconBrandDocker size="var(--mantine-font-size-xl)" style={{ flexShrink: 0 }} />
|
||||
<Text size="sm" truncate>
|
||||
{t("table.footer", { count: containers.length.toString() })}
|
||||
</Text>
|
||||
@@ -457,7 +457,7 @@ export default function DockerWidget({
|
||||
onClick={() => refreshInventory.mutate()}
|
||||
aria-label={t("table.refresh.lastUpdated", { when: relativeTime })}
|
||||
>
|
||||
<IconRefresh size={16} />
|
||||
<IconRefresh size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
@@ -491,7 +491,7 @@ function ContainerMenuButton({
|
||||
aria-label={t("title")}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<IconDots size={16} />
|
||||
<IconDots size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown w={containerMenuWidth} miw={containerMenuWidth} maw={containerMenuWidth}>
|
||||
@@ -525,7 +525,7 @@ function ContainerActionItems({
|
||||
{container.name}
|
||||
</Menu.Label>
|
||||
<Menu.Item
|
||||
leftSection={<IconFileText size={14} />}
|
||||
leftSection={<IconFileText size="var(--mantine-font-size-sm)" />}
|
||||
disabled={!handlers.canUse(container, "logs")}
|
||||
onClick={() => {
|
||||
handlers.onOpenLogs(container);
|
||||
@@ -537,7 +537,7 @@ function ContainerActionItems({
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
color={stateAction === "start" ? "green" : "red"}
|
||||
leftSection={<StateIcon size={14} />}
|
||||
leftSection={<StateIcon size="var(--mantine-font-size-sm)" />}
|
||||
disabled={!handlers.canUse(container, "lifecycle")}
|
||||
onClick={() => invokeAction(stateAction)}
|
||||
>
|
||||
@@ -545,7 +545,7 @@ function ContainerActionItems({
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
color="orange"
|
||||
leftSection={<IconRotateClockwise size={14} />}
|
||||
leftSection={<IconRotateClockwise size="var(--mantine-font-size-sm)" />}
|
||||
disabled={!handlers.canUse(container, "lifecycle")}
|
||||
onClick={() => invokeAction("restart")}
|
||||
>
|
||||
@@ -553,7 +553,7 @@ function ContainerActionItems({
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={<IconTrash size={14} />}
|
||||
leftSection={<IconTrash size="var(--mantine-font-size-sm)" />}
|
||||
disabled={!handlers.canUse(container, "remove")}
|
||||
onClick={() => invokeAction("remove")}
|
||||
>
|
||||
@@ -561,7 +561,7 @@ function ContainerActionItems({
|
||||
</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
leftSection={<IconCategoryPlus size={14} />}
|
||||
leftSection={<IconCategoryPlus size="var(--mantine-font-size-sm)" />}
|
||||
onClick={() => {
|
||||
handlers.onAddToHomarr(container);
|
||||
onClose();
|
||||
|
||||
@@ -279,7 +279,7 @@ function SpeedCell({
|
||||
const { Icon, color } = speedColumnConfig[direction];
|
||||
return (
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<Icon size={12} style={{ flexShrink: 0, opacity: 0.5 }} />
|
||||
<Icon size="var(--mantine-font-size-xs)" style={{ flexShrink: 0, opacity: 0.5 }} />
|
||||
<Text size={fontSize} c={color}>
|
||||
{formatByteRate(speed)}
|
||||
</Text>
|
||||
@@ -914,7 +914,7 @@ function ExpandedRow({ item, collapse }: { item: ExtendedDownloadClientItem; col
|
||||
<DetailPair
|
||||
label={t("items.downSpeed.detailsTitle")}
|
||||
value={formatByteRate(item.downSpeed)}
|
||||
icon={<IconDownload size={10} />}
|
||||
icon={<IconDownload size="var(--mantine-font-size-xs)" />}
|
||||
color="blue"
|
||||
/>
|
||||
)}
|
||||
@@ -922,7 +922,7 @@ function ExpandedRow({ item, collapse }: { item: ExtendedDownloadClientItem; col
|
||||
<DetailPair
|
||||
label={t("items.upSpeed.detailsTitle")}
|
||||
value={formatByteRate(item.upSpeed)}
|
||||
icon={<IconUpload size={10} />}
|
||||
icon={<IconUpload size="var(--mantine-font-size-xs)" />}
|
||||
color="green"
|
||||
/>
|
||||
)}
|
||||
@@ -1005,7 +1005,7 @@ function GlobalStatsBar({
|
||||
|
||||
<Tooltip label={t("stats.totalSize")} withArrow>
|
||||
<Group gap={4}>
|
||||
<IconDatabase size={12} style={{ opacity: 0.6 }} />
|
||||
<IconDatabase size="var(--mantine-font-size-xs)" style={{ opacity: 0.6 }} />
|
||||
<Text size="xs">{`${formatBytes(queueStats.completedSize)} / ${formatBytes(queueStats.totalSize)}`}</Text>
|
||||
</Group>
|
||||
</Tooltip>
|
||||
@@ -1013,14 +1013,14 @@ function GlobalStatsBar({
|
||||
|
||||
<Group gap="md">
|
||||
<Group gap={4}>
|
||||
<IconDownload size={12} style={{ opacity: 0.6 }} />
|
||||
<IconDownload size="var(--mantine-font-size-xs)" style={{ opacity: 0.6 }} />
|
||||
<Text size="xs" fw={600} c="blue">
|
||||
{formatByteRate(totalSpeed)}
|
||||
</Text>
|
||||
</Group>
|
||||
{totalUpSpeed > 0 && (
|
||||
<Group gap={4}>
|
||||
<IconUpload size={12} style={{ opacity: 0.6 }} />
|
||||
<IconUpload size="var(--mantine-font-size-xs)" style={{ opacity: 0.6 }} />
|
||||
<Text size="xs" fw={600} c="green">
|
||||
{formatByteRate(totalUpSpeed)}
|
||||
</Text>
|
||||
@@ -1093,7 +1093,7 @@ function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClos
|
||||
<Menu.Label>{truncateText(item.name, 40)}</Menu.Label>
|
||||
|
||||
<Menu.Item
|
||||
leftSection={<IconInfoCircle size={14} />}
|
||||
leftSection={<IconInfoCircle size="var(--mantine-font-size-sm)" />}
|
||||
onClick={() => {
|
||||
void navigator.clipboard.writeText(item.name).catch(() => {});
|
||||
onClose();
|
||||
@@ -1104,7 +1104,7 @@ function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClos
|
||||
|
||||
{item.id && (
|
||||
<Menu.Item
|
||||
leftSection={<IconCopy size={14} />}
|
||||
leftSection={<IconCopy size="var(--mantine-font-size-sm)" />}
|
||||
onClick={() => {
|
||||
void navigator.clipboard.writeText(item.id).catch(() => {});
|
||||
onClose();
|
||||
@@ -1118,7 +1118,7 @@ function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClos
|
||||
<>
|
||||
<Menu.Divider />
|
||||
<Menu.Item
|
||||
leftSection={<PauseResumeIcon size={14} />}
|
||||
leftSection={<PauseResumeIcon size="var(--mantine-font-size-sm)" />}
|
||||
onClick={() => {
|
||||
pauseResumeInvoke?.();
|
||||
onClose();
|
||||
@@ -1130,7 +1130,7 @@ function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClos
|
||||
{!showDeleteConfirm && (
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={<IconTrash size={14} />}
|
||||
leftSection={<IconTrash size="var(--mantine-font-size-sm)" />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openDelete();
|
||||
@@ -1144,7 +1144,7 @@ function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClos
|
||||
<>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={<IconTrash size={14} />}
|
||||
leftSection={<IconTrash size="var(--mantine-font-size-sm)" />}
|
||||
onClick={() => {
|
||||
item.actions?.delete({ fromDisk: false });
|
||||
onClose();
|
||||
@@ -1154,7 +1154,7 @@ function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClos
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
color="red"
|
||||
leftSection={<IconTrashX size={14} />}
|
||||
leftSection={<IconTrashX size="var(--mantine-font-size-sm)" />}
|
||||
onClick={() => {
|
||||
item.actions?.delete({ fromDisk: true });
|
||||
onClose();
|
||||
@@ -1321,14 +1321,14 @@ function WidgetFooter({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FilterIcon size={14} />
|
||||
<FilterIcon size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
|
||||
{toggleStats ? (
|
||||
<Tooltip label={statsTooltip}>
|
||||
<ActionIcon size="xs" variant={statsIconVariant} aria-label={statsTooltip} onClick={toggleStats}>
|
||||
<StatsIcon size={14} />
|
||||
<StatsIcon size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
@@ -1356,7 +1356,7 @@ function WidgetFooter({
|
||||
disabled={integrationsStatuses.paused.length === 0}
|
||||
onClick={() => resumeQueue({ integrationIds: integrationsStatuses.paused })}
|
||||
>
|
||||
<IconPlayerPlay size={14} />
|
||||
<IconPlayerPlay size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -1364,12 +1364,12 @@ function WidgetFooter({
|
||||
{!showStats && (
|
||||
<Group gap={2}>
|
||||
<Text size="xs" fw={600} c="blue">
|
||||
<IconDownload size={10} style={{ verticalAlign: "middle", marginRight: 2 }} />
|
||||
<IconDownload size="var(--mantine-font-size-xs)" style={{ verticalAlign: "middle", marginRight: 2 }} />
|
||||
{formatByteRate(totalSpeed)}
|
||||
</Text>
|
||||
{totalUpSpeed > 0 && (
|
||||
<Text size="xs" fw={600} c="green">
|
||||
<IconUpload size={10} style={{ verticalAlign: "middle", marginRight: 2 }} />
|
||||
<IconUpload size="var(--mantine-font-size-xs)" style={{ verticalAlign: "middle", marginRight: 2 }} />
|
||||
{formatByteRate(totalUpSpeed)}
|
||||
</Text>
|
||||
)}
|
||||
@@ -1386,7 +1386,7 @@ function WidgetFooter({
|
||||
disabled={integrationsStatuses.active.length === 0}
|
||||
onClick={() => pauseQueue({ integrationIds: integrationsStatuses.active })}
|
||||
>
|
||||
<IconPlayerPause size={14} />
|
||||
<IconPlayerPause size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -386,7 +386,7 @@ const InterfacesPanel = ({
|
||||
variant={isAdvanced ? "contained" : "default"}
|
||||
>
|
||||
<Accordion.Item value="interfaces">
|
||||
<Accordion.Control icon={<IconTopologyBus size={16} />}>
|
||||
<Accordion.Control icon={<IconTopologyBus size="var(--mantine-font-size-md)" />}>
|
||||
<Group justify="space-between" wrap="nowrap" gap="xs">
|
||||
<Text size="xs">{label}</Text>
|
||||
{hasError && (
|
||||
@@ -410,7 +410,7 @@ const InterfacesPanel = ({
|
||||
</Text>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconArrowBarUp
|
||||
size={12}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color="light-dark(var(--mantine-color-green-8), var(--mantine-color-green-3))"
|
||||
/>
|
||||
<Text size="xs" c="light-dark(var(--mantine-color-green-8), var(--mantine-color-green-3))">
|
||||
@@ -419,7 +419,7 @@ const InterfacesPanel = ({
|
||||
</Group>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconArrowBarDown
|
||||
size={12}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color="light-dark(var(--mantine-color-yellow-9), var(--mantine-color-yellow-3))"
|
||||
/>
|
||||
<Text size="xs" c="light-dark(var(--mantine-color-yellow-9), var(--mantine-color-yellow-3))">
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ResourceAccordionItem = ({
|
||||
}: PropsWithChildren<ResourceAccordionItemProps>) => {
|
||||
return (
|
||||
<Accordion.Item value={value}>
|
||||
<Accordion.Control icon={isTiny ? null : <Icon size={16} />}>
|
||||
<Accordion.Control icon={isTiny ? null : <Icon size="var(--mantine-font-size-md)" />}>
|
||||
<Group style={{ rowGap: "0" }} gap="xs">
|
||||
<Text size="xs">{title}</Text>
|
||||
<Badge variant="dot" color={badge.color} size="xs">
|
||||
|
||||
@@ -97,20 +97,20 @@ const ComputeResourceDetails = ({ item }: { item: ComputeResource }) => {
|
||||
const storage = formatBytesPair(item.storage.used, item.storage.total);
|
||||
return (
|
||||
<List>
|
||||
<List.Item icon={<IconCpu size={16} />}>
|
||||
<List.Item icon={<IconCpu size="var(--mantine-font-size-md)" />}>
|
||||
{t("cpu")} - {item.cpu.cores}
|
||||
</List.Item>
|
||||
<List.Item icon={<IconBrain size={16} />}>
|
||||
<List.Item icon={<IconBrain size="var(--mantine-font-size-md)" />}>
|
||||
{t("memory")} - {memory.used} / {memory.available}
|
||||
</List.Item>
|
||||
<List.Item icon={<IconDatabase size={16} />}>
|
||||
<List.Item icon={<IconDatabase size="var(--mantine-font-size-md)" />}>
|
||||
{t("storage")} - {storage.used} / {storage.available}
|
||||
</List.Item>
|
||||
<List.Item icon={<IconClockHour3 size={16} />}>
|
||||
<List.Item icon={<IconClockHour3 size="var(--mantine-font-size-md)" />}>
|
||||
{t("uptime")} - {dayjs(dayjs().add(-item.uptime, "seconds")).fromNow(true)}
|
||||
</List.Item>
|
||||
{item.haState && (
|
||||
<List.Item icon={<IconHeartBolt size={16} />}>
|
||||
<List.Item icon={<IconHeartBolt size="var(--mantine-font-size-md)" />}>
|
||||
{t("haState")} - {capitalize(item.haState)}
|
||||
</List.Item>
|
||||
)}
|
||||
@@ -152,15 +152,15 @@ const DiskStats = ({ item }: { item: ComputeResource }) => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<List.Item icon={<IconDatabase size={16} />}>
|
||||
<List.Item icon={<IconDatabase size="var(--mantine-font-size-md)" />}>
|
||||
<Group gap="sm">
|
||||
<Group gap={0}>
|
||||
<Text>{formatBytes(item.storage.write)}</Text>
|
||||
<IconArrowNarrowDown size={14} />
|
||||
<IconArrowNarrowDown size="var(--mantine-font-size-sm)" />
|
||||
</Group>
|
||||
<Group gap={0}>
|
||||
<Text>{formatBytes(item.storage.read)}</Text>
|
||||
<IconArrowNarrowUp size={14} />
|
||||
<IconArrowNarrowUp size="var(--mantine-font-size-sm)" />
|
||||
</Group>
|
||||
</Group>
|
||||
</List.Item>
|
||||
@@ -172,15 +172,15 @@ const NetStats = ({ item }: { item: ComputeResource }) => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<List.Item icon={<IconNetwork size={16} />}>
|
||||
<List.Item icon={<IconNetwork size="var(--mantine-font-size-md)" />}>
|
||||
<Group gap="sm">
|
||||
<Group gap={0}>
|
||||
<Text>{formatBytes(item.network.in)}</Text>
|
||||
<IconArrowNarrowDown size={14} />
|
||||
<IconArrowNarrowDown size="var(--mantine-font-size-sm)" />
|
||||
</Group>
|
||||
<Group gap={0}>
|
||||
<Text>{formatBytes(item.network.out)}</Text>
|
||||
<IconArrowNarrowUp size={14} />
|
||||
<IconArrowNarrowUp size="var(--mantine-font-size-sm)" />
|
||||
</Group>
|
||||
</Group>
|
||||
</List.Item>
|
||||
|
||||
@@ -178,7 +178,7 @@ function Carousel({
|
||||
size={40}
|
||||
onClick={() => move(-1)}
|
||||
>
|
||||
<IconChevronLeft size={18} />
|
||||
<IconChevronLeft size="var(--mantine-font-size-lg)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ActionIcon
|
||||
@@ -189,7 +189,7 @@ function Carousel({
|
||||
size={advanced ? 40 : 32}
|
||||
onClick={() => setPaused((value) => !value)}
|
||||
>
|
||||
{paused ? <IconPlayerPlay size={18} /> : <IconPlayerPause size={18} />}
|
||||
{paused ? <IconPlayerPlay size="var(--mantine-font-size-lg)" /> : <IconPlayerPause size="var(--mantine-font-size-lg)" />}
|
||||
</ActionIcon>
|
||||
{advanced && (
|
||||
<ActionIcon
|
||||
@@ -200,7 +200,7 @@ function Carousel({
|
||||
size={40}
|
||||
onClick={() => move(1)}
|
||||
>
|
||||
<IconChevronRight size={18} />
|
||||
<IconChevronRight size="var(--mantine-font-size-lg)" />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</Group>
|
||||
@@ -214,7 +214,7 @@ function Carousel({
|
||||
</Text>
|
||||
)}
|
||||
<Group gap="xs">
|
||||
<IconCalendar size={16} />
|
||||
<IconCalendar size="var(--mantine-font-size-md)" />
|
||||
<Text size="xs">{new Date(currentAsset.fileModifiedAt).toLocaleDateString(locale)}</Text>
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed">
|
||||
|
||||
@@ -59,7 +59,7 @@ export default function ImmichServerStatsWidget({
|
||||
<SimpleGrid cols={statsLayout.columns} spacing={statsLayout.dense ? 4 : "sm"}>
|
||||
{statVisibility.showUsers && (
|
||||
<StatItem
|
||||
icon={<IconUsers size={20} />}
|
||||
icon={<IconUsers size="var(--mantine-font-size-xl)" />}
|
||||
label={t("widget.immich-serverStats.users")}
|
||||
value={stats.userCount.toLocaleString(locale)}
|
||||
dense={statsLayout.dense}
|
||||
@@ -67,7 +67,7 @@ export default function ImmichServerStatsWidget({
|
||||
)}
|
||||
{statVisibility.showPhotos && (
|
||||
<StatItem
|
||||
icon={<IconPhoto size={20} />}
|
||||
icon={<IconPhoto size="var(--mantine-font-size-xl)" />}
|
||||
label={t("widget.immich-serverStats.photos")}
|
||||
value={stats.photoCount.toLocaleString(locale)}
|
||||
dense={statsLayout.dense}
|
||||
@@ -75,7 +75,7 @@ export default function ImmichServerStatsWidget({
|
||||
)}
|
||||
{statVisibility.showVideos && (
|
||||
<StatItem
|
||||
icon={<IconVideo size={20} />}
|
||||
icon={<IconVideo size="var(--mantine-font-size-xl)" />}
|
||||
label={t("widget.immich-serverStats.videos")}
|
||||
value={stats.videoCount.toLocaleString(locale)}
|
||||
dense={statsLayout.dense}
|
||||
@@ -83,7 +83,7 @@ export default function ImmichServerStatsWidget({
|
||||
)}
|
||||
{statVisibility.showStorage && (
|
||||
<StatItem
|
||||
icon={<IconDatabase size={20} />}
|
||||
icon={<IconDatabase size="var(--mantine-font-size-xl)" />}
|
||||
label={t("widget.immich-serverStats.storage")}
|
||||
value={formatBytes(stats.totalLibraryUsageInBytes)}
|
||||
dense={statsLayout.dense}
|
||||
|
||||
@@ -133,7 +133,7 @@ export default function IndexerManagerWidget({
|
||||
}}
|
||||
aria-label={t("widget.indexerManager.testAll")}
|
||||
>
|
||||
<IconTestPipe size={14} />
|
||||
<IconTestPipe size="var(--mantine-font-size-sm)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function MediaMissingWidget({
|
||||
{showMissing && (
|
||||
<Paper withBorder radius="sm" style={{ minHeight: 0, overflow: "hidden" }}>
|
||||
<Group p="xs" gap="xs">
|
||||
<IconQuestionMark size={16} />
|
||||
<IconQuestionMark size="var(--mantine-font-size-md)" />
|
||||
<Text size="sm" fw={600}>
|
||||
{tabLabel(t("tab.missing"), missing.length, missingCount)}
|
||||
</Text>
|
||||
@@ -152,7 +152,7 @@ export default function MediaMissingWidget({
|
||||
{showQueued && (
|
||||
<Paper withBorder radius="sm" style={{ minHeight: 0, overflow: "hidden" }}>
|
||||
<Group p="xs" gap="xs">
|
||||
<IconDownload size={16} />
|
||||
<IconDownload size="var(--mantine-font-size-md)" />
|
||||
<Text size="sm" fw={600}>
|
||||
{tabLabel(t("tab.queued"), queued.length, queuedCount)}
|
||||
</Text>
|
||||
@@ -181,12 +181,12 @@ export default function MediaMissingWidget({
|
||||
</Group>
|
||||
<Tabs.List grow>
|
||||
{showMissing && (
|
||||
<Tabs.Tab value="missing" px={isThin ? 6 : undefined} leftSection={<IconQuestionMark size={14} />}>
|
||||
<Tabs.Tab value="missing" px={isThin ? 6 : undefined} leftSection={<IconQuestionMark size="var(--mantine-font-size-sm)" />}>
|
||||
{tabLabel(t("tab.missing"), missing.length, missingCount)}
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
{showQueued && (
|
||||
<Tabs.Tab value="queued" px={isThin ? 6 : undefined} leftSection={<IconDownload size={14} />}>
|
||||
<Tabs.Tab value="queued" px={isThin ? 6 : undefined} leftSection={<IconDownload size="var(--mantine-font-size-sm)" />}>
|
||||
{tabLabel(t("tab.queued"), queued.length, queuedCount)}
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
|
||||
@@ -273,7 +273,7 @@ const InfoDivider = () => (
|
||||
const Info = ({ icon: Icon, label }: IconAndLabelProps) => {
|
||||
return (
|
||||
<Group gap={4} wrap="nowrap" miw={0}>
|
||||
{Icon && <Icon size={12} color={getMantineColor("gray", 5)} style={{ flexShrink: 0 }} />}
|
||||
{Icon && <Icon size="var(--mantine-font-size-xs)" color={getMantineColor("gray", 5)} style={{ flexShrink: 0 }} />}
|
||||
<Text size="xs" c="dimmed" truncate="end">
|
||||
{label}
|
||||
</Text>
|
||||
|
||||
@@ -94,7 +94,7 @@ const MediaRequestSearchButton = ({ integrationIds }: { integrationIds: string[]
|
||||
aria-label={t("action.search.label")}
|
||||
onClick={() => openMediaRequestSearch({ integrationIds })}
|
||||
>
|
||||
<IconSearch size={16} />
|
||||
<IconSearch size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -281,7 +281,7 @@ const DecisionButtons = ({ requestId, integrationId, canInteract, alwaysVisible
|
||||
handleDecision("approve");
|
||||
}}
|
||||
>
|
||||
<IconThumbUp size={16} />
|
||||
<IconThumbUp size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("pending.decline")}>
|
||||
@@ -296,7 +296,7 @@ const DecisionButtons = ({ requestId, integrationId, canInteract, alwaysVisible
|
||||
handleDecision("decline");
|
||||
}}
|
||||
>
|
||||
<IconThumbDown size={16} />
|
||||
<IconThumbDown size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
@@ -132,7 +132,7 @@ export default function MediaServerWidget({
|
||||
<Tooltip label={t(`titles.stats.${stat.name}`)}>
|
||||
<Card p={0} radius={board.itemRadius} className={classes.card}>
|
||||
<Group className="mediaRequests-stats-stat-stack" justify="center" align="center" gap="xs" w="100%">
|
||||
<stat.icon className="mediaRequests-stats-stat-icon" size={16} />
|
||||
<stat.icon className="mediaRequests-stats-stat-icon" size="var(--mantine-font-size-md)" />
|
||||
<Text className="mediaRequests-stats-stat-value" size="md">
|
||||
{stat.number}
|
||||
</Text>
|
||||
@@ -226,7 +226,7 @@ const MediaRequestSearchButton = ({ integrationIds }: { integrationIds: string[]
|
||||
aria-label={t("action.search.label")}
|
||||
onClick={() => openMediaRequestSearch({ integrationIds })}
|
||||
>
|
||||
<IconSearch size={16} />
|
||||
<IconSearch size="var(--mantine-font-size-md)" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
|
||||
.tableViewport :global(th),
|
||||
.tableViewport :global(td) {
|
||||
padding-block: 4px;
|
||||
padding-block: 8px;
|
||||
padding-inline: 8px;
|
||||
overflow: hidden;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tableViewport :global(th) {
|
||||
@@ -41,7 +43,6 @@
|
||||
|
||||
.cellContent {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.userIdentity {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import type { StreamSession } from "@homarr/integrations";
|
||||
|
||||
import { getMediaServerColumnVisibility, SessionDetailsPopover } from "./component";
|
||||
import { getMediaServerColumnVisibility, getSeasonEpisodeParams, SessionDetailsPopover } from "./component";
|
||||
|
||||
vi.mock("@homarr/translation/client", () => ({
|
||||
useScopedI18n: () => (key: string) => key,
|
||||
@@ -108,3 +108,19 @@ describe("getMediaServerColumnVisibility", () => {
|
||||
expect(getMediaServerColumnVisibility(0, true)).toEqual({ user: true, status: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSeasonEpisodeParams", () => {
|
||||
test("zero-pads season and episode numbers", () => {
|
||||
expect(getSeasonEpisodeParams(4, 12)).toEqual({ season: "04", episode: "12" });
|
||||
});
|
||||
|
||||
test("does not pad numbers already two digits or longer", () => {
|
||||
expect(getSeasonEpisodeParams(12, 104)).toEqual({ season: "12", episode: "104" });
|
||||
});
|
||||
|
||||
test("returns null when either number is missing", () => {
|
||||
expect(getSeasonEpisodeParams(undefined, 12)).toBeNull();
|
||||
expect(getSeasonEpisodeParams(4, null)).toBeNull();
|
||||
expect(getSeasonEpisodeParams(null, undefined)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -77,6 +77,16 @@ function formatBitrate(bitrateKbps: number | null | undefined): string | null {
|
||||
return bitrateKbps >= 1000 ? `${(bitrateKbps / 1000).toFixed(1)} Mbps` : `${Math.round(bitrateKbps)} kbps`;
|
||||
}
|
||||
|
||||
export function getSeasonEpisodeParams(
|
||||
seasonNumber: number | null | undefined,
|
||||
episodeNumber: number | null | undefined,
|
||||
): { season: string; episode: string } | null {
|
||||
if (seasonNumber === null || seasonNumber === undefined || episodeNumber === null || episodeNumber === undefined) {
|
||||
return null;
|
||||
}
|
||||
return { season: String(seasonNumber).padStart(2, "0"), episode: String(episodeNumber).padStart(2, "0") };
|
||||
}
|
||||
|
||||
const getSessionSortValue = (session: StreamSession, column: SortColumn) => {
|
||||
if (column === "user") return session.user?.username ?? session.sessionName;
|
||||
if (column === "currentlyPlaying") return session.currentlyPlaying?.name ?? session.sessionName;
|
||||
@@ -124,7 +134,7 @@ function StreamTableHeader({
|
||||
<Text component="span" size="xs" fw={600} truncate>
|
||||
{label}
|
||||
</Text>
|
||||
<SortIcon size={13} aria-hidden />
|
||||
<SortIcon size="var(--mantine-font-size-xs)" aria-hidden />
|
||||
</UnstyledButton>
|
||||
) : (
|
||||
<Text size="xs" fw={600} truncate>
|
||||
@@ -225,7 +235,7 @@ export default function MediaServerWidget({
|
||||
onChange={(event) => setSearch(event.currentTarget.value)}
|
||||
placeholder={tGlobal("search.placeholder")}
|
||||
aria-label={tGlobal("search.placeholder")}
|
||||
leftSection={<IconSearch size={14} aria-hidden />}
|
||||
leftSection={<IconSearch size="var(--mantine-font-size-xs)" aria-hidden />}
|
||||
/>
|
||||
<WidgetQueryErrorIndicator error={currentStreamsQuery.error} label={t("name")} />
|
||||
<IntegrationErrorIndicator results={currentStreams} />
|
||||
@@ -304,13 +314,13 @@ export default function MediaServerWidget({
|
||||
>
|
||||
{columnVisibility.user && (
|
||||
<Table.Td>
|
||||
<Group gap="xs" wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
<Avatar size={28} src={session.user?.profilePictureUrl} style={{ flexShrink: 0 }} />
|
||||
<div className={classes.userIdentity}>
|
||||
<Text component="div" size="xs" truncate className={classes.userIdentityLine}>
|
||||
<Group gap="xs" wrap="nowrap" w="100%">
|
||||
<Avatar size={28} src={session.user?.profilePictureUrl} />
|
||||
<Stack gap={2} className={classes.cellContent}>
|
||||
<Text size="xs" truncate>
|
||||
{session.user?.username ?? t("items.unknownUser")}
|
||||
</Text>
|
||||
<Text component="div" size="10px" c="dimmed" truncate className={classes.userIdentityLine}>
|
||||
<Text size="xs" c="dimmed" truncate>
|
||||
{session.sessionName}
|
||||
</Text>
|
||||
</div>
|
||||
@@ -329,22 +339,27 @@ export default function MediaServerWidget({
|
||||
{columnVisibility.status && (
|
||||
<Table.Td>
|
||||
{currentlyPlaying && (
|
||||
<Stack gap={4} align="flex-start" className={classes.cellContent}>
|
||||
<Stack gap={4} align="flex-start" w="100%" className={classes.cellContent}>
|
||||
<Badge size="xs" variant="light" color={playbackStatusColorMap[status]}>
|
||||
{t(`items.${status}` as never)}
|
||||
</Badge>
|
||||
{(location ?? bitrateLabel) && (
|
||||
<Group gap={4} align="center" justify="space-between" wrap="nowrap" w="100%">
|
||||
<Group gap={4} align="center" wrap="nowrap">
|
||||
{location && (location === "lan" ? <IconWifi size={12} /> : <IconWorld size={12} />)}
|
||||
{location &&
|
||||
(location === "lan" ? (
|
||||
<IconWifi size="var(--mantine-font-size-xs)" />
|
||||
) : (
|
||||
<IconWorld size="var(--mantine-font-size-xs)" />
|
||||
))}
|
||||
{location && (
|
||||
<Text size="10px" c="dimmed" tt="uppercase">
|
||||
<Text size="xs" c="dimmed" tt="uppercase">
|
||||
{location}
|
||||
</Text>
|
||||
)}
|
||||
</Group>
|
||||
{bitrateLabel && (
|
||||
<Text size="10px" c="dimmed" style={{ whiteSpace: "nowrap" }}>
|
||||
<Text size="xs" c="dimmed" style={{ whiteSpace: "nowrap" }}>
|
||||
{bitrateLabel}
|
||||
</Text>
|
||||
)}
|
||||
@@ -372,7 +387,7 @@ export default function MediaServerWidget({
|
||||
}}
|
||||
>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconVideo size={16} style={{ flexShrink: 0 }} />
|
||||
<IconVideo size="var(--mantine-font-size-xs)" style={{ flexShrink: 0 }} />
|
||||
<Text size="sm" style={{ whiteSpace: "nowrap" }}>
|
||||
{(t as unknown as (key: string, params?: { count: number }) => string)("footer.streams", {
|
||||
count: flatSessions.length,
|
||||
@@ -424,14 +439,25 @@ function CurrentlyPlaying({ item }: { item: StreamSession }) {
|
||||
: null;
|
||||
const remainingMinutes =
|
||||
positionMs !== null && durationMs !== null ? Math.max(0, Math.round((durationMs - positionMs) / 60_000)) : null;
|
||||
const seasonEpisodeParams = getSeasonEpisodeParams(currentlyPlaying.seasonNumber, currentlyPlaying.episodeNumber);
|
||||
const seasonEpisodeLabel = seasonEpisodeParams ? t("items.seasonEpisode", seasonEpisodeParams) : null;
|
||||
|
||||
return (
|
||||
<Stack gap={4} style={{ minWidth: 0 }}>
|
||||
<Stack gap={6} style={{ minWidth: 0 }}>
|
||||
<Group gap="xs" align="center" wrap="nowrap" style={{ minWidth: 0 }}>
|
||||
<Icon size={16} color={isPaused ? "var(--mantine-color-yellow-6)" : undefined} style={{ flexShrink: 0 }} />
|
||||
<Icon
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color={isPaused ? "var(--mantine-color-yellow-6)" : undefined}
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
<Text size="xs" lineClamp={1} style={{ minWidth: 0 }}>
|
||||
{currentlyPlaying.name}
|
||||
</Text>
|
||||
{seasonEpisodeLabel && (
|
||||
<Text size="xs" c="dimmed" style={{ flexShrink: 0 }}>
|
||||
{seasonEpisodeLabel}
|
||||
</Text>
|
||||
)}
|
||||
{isPaused ? (
|
||||
<Text size="xs" c="yellow" style={{ flexShrink: 0 }}>
|
||||
{t("items.paused")}
|
||||
|
||||
@@ -153,7 +153,7 @@ export default function MediaTranscodingWidget({
|
||||
return {
|
||||
label: (
|
||||
<Center style={{ gap: 4 }}>
|
||||
<Icon size={12} />
|
||||
<Icon size="var(--mantine-font-size-xs)" style={{ flexShrink: 0 }} />
|
||||
{footerLayout.showTabLabels ? (
|
||||
<Text span size="xs">
|
||||
{t(`tab.${value}`)}
|
||||
@@ -259,7 +259,7 @@ export const resolveQueuePagination = (
|
||||
const AdvancedPanel = ({ title, icon: Icon, children }: { title: string; icon: TablerIcon; children: ReactNode }) => (
|
||||
<Paper withBorder radius="sm" p="xs" mih={280} style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Group gap="xs" mb="xs">
|
||||
<Icon size={16} />
|
||||
<Icon size="var(--mantine-font-size-md)" style={{ flexShrink: 0 }} />
|
||||
<Text size="sm" fw={600}>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function HealthCheckStatus(props: HealthCheckStatusProps) {
|
||||
<Popover.Target>
|
||||
<ActionIcon className={actionTargetClasses.root} variant="subtle" size="sm" aria-label={t("title")}>
|
||||
<Indicator color={textColor(indicatorColor, colorScheme)} size={6} display="flex">
|
||||
<IconHeartbeat size={16} />
|
||||
<IconHeartbeat size="var(--mantine-font-size-md)" />
|
||||
</Indicator>
|
||||
</ActionIcon>
|
||||
</Popover.Target>
|
||||
@@ -38,7 +38,7 @@ export function HealthCheckStatus(props: HealthCheckStatusProps) {
|
||||
>
|
||||
<Stack gap="sm" align="center">
|
||||
<Group gap="xs">
|
||||
<IconHeartbeat size={18} />
|
||||
<IconHeartbeat size="var(--mantine-font-size-lg)" />
|
||||
<Text size="sm">{t("title")}</Text>
|
||||
</Group>
|
||||
<Divider
|
||||
|
||||
@@ -59,11 +59,11 @@ export function QueuePanel(props: QueuePanelProps) {
|
||||
<Group gap={4} wrap="nowrap">
|
||||
{item.type === "transcode" ? (
|
||||
<Tooltip label={t("table.transcode")}>
|
||||
<IconTransform size={12} />
|
||||
<IconTransform size="var(--mantine-font-size-xs)" style={{ flexShrink: 0 }} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip label={t("table.healthCheck")}>
|
||||
<IconHeartbeat size={12} />
|
||||
<IconHeartbeat size="var(--mantine-font-size-xs)" style={{ flexShrink: 0 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
<Text lineClamp={1} size="xs">
|
||||
|
||||
@@ -94,7 +94,7 @@ function StatisticItem(props: StatisticItemProps) {
|
||||
<Tooltip label={props.label}>
|
||||
<Card p={0} radius={board.itemRadius} miw={48} flex={1}>
|
||||
<Group justify="center" align="center" gap="xs" w="100%" wrap="nowrap">
|
||||
<props.icon size={16} style={{ minWidth: 16 }} />
|
||||
<props.icon size="var(--mantine-font-size-md)" style={{ flexShrink: 0 }} />
|
||||
<Text size="md">{props.value}</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
|
||||
@@ -66,11 +66,11 @@ export function WorkersPanel(props: WorkersPanelProps) {
|
||||
<div>
|
||||
{worker.jobType === "transcode" ? (
|
||||
<Tooltip label={t("table.transcode")}>
|
||||
<IconTransform size={14} />
|
||||
<IconTransform size="var(--mantine-font-size-sm)" style={{ flexShrink: 0 }} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip label={t("table.healthCheck")}>
|
||||
<IconHeartbeat size={14} />
|
||||
<IconHeartbeat size="var(--mantine-font-size-sm)" style={{ flexShrink: 0 }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -76,7 +76,7 @@ export const EmbeddedAppEditForm = ({ appId, handleRef }: EmbeddedAppEditFormPro
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Alert icon={<IconInfoCircle size={16} />} color="blue" variant="light">
|
||||
<Alert icon={<IconInfoCircle size="var(--mantine-font-size-md)" />} color="blue" variant="light">
|
||||
{t("item.edit.app.propagationNotice")}
|
||||
</Alert>
|
||||
<AppForm
|
||||
|
||||
@@ -111,7 +111,7 @@ export default function NetworkControllerSummaryWidget({
|
||||
<StatusIcon
|
||||
status={section.status}
|
||||
label={statusLabels[getBinaryStatusKey(section.status)]}
|
||||
size={16}
|
||||
size="var(--mantine-font-size-md)"
|
||||
/>
|
||||
<Text size="sm" fw={600}>
|
||||
{getMatrixSectionLabel(section.key, t)}
|
||||
@@ -210,7 +210,7 @@ export default function NetworkControllerSummaryWidget({
|
||||
<StatusIcon
|
||||
status={summary.lan.status}
|
||||
label={statusLabels[getBinaryStatusKey(summary.lan.status)]}
|
||||
size={20}
|
||||
size="var(--mantine-font-size-xl)"
|
||||
/>
|
||||
}
|
||||
>
|
||||
@@ -256,7 +256,15 @@ const getMatrixSectionLabel = (key: NetworkControllerMatrixSectionKey, t: Transl
|
||||
return t(`widget.networkControllerSummary.card.${key}`);
|
||||
};
|
||||
|
||||
const StatusIcon = ({ status, label, size }: { status?: "enabled" | "disabled"; label: string; size: number }) => {
|
||||
const StatusIcon = ({
|
||||
status,
|
||||
label,
|
||||
size,
|
||||
}: {
|
||||
status?: "enabled" | "disabled";
|
||||
label: string;
|
||||
size: number | string;
|
||||
}) => {
|
||||
const mantineTheme = useMantineTheme();
|
||||
if (status === "enabled") {
|
||||
return <IconCircleCheckFilled aria-label={label} size={size} color={mantineTheme.colors.green[6]} />;
|
||||
|
||||
@@ -445,7 +445,7 @@ function CompactStatGrid({
|
||||
style={{ "--stat-bg": severityToMantineBgColor(severity) } as CSSProperties}
|
||||
>
|
||||
{showIcon && (
|
||||
<Icon className={classes.statIcon} size={18} stroke={1.5} color={severityToIconColor(severity)} />
|
||||
<Icon className={classes.statIcon} size="var(--mantine-font-size-lg)" stroke={1.5} color={severityToIconColor(severity)} />
|
||||
)}
|
||||
<span className={classes.compactStatValue}>
|
||||
<Text component="span" fw={700} size="inherit" c={severityToMantineColor(severity)}>
|
||||
|
||||
@@ -303,20 +303,20 @@ export default function ReleasesWidget({ options, itemId, width, displayMode }:
|
||||
{hasError ? (
|
||||
<IconTriangleFilled
|
||||
className="releases-repository-header-releaseDate-icon releases-repository-header-releaseDate-error"
|
||||
size={10}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color="var(--mantine-color-red-filled)"
|
||||
/>
|
||||
) : repository.viewed ? (
|
||||
<IconCheck
|
||||
className="releases-repository-header-releaseDate-icon releases-repository-header-releaseDate-confirmed"
|
||||
size={10}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color="green"
|
||||
/>
|
||||
) : (
|
||||
(repository.isNewRelease || repository.isStaleRelease) && (
|
||||
<IconCircleFilled
|
||||
className="releases-repository-header-releaseDate-icon releases-repository-header-releaseDate-marker"
|
||||
size={10}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color={
|
||||
repository.isNewRelease
|
||||
? "var(--mantine-color-primaryColor-filled)"
|
||||
@@ -390,7 +390,7 @@ const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: Detai
|
||||
"releases-repository-details-icon",
|
||||
"releases-repository-details-icon-preRelease",
|
||||
)}
|
||||
size={13}
|
||||
size="var(--mantine-font-size-sm)"
|
||||
color={
|
||||
repository.isPreRelease ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"
|
||||
}
|
||||
@@ -411,7 +411,7 @@ const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: Detai
|
||||
"releases-repository-details-icon",
|
||||
"releases-repository-details-icon-archived",
|
||||
)}
|
||||
size={13}
|
||||
size="var(--mantine-font-size-sm)"
|
||||
color={
|
||||
repository.isArchived ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"
|
||||
}
|
||||
@@ -432,7 +432,7 @@ const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: Detai
|
||||
"releases-repository-details-icon",
|
||||
"releases-repository-details-icon-forked",
|
||||
)}
|
||||
size={13}
|
||||
size="var(--mantine-font-size-sm)"
|
||||
color={repository.isFork ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -459,7 +459,7 @@ const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: Detai
|
||||
"releases-repository-details-stats-icon",
|
||||
"releases-repository-details-stats-stars-icon",
|
||||
)}
|
||||
size={12}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color={!repository.starsCount ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
/>
|
||||
<Text
|
||||
@@ -501,7 +501,7 @@ const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: Detai
|
||||
"releases-repository-details-stats-icon",
|
||||
"releases-repository-details-stats-forks-icon",
|
||||
)}
|
||||
size={12}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color={!repository.forksCount ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
/>
|
||||
<Text
|
||||
@@ -543,7 +543,7 @@ const DetailsDisplay = ({ repository, isExpanded, toggleExpandedDisplay }: Detai
|
||||
"releases-repository-details-stats-icon",
|
||||
"releases-repository-details-stats-issues-icon",
|
||||
)}
|
||||
size={12}
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color={!repository.openIssues ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
|
||||
/>
|
||||
<Text
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function RssFeed({ options, width, height, displayMode }: WidgetC
|
||||
return (
|
||||
<Flex direction="column" h="100%" p="xs">
|
||||
{warning && (
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs">
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size="var(--mantine-font-size-md)" />} p="xs">
|
||||
<output>{warning}</output>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -103,7 +103,7 @@ export default function RssFeed({ options, width, height, displayMode }: WidgetC
|
||||
return (
|
||||
<ScrollArea className="scroll-area-w100" w="100%" h="100%" p={isTiny ? 4 : "xs"}>
|
||||
{warning && (
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs" mb="xs">
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size="var(--mantine-font-size-md)" />} p="xs" mb="xs">
|
||||
<output>{warning}</output>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -78,14 +78,19 @@ const TimetableWidgetInner = ({ station, baseUrl, itemId, displayMode, width, he
|
||||
)}
|
||||
{compactStaleWarning && displayMode === "compact" && (
|
||||
<Group gap={2} wrap="nowrap">
|
||||
<IconAlertTriangle aria-hidden size={12} color="var(--mantine-color-orange-light-color)" />
|
||||
<IconAlertTriangle
|
||||
aria-hidden
|
||||
size="var(--mantine-font-size-xs)"
|
||||
color="var(--mantine-color-orange-light-color)"
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
<Text component="output" size="xs" c="var(--mantine-color-text)" style={{ whiteSpace: "nowrap" }}>
|
||||
{compactStaleWarning}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{staleWarning && displayMode === "advanced" && (
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size={16} />} p="xs">
|
||||
<Alert role="presentation" color="orange" icon={<IconAlertTriangle aria-hidden size="var(--mantine-font-size-md)" />} p="xs">
|
||||
<output>{staleWarning}</output>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -18,14 +18,14 @@ export function StatsBar({
|
||||
|
||||
return (
|
||||
<SimpleGrid cols={cols} spacing="xs">
|
||||
<StatCard icon={<IconVideo size={16} />} label={t("stats.activeStreams")} value={stats.activeStreams} />
|
||||
<StatCard icon={<IconUsers size={16} />} label={t("stats.totalUsers")} value={stats.totalUsers} />
|
||||
<StatCard icon={<IconVideo size="var(--mantine-font-size-md)" />} label={t("stats.activeStreams")} value={stats.activeStreams} />
|
||||
<StatCard icon={<IconUsers size="var(--mantine-font-size-md)" />} label={t("stats.totalUsers")} value={stats.totalUsers} />
|
||||
<StatCard
|
||||
icon={<IconDevices size={16} />}
|
||||
icon={<IconDevices size="var(--mantine-font-size-md)" />}
|
||||
label={t("stats.transcodes")}
|
||||
value={`${summary.transcodes}/${summary.total}`}
|
||||
/>
|
||||
<StatCard icon={<IconNetwork size={16} />} label={t("stats.bandwidth")} value={summary.totalBitrate} />
|
||||
<StatCard icon={<IconNetwork size="var(--mantine-font-size-md)" />} label={t("stats.bandwidth")} value={summary.totalBitrate} />
|
||||
</SimpleGrid>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,9 +88,9 @@ function StreamCard({
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
|
||||
{stream.state === "playing" ? (
|
||||
<IconPlayerPlay size={14} color="var(--mantine-color-green-6)" />
|
||||
<IconPlayerPlay size="var(--mantine-font-size-sm)" color="var(--mantine-color-green-6)" />
|
||||
) : (
|
||||
<IconPlayerPause size={14} color="var(--mantine-color-yellow-6)" />
|
||||
<IconPlayerPause size="var(--mantine-font-size-sm)" color="var(--mantine-color-yellow-6)" />
|
||||
)}
|
||||
<Text size="sm" fw={600} lineClamp={1}>
|
||||
{stream.username}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function ViolationsList({
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" wrap="nowrap" style={{ overflow: "hidden" }}>
|
||||
<IconAlertTriangle
|
||||
size={14}
|
||||
size="var(--mantine-font-size-sm)"
|
||||
color={
|
||||
violation.severity === "high"
|
||||
? "var(--mantine-color-red-6)"
|
||||
|
||||
@@ -308,7 +308,7 @@ function ProtocolCard({ protocol, data }: { protocol: ProtocolKey; data: Traefik
|
||||
{rows.map(({ key, icon: Icon, summary }) => (
|
||||
<div key={key} className={classes.protocolRow}>
|
||||
<Group gap={4} wrap="nowrap" className={classes.protocolLabel}>
|
||||
<Icon size={14} />
|
||||
<Icon size="var(--mantine-font-size-sm)" />
|
||||
<Text>{t(`resource.${key}`)}</Text>
|
||||
</Group>
|
||||
<StatusCount summary={summary} />
|
||||
@@ -354,7 +354,7 @@ function HealthBadge({ errors, warnings }: { errors: number; warnings: number })
|
||||
|
||||
if (errors > 0) {
|
||||
return (
|
||||
<Badge color="red" variant="light" leftSection={<IconAlertTriangle size={12} />} radius="sm">
|
||||
<Badge color="red" variant="light" leftSection={<IconAlertTriangle size="var(--mantine-font-size-xs)" />} radius="sm">
|
||||
{t("status.errors", { count: errors })}
|
||||
</Badge>
|
||||
);
|
||||
@@ -362,14 +362,14 @@ function HealthBadge({ errors, warnings }: { errors: number; warnings: number })
|
||||
|
||||
if (warnings > 0) {
|
||||
return (
|
||||
<Badge color="yellow" variant="light" leftSection={<IconAlertTriangle size={12} />} radius="sm">
|
||||
<Badge color="yellow" variant="light" leftSection={<IconAlertTriangle size="var(--mantine-font-size-xs)" />} radius="sm">
|
||||
{t("status.warnings", { count: warnings })}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge color="green" variant="light" leftSection={<IconCircleCheck size={12} />} radius="sm">
|
||||
<Badge color="green" variant="light" leftSection={<IconCircleCheck size="var(--mantine-font-size-xs)" />} radius="sm">
|
||||
{t("status.healthy")}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -159,7 +159,7 @@ function VpnInfoColumn({
|
||||
</Text>
|
||||
{!dense && (city || country) && (
|
||||
<Group gap={4} justify="flex-start" wrap="nowrap">
|
||||
<IconMapPin aria-hidden size={10} />
|
||||
<IconMapPin aria-hidden size="var(--mantine-font-size-xs)" />
|
||||
<Text fs="italic" fw={500} size="sm" lh={1.2} lineClamp={1}>
|
||||
{[city, country].filter(Boolean).join(", ")}
|
||||
</Text>
|
||||
@@ -176,7 +176,7 @@ function VpnProviderDetails({ provider, protocol }: { provider: string; protocol
|
||||
<Text fz="xs" lh={1.2} tt="capitalize" lineClamp={1}>
|
||||
{provider}
|
||||
</Text>
|
||||
<IconArrowsExchange stroke={1} size={12} style={{ flexShrink: 0 }} />
|
||||
<IconArrowsExchange stroke={1} size="var(--mantine-font-size-xs)" style={{ flexShrink: 0 }} />
|
||||
<Text fz="xs" lh={1.2} tt="capitalize" lineClamp={1}>
|
||||
{protocol}
|
||||
</Text>
|
||||
@@ -196,7 +196,7 @@ function DnsStatusBadge({ status }: { status: string }) {
|
||||
color={getStatusColor(status)}
|
||||
size="xs"
|
||||
radius="xl"
|
||||
leftSection={<StatusIcon aria-hidden size={9} />}
|
||||
leftSection={<StatusIcon aria-hidden size="var(--mantine-font-size-xs)" />}
|
||||
>
|
||||
DNS
|
||||
<VisuallyHidden> {statusLabel}</VisuallyHidden>
|
||||
|
||||
@@ -116,11 +116,11 @@ const AdvancedWeather = ({ options, weather }: WeatherProps) => {
|
||||
|
||||
<Group gap="lg" wrap="wrap">
|
||||
<Group gap={5} wrap="nowrap">
|
||||
<IconMapPin size={16} aria-hidden />
|
||||
<IconMapPin size="var(--mantine-font-size-md)" aria-hidden />
|
||||
<Text size="sm">{options.location.name}</Text>
|
||||
</Group>
|
||||
<Group gap={5} wrap="nowrap">
|
||||
<IconWind size={16} aria-hidden />
|
||||
<IconWind size="var(--mantine-font-size-md)" aria-hidden />
|
||||
<Text size="sm">
|
||||
{t("currentWindSpeed", {
|
||||
currentWindSpeed: getPreferredWindSpeed(weather.current.windspeed, options.useImperialSpeed),
|
||||
@@ -196,7 +196,7 @@ const AdvancedWeather = ({ options, weather }: WeatherProps) => {
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<IconArrowUpRight size={15} aria-hidden />
|
||||
<IconArrowUpRight size="var(--mantine-font-size-sm)" aria-hidden />
|
||||
<Text size="sm">
|
||||
{getPreferredUnit(
|
||||
dayWeather.maxTemp,
|
||||
@@ -207,7 +207,7 @@ const AdvancedWeather = ({ options, weather }: WeatherProps) => {
|
||||
<Text size="sm" c="dimmed">
|
||||
/
|
||||
</Text>
|
||||
<IconArrowDownRight size={15} aria-hidden />
|
||||
<IconArrowDownRight size="var(--mantine-font-size-sm)" aria-hidden />
|
||||
<Text size="sm">
|
||||
{getPreferredUnit(
|
||||
dayWeather.minTemp,
|
||||
@@ -219,13 +219,13 @@ const AdvancedWeather = ({ options, weather }: WeatherProps) => {
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap={5} wrap="nowrap">
|
||||
<IconDroplets size={15} aria-hidden />
|
||||
<IconDroplets size="var(--mantine-font-size-sm)" aria-hidden />
|
||||
<Text size="sm">{dayWeather.humidity === undefined ? "?" : `${dayWeather.humidity}%`}</Text>
|
||||
</Group>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Group gap={5} wrap="nowrap">
|
||||
<IconWind size={15} aria-hidden />
|
||||
<IconWind size="var(--mantine-font-size-sm)" aria-hidden />
|
||||
<Text size="sm">
|
||||
{getPreferredWindSpeed(dayWeather.maxWindSpeed, options.useImperialSpeed)} /{" "}
|
||||
{getPreferredWindSpeed(dayWeather.maxWindGusts, options.useImperialSpeed)}
|
||||
@@ -235,14 +235,14 @@ const AdvancedWeather = ({ options, weather }: WeatherProps) => {
|
||||
<Table.Td>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Group gap={3} wrap="nowrap">
|
||||
<IconSunrise size={15} aria-hidden />
|
||||
<IconSunrise size="var(--mantine-font-size-sm)" aria-hidden />
|
||||
<Text size="sm">{getPreferredTime(dayWeather.sunrise, locale)}</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
/
|
||||
</Text>
|
||||
<Group gap={3} wrap="nowrap">
|
||||
<IconSunset size={15} aria-hidden />
|
||||
<IconSunset size="var(--mantine-font-size-sm)" aria-hidden />
|
||||
<Text size="sm">{getPreferredTime(dayWeather.sunset, locale)}</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
@@ -285,7 +285,7 @@ const DailyWeather = ({ options, weather, isEditMode }: CompactWeatherProps) =>
|
||||
<Stack gap="xs" align="center">
|
||||
{options.showCurrentWindSpeed && (
|
||||
<Group className="weather-current-wind-speed-group" wrap="nowrap" gap="xs">
|
||||
<IconWind size={16} />
|
||||
<IconWind size="var(--mantine-font-size-md)" />
|
||||
<Text fz={16}>
|
||||
{t("currentWindSpeed", {
|
||||
currentWindSpeed: (options.useImperialSpeed
|
||||
@@ -301,13 +301,13 @@ const DailyWeather = ({ options, weather, isEditMode }: CompactWeatherProps) =>
|
||||
)}
|
||||
{weather.daily[0]?.humidity !== undefined && (
|
||||
<Group className="weather-humidity-group" wrap="nowrap" gap="xs">
|
||||
<IconDroplets size={16} />
|
||||
<IconDroplets size="var(--mantine-font-size-md)" />
|
||||
<Text fz={16}>{t("dailyForecast.humidity", { humidity: weather.daily[0].humidity })}</Text>
|
||||
</Group>
|
||||
)}
|
||||
<Group className="weather-max-min-temp-group" wrap="nowrap" gap="sm">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<IconArrowUpRight size={16} />
|
||||
<IconArrowUpRight size="var(--mantine-font-size-md)" />
|
||||
<Text fz={16}>
|
||||
{getPreferredUnit(
|
||||
weather.daily[0]?.maxTemp,
|
||||
@@ -317,7 +317,7 @@ const DailyWeather = ({ options, weather, isEditMode }: CompactWeatherProps) =>
|
||||
</Text>
|
||||
</Group>
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<IconArrowDownRight size={16} />
|
||||
<IconArrowDownRight size="var(--mantine-font-size-md)" />
|
||||
<Text fz={16}>
|
||||
{getPreferredUnit(
|
||||
weather.daily[0]?.minTemp,
|
||||
@@ -331,7 +331,7 @@ const DailyWeather = ({ options, weather, isEditMode }: CompactWeatherProps) =>
|
||||
{options.showCity && (
|
||||
<>
|
||||
<Group className="weather-city-group" wrap="nowrap" gap="xs">
|
||||
<IconMapPin size={16} />
|
||||
<IconMapPin size="var(--mantine-font-size-md)" />
|
||||
<Text fz={16} style={{ whiteSpace: "nowrap" }}>
|
||||
{options.location.name}
|
||||
</Text>
|
||||
@@ -350,7 +350,7 @@ const WeeklyForecast = ({ options, weather, isEditMode, maxDays }: CompactWeathe
|
||||
<Group className="weather-forecast-city-temp-group" wrap="nowrap" gap="md">
|
||||
{options.showCity && (
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<IconMapPin size={16} />
|
||||
<IconMapPin size="var(--mantine-font-size-md)" />
|
||||
<Text fz={16} style={{ whiteSpace: "nowrap" }}>
|
||||
{options.location.name}
|
||||
</Text>
|
||||
|
||||
@@ -95,15 +95,15 @@ export const WeatherDescription = ({
|
||||
<Text fz="24px">{formatWeatherDate(time, locale, dateFormat)}</Text>
|
||||
<Text fz="16px">{t(`kind.${name}`)}</Text>
|
||||
<List>
|
||||
<List.Item icon={<IconTemperaturePlus size={15} />}>{`${tCommon("information.max")}: ${maxTemp}`}</List.Item>
|
||||
<List.Item icon={<IconTemperatureMinus size={15} />}>{`${tCommon("information.min")}: ${minTemp}`}</List.Item>
|
||||
<List.Item icon={<IconSun size={15} />}>{`${t("dailyForecast.sunrise")}: ${sunrise}`}</List.Item>
|
||||
<List.Item icon={<IconMoon size={15} />}>{`${t("dailyForecast.sunset")}: ${sunset}`}</List.Item>
|
||||
<List.Item icon={<IconTemperaturePlus size="var(--mantine-font-size-sm)" />}>{`${tCommon("information.max")}: ${maxTemp}`}</List.Item>
|
||||
<List.Item icon={<IconTemperatureMinus size="var(--mantine-font-size-sm)" />}>{`${tCommon("information.min")}: ${minTemp}`}</List.Item>
|
||||
<List.Item icon={<IconSun size="var(--mantine-font-size-sm)" />}>{`${t("dailyForecast.sunrise")}: ${sunrise}`}</List.Item>
|
||||
<List.Item icon={<IconMoon size="var(--mantine-font-size-sm)" />}>{`${t("dailyForecast.sunset")}: ${sunset}`}</List.Item>
|
||||
{humidity !== undefined && (
|
||||
<List.Item icon={<IconDroplets size={15} />}>{t("dailyForecast.humidity", { humidity })}</List.Item>
|
||||
<List.Item icon={<IconDroplets size="var(--mantine-font-size-sm)" />}>{t("dailyForecast.humidity", { humidity })}</List.Item>
|
||||
)}
|
||||
{maxWindSpeed !== undefined && (
|
||||
<List.Item icon={<IconWind size={15} />}>
|
||||
<List.Item icon={<IconWind size="var(--mantine-font-size-sm)" />}>
|
||||
{t("dailyForecast.maxWindSpeed", {
|
||||
maxWindSpeed: (useImperialSpeed ? metricToImperial(maxWindSpeed) : maxWindSpeed).toFixed(1),
|
||||
unit: useImperialSpeed ? tCommon("unit.speed.milesPerHour") : tCommon("unit.speed.kilometersPerHour"),
|
||||
@@ -111,7 +111,7 @@ export const WeatherDescription = ({
|
||||
</List.Item>
|
||||
)}
|
||||
{maxWindGusts !== undefined && (
|
||||
<List.Item icon={<IconWind size={15} />}>
|
||||
<List.Item icon={<IconWind size="var(--mantine-font-size-sm)" />}>
|
||||
{t("dailyForecast.maxWindGusts", {
|
||||
maxWindGusts: (useImperialSpeed ? metricToImperial(maxWindGusts) : maxWindGusts).toFixed(1),
|
||||
unit: useImperialSpeed ? tCommon("unit.speed.milesPerHour") : tCommon("unit.speed.kilometersPerHour"),
|
||||
|
||||
@@ -88,7 +88,7 @@ export const WidgetIntegrationSelect = ({
|
||||
return (
|
||||
<Combobox.Option value={item.id} key={item.id} active={multiSelectValues.includes(item.id)}>
|
||||
<Group gap="sm" align="center">
|
||||
{multiSelectValues.includes(item.id) ? <CheckIcon size={12} /> : null}
|
||||
{multiSelectValues.includes(item.id) ? <CheckIcon size="var(--mantine-font-size-xs)" /> : null}
|
||||
<Group gap={7} align="center">
|
||||
<Avatar src={getIconUrl(item.kind)} size="sm" />
|
||||
<Stack gap={0}>
|
||||
|
||||
Reference in new issue
Block a user