From 558973d4a8409fe3ca38fc05a6458f33cade0cf0 Mon Sep 17 00:00:00 2001 From: Sectumsempra82 Date: Sat, 12 Sep 2026 01:36:03 +0200 Subject: [PATCH 1/2] style(transfers): compact rows and preserve filename endings Reduce table and cell dimensions so progress, speed, ETA, and Stop fit on typical desktop widths. Shorten long paths in the middle using the available width, retaining complete filenames where possible and full text for hover and screen readers. --- src/components/PathLabel.tsx | 69 +++++++++++++++++++++++++++++++ src/components/TransfersTable.tsx | 65 ++++++++++++++--------------- 2 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 src/components/PathLabel.tsx diff --git a/src/components/PathLabel.tsx b/src/components/PathLabel.tsx new file mode 100644 index 0000000..2fe39a4 --- /dev/null +++ b/src/components/PathLabel.tsx @@ -0,0 +1,69 @@ +import { useLayoutEffect, useRef, useState } from 'react' +import { cn } from '@/lib/ui' + +export function PathLabel({ value, className }: { value: string; className?: string }) { + const ref = useRef(null) + const [label, setLabel] = useState(value) + + useLayoutEffect(() => { + const element = ref.current + const context = document.createElement('canvas').getContext('2d') + if (!element || !context) return + + const update = () => { + const style = getComputedStyle(element) + context.font = style.font + const width = element.clientWidth + const measure = (text: string) => context.measureText(text).width + if (measure(value) <= width) { + setLabel(value) + return + } + + const characters = Array.from(value) + const ellipsis = '...' + const filename = value.slice( + Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\'), value.lastIndexOf(':')) + + 1 + ) + const suffixBudget = + measure(ellipsis + filename) <= width + ? width - measure(ellipsis) + : Math.max(0, width * 0.8 - measure(ellipsis)) + let low = 0 + let high = characters.length + while (low < high) { + const middle = Math.ceil((low + high) / 2) + if (measure(characters.slice(-middle).join('')) <= suffixBudget) low = middle + else high = middle - 1 + } + const suffixLength = Math.min(low, Array.from(filename).length || low) + const suffix = suffixLength ? characters.slice(-suffixLength).join('') : '' + low = 0 + high = characters.length - suffixLength + while (low < high) { + const middle = Math.ceil((low + high) / 2) + if (measure(characters.slice(0, middle).join('') + ellipsis + suffix) <= width) + low = middle + else high = middle - 1 + } + setLabel(characters.slice(0, low).join('') + ellipsis + suffix) + } + + update() + const observer = new ResizeObserver(update) + observer.observe(element) + return () => observer.disconnect() + }, [value]) + + return ( + + + {value || '—'} + + ) +} diff --git a/src/components/TransfersTable.tsx b/src/components/TransfersTable.tsx index 35a7b99..70c0186 100644 --- a/src/components/TransfersTable.tsx +++ b/src/components/TransfersTable.tsx @@ -1,4 +1,5 @@ import { CheckCircle2Icon, RefreshCwIcon, XCircleIcon } from 'lucide-react' +import { PathLabel } from '@/components/PathLabel' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { @@ -25,9 +26,7 @@ function getTransferredLabel(job: JobRow) { } function TransferLocationCell({ value }: { value: string }) { - return ( - {value || '—'} - ) + return } const statusUi: Record< @@ -60,14 +59,14 @@ const statusUi: Record< } const columnWidths = { - id: 'w-[92px]', - status: 'w-[168px]', - source: 'w-[320px]', - destination: 'w-[372px]', - progress: 'w-[284px]', - speed: 'w-[152px]', - eta: 'w-[152px]', - actions: 'w-[124px]', + id: 'w-[64px]', + status: 'w-[112px]', + source: 'w-[240px]', + destination: 'w-[240px]', + progress: 'w-[220px]', + speed: 'w-[100px]', + eta: 'w-[100px]', + actions: 'w-[80px]', } as const export function TransfersTable({ @@ -82,13 +81,13 @@ export function TransfersTable({ const t = useT() return (
- +
{t('transfersTable.group')} @@ -96,7 +95,7 @@ export function TransfersTable({ {t('transfersTable.status')} @@ -104,7 +103,7 @@ export function TransfersTable({ {t('transfersTable.source')} @@ -112,7 +111,7 @@ export function TransfersTable({ {t('transfersTable.destination')} @@ -120,7 +119,7 @@ export function TransfersTable({ {t('transfersTable.progress')} @@ -128,7 +127,7 @@ export function TransfersTable({ {t('transfersTable.speed')} @@ -136,7 +135,7 @@ export function TransfersTable({ {t('transfersTable.eta')} @@ -144,7 +143,7 @@ export function TransfersTable({ {t('transfersTable.actions')} @@ -158,7 +157,7 @@ export function TransfersTable({ const badge = ( #{job.id} @@ -187,7 +186,7 @@ export function TransfersTable({
@@ -211,7 +210,7 @@ export function TransfersTable({ @@ -220,7 +219,7 @@ export function TransfersTable({ @@ -229,17 +228,17 @@ export function TransfersTable({ -
-
+
+
{job.progress}% {getTransferredLabel(job)}
-
+
{job.speedLabel} @@ -263,7 +262,7 @@ export function TransfersTable({ {job.etaLabel} @@ -272,13 +271,13 @@ export function TransfersTable({ {job.canStop ? (
+
+ {visibleJobs.length > 0 ? ( + stopMutation.mutate(jobid)} + isStopping={stopMutation.isPending} + /> + ) : ( +

+ {t('transfers.noActive')} +

+ )} +
+ ) : null} diff --git a/src/rclone/jobs.ts b/src/rclone/jobs.ts index 5121064..6cb8811 100644 --- a/src/rclone/jobs.ts +++ b/src/rclone/jobs.ts @@ -14,6 +14,7 @@ export type JobRow = { id: number status: 'running' | 'completed' | 'failed' startTime: string + completedAt?: string source: string destination: string bytes: number @@ -357,6 +358,7 @@ export async function fetchJobsSnapshot() { id: jobid, status: isFailed ? 'failed' : 'completed', startTime: status.startTime, + completedAt: getText(item.completed_at), source: source || '—', destination: destination || '—', bytes, @@ -421,7 +423,9 @@ export async function fetchJobsSnapshot() { } satisfies Record return [...runningRows, ...failedRows, ...completedRows].sort( - (a, b) => statusOrder[a.status] - statusOrder[b.status] + (a, b) => + statusOrder[a.status] - statusOrder[b.status] || + (Date.parse(b.completedAt ?? '') || 0) - (Date.parse(a.completedAt ?? '') || 0) ) }