diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index f2559adcf..b109bcdd3 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -3099,7 +3099,8 @@ "detailsTitle": "Current index within client" }, "id": { - "columnTitle": "Id" + "columnTitle": "Id", + "detailsTitle": "Item ID" }, "integration": { "columnTitle": "Integration" @@ -3133,7 +3134,8 @@ }, "time": { "columnTitle": "Finish time", - "detailsTitle": "Time since/to completion" + "detailsTitle": "Time since/to completion", + "eta": "ETA" }, "type": { "columnTitle": "Type", @@ -3192,7 +3194,8 @@ "overallProgress": "Overall queue progress", "totalSize": "Total queue size", "clients": "{count, plural, one {# client} other {# clients}}" - } + }, + "disconnected": "(disconnected)" }, "mediaReleases": { "name": "Media releases", diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx index a0d6f829a..66db96eb0 100644 --- a/packages/widgets/src/downloads/component.tsx +++ b/packages/widgets/src/downloads/component.tsx @@ -300,7 +300,9 @@ export default function DownloadClientsWidget({ const { mutate: mutateResumeQueue } = clientApi.widget.downloads.resume.useMutation(mutationOptions); const { mutate: mutatePauseQueue } = clientApi.widget.downloads.pause.useMutation(mutationOptions); - const { mutate: saveItemOptions } = clientApi.widget.options.saveItemOptions.useMutation(); + const { mutate: saveItemOptions } = clientApi.widget.options.saveItemOptions.useMutation({ + onError: () => showErrorNotification({ title: t("errors.actionFailed"), message: t("errors.actionFailedMessage") }), + }); const persistOption = useCallback( (newOptions: Partial>) => { setOptions({ newOptions }); @@ -321,6 +323,9 @@ export default function DownloadClientsWidget({ columnAccessor: options.defaultSort, direction: defaultSortDirection, }); + useEffect(() => { + setSortStatus({ columnAccessor: options.defaultSort, direction: defaultSortDirection }); + }, [options.defaultSort, defaultSortDirection]); const data = useMemo(() => { return currentItems @@ -525,7 +530,7 @@ export default function DownloadClientsWidget({ noWrap: true, render: (record) => { if (record.time === 0) return ∞; - return {dayjs().add(record.time).fromNow(true)}; + return {dayjs().add(record.time, "milliseconds").fromNow(true)}; }, }, includeColumn("state") && { @@ -620,24 +625,25 @@ export default function DownloadClientsWidget({ }); const lastStoreKey = useRef(storeKey); - const initialSyncDone = useRef(false); + const hydrated = useRef(false); useEffect(() => { if (lastStoreKey.current !== storeKey) { lastStoreKey.current = storeKey; - initialSyncDone.current = false; + hydrated.current = false; } - if (initialSyncDone.current) return; - initialSyncDone.current = true; + if (hydrated.current) return; if (savedOrder.length > 0) setColumnsOrder(savedOrder); if (Object.keys(savedWidths).length > 0) { 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; }); }, [storeKey, savedOrder, savedWidths, setColumnsOrder, setMultipleColumnWidths]); const prevOrder = useRef(columnsOrder); const prevWidths = useRef(columnsWidth); useEffect(() => { - if (!initialSyncDone.current) return; + if (!hydrated.current) return; const orderChanged = JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current); const widthsChanged = JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current); prevOrder.current = columnsOrder; @@ -815,7 +821,7 @@ function buildHoverTooltip(record: ExtendedDownloadClientItem, t: DownloadsT): R lines.push({ label: t("items.upSpeed.detailsTitle"), value: formatByteRate(record.upSpeed) }); } if (record.time !== 0) { - lines.push({ label: "ETA", value: dayjs().add(record.time, "milliseconds").fromNow() }); + lines.push({ label: t("items.time.eta"), value: dayjs().add(record.time, "milliseconds").fromNow() }); } if (record.ratio !== undefined) { lines.push({ label: t("items.ratio.detailsTitle"), value: record.ratio.toFixed(2) }); @@ -885,7 +891,7 @@ function ExpandedRow({ item, collapse }: { item: ExtendedDownloadClientItem; col {item.sent !== undefined && } {item.added !== undefined && } {categoryDisplay && } - + @@ -1026,7 +1032,7 @@ function RowContextMenu({ } onClick={() => { - navigator.clipboard.writeText(item.name); + void navigator.clipboard.writeText(item.name).catch(() => {}); onClose(); }} > @@ -1037,7 +1043,7 @@ function RowContextMenu({ } onClick={() => { - navigator.clipboard.writeText(item.id); + void navigator.clipboard.writeText(item.id).catch(() => {}); onClose(); }} > @@ -1185,6 +1191,8 @@ function WidgetFooter({ return ( setClientFilter(toggleArrayItem(clientFilter, integration.id))} > setStatusFilter(toggleArrayItem(statusFilter, status))} > @@ -1232,6 +1242,7 @@ function WidgetFooter({ { if (hasActiveFilter && !filterOpen) { setClientFilter([]); @@ -1246,7 +1257,7 @@ function WidgetFooter({ - + @@ -1270,6 +1281,7 @@ function WidgetFooter({ size="xs" variant="subtle" color="green" + aria-label={t("actions.clients.resume")} disabled={integrationsStatuses.paused.length === 0} onClick={() => resumeQueue({ integrationIds: integrationsStatuses.paused })} > @@ -1299,6 +1311,7 @@ function WidgetFooter({ size="xs" variant="subtle" color="red" + aria-label={t("actions.clients.pause")} disabled={integrationsStatuses.active.length === 0} onClick={() => pauseQueue({ integrationIds: integrationsStatuses.active })} > @@ -1319,7 +1332,8 @@ function ClientIndicator({ integration }: { integration: ExtendedClientStatus["i const isConnected = useIntegrationConnected(integration.updatedAt, { timeout: 30000 }); let tooltipLabel = integration.name; - if (!isConnected) tooltipLabel = `${integration.name} (disconnected)`; + const t = useScopedI18n("widget.downloads"); + if (!isConnected) tooltipLabel = `${integration.name} ${t("disconnected")}`; let avatarFilter: string | undefined; let avatarOpacity = 1;