diff --git a/apps/docs/docs/widgets/downloads/index.ts b/apps/docs/docs/widgets/downloads/index.ts index f10e9166d..b5f66f5ca 100644 --- a/apps/docs/docs/widgets/downloads/index.ts +++ b/apps/docs/docs/widgets/downloads/index.ts @@ -29,7 +29,8 @@ export const downloadsWidget: WidgetDefinition = { items: [ { name: "Columns to show", - description: "Select the columns you want to display in the widget. Columns automatically hide/show based on widget width.", + description: + "Select the columns you want to display in the widget. Columns automatically hide/show based on widget width.", values: `List of columns: ${columnsList.join(", ")}`, defaultValue: ["name", "progress", "downSpeed", "time", "state"].join(", "), }, diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index f3bc7c651..8900c34fc 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -84,11 +84,25 @@ interface ColumnContext { const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [ { maxWidth: 300, - config: { fontSize: "xs", iconSize: 12, cellPadding: 2, showSpeedColumns: false, showTimeColumn: false, showStateColumn: false }, + config: { + fontSize: "xs", + iconSize: 12, + cellPadding: 2, + showSpeedColumns: false, + showTimeColumn: false, + showStateColumn: false, + }, }, { maxWidth: 500, - config: { fontSize: "xs", iconSize: 14, cellPadding: 4, showSpeedColumns: false, showTimeColumn: true, showStateColumn: false }, + config: { + fontSize: "xs", + iconSize: 14, + cellPadding: 4, + showSpeedColumns: false, + showTimeColumn: true, + showStateColumn: false, + }, }, ]; @@ -257,7 +271,9 @@ function SpeedCell({ return ( - {formatByteRate(speed)} + + {formatByteRate(speed)} + ); } @@ -277,7 +293,11 @@ export default function DownloadClientsWidget({ [allInteractAccess, integrationIds], ); - const { data: currentItems = [], isFetching, isError } = clientApi.widget.downloads.getJobsAndStatuses.useQuery({ + const { + data: currentItems = [], + isFetching, + isError, + } = clientApi.widget.downloads.getJobsAndStatuses.useQuery({ integrationIds, limitPerIntegration: options.limitPerIntegration, }); @@ -332,9 +352,7 @@ export default function DownloadClientsWidget({ .filter(({ integration }) => integrationIds.includes(integration.id)) .flatMap((pair) => pair.data.items - .filter(({ category }) => - matchesCategoryFilter(category, options.categoryFilter, options.filterIsWhitelist), - ) + .filter(({ category }) => matchesCategoryFilter(category, options.categoryFilter, options.filterIsWhitelist)) .filter((item) => showCompletedItem(item, options)) .filter( ({ state }) => @@ -390,8 +408,7 @@ export default function DownloadClientsWidget({ .map(({ integration, data: clientData }): ExtendedClientStatus => { const interact = integrationInteractSet.has(integration.id); const isTorrent = getIntegrationKindsByCategory("torrent").some((kind) => kind === integration.kind); - const applyRatioFilter = - options.applyFilterToRatio && clientData.status.types.includes("torrent"); + const applyRatioFilter = options.applyFilterToRatio && clientData.status.types.includes("torrent"); let initialTotalUp: number | undefined; if (isTorrent) initialTotalUp = 0; @@ -422,7 +439,14 @@ export default function DownloadClientsWidget({ status: { totalUp, totalDown, ratio, ...clientData.status }, }; }); - }, [currentItems, integrationIds, integrationInteractSet, options.applyFilterToRatio, options.categoryFilter, options.filterIsWhitelist]); + }, [ + currentItems, + integrationIds, + integrationInteractSet, + options.applyFilterToRatio, + options.categoryFilter, + options.filterIsWhitelist, + ]); const hasTorrents = data.some(({ type }) => type === "torrent"); const hasMultipleClients = clients.length > 1; @@ -469,7 +493,9 @@ export default function DownloadClientsWidget({ > - {record.name} + + {record.name} + ), @@ -529,7 +555,12 @@ export default function DownloadClientsWidget({ width: 100, noWrap: true, render: (record) => { - if (record.time === 0) return ∞; + if (record.time === 0) + return ( + + ∞ + + ); return {dayjs().add(record.time, "milliseconds").fromNow(true)}; }, }, @@ -591,7 +622,11 @@ export default function DownloadClientsWidget({ render: (record) => { const display = formatCategoryDisplay(record.category); if (!display) return null; - return {display}; + return ( + + {display} + + ); }, }, includeColumn("index") && { @@ -606,23 +641,22 @@ export default function DownloadClientsWidget({ title: t("items.type.columnTitle"), sortable: true, width: 70, - render: (record) => {record.type}, + render: (record) => ( + + {record.type} + + ), }, ]; return cols.filter(Boolean) as DataTableColumn[]; }, [columnContext, t, size, progressColumnWidth]); - const storeKey = `downloads-${itemId ?? "preview"}-${[...options.columns].sort().join(",")}`; - const { - effectiveColumns, - columnsOrder, - columnsWidth, - setColumnsOrder, - setMultipleColumnWidths, - } = useDataTableColumns({ - key: storeKey, - columns, - }); + const storeKey = `downloads-${itemId ?? "preview"}-${[...options.columns].toSorted().join(",")}`; + const { effectiveColumns, columnsOrder, columnsWidth, setColumnsOrder, setMultipleColumnWidths } = + useDataTableColumns({ + key: storeKey, + columns, + }); const lastStoreKey = useRef(storeKey); const hydrated = useRef(false); @@ -637,7 +671,9 @@ export default function DownloadClientsWidget({ setMultipleColumnWidths(Object.entries(savedWidths).map(([accessor, w]) => ({ accessor, width: w }))); } // ponytail: defer hydrated flag so the persist effect skips the restore echo - requestAnimationFrame(() => { hydrated.current = true; }); + requestAnimationFrame(() => { + hydrated.current = true; + }); }, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]); const prevOrder = useRef(columnsOrder); @@ -832,12 +868,18 @@ function buildHoverTooltip(record: ExtendedDownloadClientItem, t: DownloadsT): R return ( - {record.name} + + {record.name} + {lines.map(({ label, value }) => ( - {label} - {value} + + {label} + + + {value} + ))} @@ -871,24 +913,45 @@ function ExpandedRow({ item, collapse }: { item: ExtendedDownloadClientItem; col - {item.name} + + {item.name} + {t(`states.${item.state}`)} - + {item.downSpeed !== undefined && item.downSpeed > 0 && ( - } color="blue" /> + } + color="blue" + /> )} {item.upSpeed !== undefined && item.upSpeed > 0 && ( - } color="green" /> + } + color="green" + /> + )} + {item.ratio !== undefined && ( + )} - {item.ratio !== undefined && } - {item.sent !== undefined && } - {item.added !== undefined && } + {item.sent !== undefined && ( + + )} + {item.added !== undefined && ( + + )} {categoryDisplay && } @@ -898,13 +961,27 @@ function ExpandedRow({ item, collapse }: { item: ExtendedDownloadClientItem; col ); } -function DetailPair({ label, value, icon, color }: { label: string; value: string; icon?: React.ReactNode; color?: string }) { +function DetailPair({ + label, + value, + icon, + color, +}: { + label: string; + value: string; + icon?: React.ReactNode; + color?: string; +}) { return ( - {label}: + + {label}: + {icon} - {value} + + {value} + ); @@ -953,12 +1030,16 @@ function GlobalStatsBar({ - {formatByteRate(totalSpeed)} + + {formatByteRate(totalSpeed)} + {totalUpSpeed > 0 && ( - {formatByteRate(totalUpSpeed)} + + {formatByteRate(totalUpSpeed)} + )} {hasTorrents && globalRatio > 0 && ( @@ -983,15 +1064,7 @@ function GlobalStatsBar({ ); } -function RowContextMenu({ - state, - onClose, - t, -}: { - state: ContextMenuState; - onClose: () => void; - t: DownloadsT; -}) { +function RowContextMenu({ state, onClose, t }: { state: ContextMenuState; onClose: () => void; t: DownloadsT }) { const { item } = state; const isPaused = item.state === "paused"; const hasActions = !!item.actions; @@ -1021,7 +1094,14 @@ function RowContextMenu({ onClose(); }} > - + @@ -1148,7 +1228,11 @@ function WidgetFooter({ const hasActiveFilter = clientFilter.length > 0 || statusFilter.length > 0; const integrationsStatuses = { paused: [] as string[], active: [] as string[] }; - for (const { status, integration: { id }, interact } of clients) { + for (const { + status, + integration: { id }, + interact, + } of clients) { if (!status || !interact) continue; if (status.paused) integrationsStatuses.paused.push(id); else integrationsStatuses.active.push(id); @@ -1179,7 +1263,9 @@ function WidgetFooter({ {clients.length > 1 && ( - {t("items.integration.columnTitle")}: + + {t("items.integration.columnTitle")}: + {clients.map(({ integration }) => { const active = clientFilter.includes(integration.id); @@ -1207,7 +1293,9 @@ function WidgetFooter({ )} {availableStatuses.length > 1 && ( - {t("items.state.columnTitle")}: + + {t("items.state.columnTitle")}: + {availableStatuses.map((status) => { const active = statusFilter.includes(status); @@ -1317,7 +1405,6 @@ function WidgetFooter({ )} -