diff --git a/src/components/server.tsx b/src/components/server.tsx index 4c83d6e8..524c2884 100644 --- a/src/components/server.tsx +++ b/src/components/server.tsx @@ -28,6 +28,7 @@ import type { TorrentFilter } from "./filters"; import { DefaultFilter, Filters } from "./filters"; import { Statusbar } from "./statusbar"; import { TorrentTable, useInitialTorrentRequiredFields } from "./tables/torrenttable"; +import { TableDisplayContext } from "./tables/common"; import { MemoizedToolbar } from "./toolbar"; import { useSession, useTorrentList } from "queries"; import type { TorrentFieldsType } from "rpc/transmission"; @@ -194,91 +195,114 @@ export function Server({ hostname, tabsRef, toolbarExtra, toggleTabStrip }: Serv const [showFiltersPanel, { toggle: toggleFiltersPanel }] = useDisclosure(config.values.interface.showFiltersPanel); const [showDetailsPanel, { toggle: toggleDetailsPanel }] = useDisclosure(config.values.interface.showDetailsPanel); + const [colorizeUploadRatio, setColorizeUploadRatio] = useState(config.values.interface.colorizeUploadRatio); + const [showTableRowSeparators, setShowTableRowSeparators] = useState(config.values.interface.showTableRowSeparators); const [mainSplit, toggleMainSplit] = useToggle([ config.values.interface.mainSplit, config.values.interface.mainSplit === "vertical" ? "horizontal" : "vertical"]); + // The config object is mutable and does not trigger re-renders, so the new + // value is written before the state update. That way the tables pick it up + // on the very same render. + const toggleColorizeUploadRatio = useCallback(() => { + const next = !config.values.interface.colorizeUploadRatio; + config.values.interface.colorizeUploadRatio = next; + setColorizeUploadRatio(next); + }, [config]); + + const toggleTableRowSeparators = useCallback(() => { + const next = !config.values.interface.showTableRowSeparators; + config.values.interface.showTableRowSeparators = next; + setShowTableRowSeparators(next); + }, [config]); + useEffect(() => { config.values.interface.showFiltersPanel = showFiltersPanel; config.values.interface.showDetailsPanel = showDetailsPanel; + config.values.interface.colorizeUploadRatio = colorizeUploadRatio; + config.values.interface.showTableRowSeparators = showTableRowSeparators; config.values.interface.mainSplit = mainSplit; - }, [config, showFiltersPanel, showDetailsPanel, mainSplit]); + }, [config, showFiltersPanel, showDetailsPanel, colorizeUploadRatio, showTableRowSeparators, mainSplit]); return - - - {overlayVisible && - - {sessionIsLoading - ? - : sessionIsError - ? <> - Failed to load session - {(sessionError as Error).message} - - : session?.["rpc-version"] === undefined - ? Server does not appear to be transmission daemon - : rpcVersion < 14 - ? Transmission version 2.40 or higher is required. - : <>} - - } - - - - - - - : undefined} - right={ - + + + {overlayVisible && + + {sessionIsLoading + ? + : sessionIsError + ? <> + Failed to load session + {(sessionError as Error).message} + + : session?.["rpc-version"] === undefined + ? Server does not appear to be transmission daemon + : rpcVersion < 14 + ? Transmission version 2.40 or higher is required. + : <>} + + } + + - } - bottom={showDetailsPanel - ? - : undefined} - /> - - + + + + : undefined} + right={ + + } + bottom={showDetailsPanel + ? + : undefined} /> - - + + + + + ; } diff --git a/src/components/tables/common.tsx b/src/components/tables/common.tsx index 9fa0e044..1caec1be 100644 --- a/src/components/tables/common.tsx +++ b/src/components/tables/common.tsx @@ -518,6 +518,20 @@ export interface TrguiTableRef { setExpanded: (state: boolean) => void, } +// Display options shared by all tables. They are carried in a context instead +// of being read straight from the config, because table rows are memoized: +// a context change re-renders the consumers even when the memoized parent +// bails out on unchanged props. +export interface TableDisplaySettings { + colorizeUploadRatio: boolean, + showRowSeparators: boolean, +} + +export const TableDisplayContext = React.createContext({ + colorizeUploadRatio: true, + showRowSeparators: true, +}); + export function TrguiTable(props: { tablename: TableName, tableRef?: React.MutableRefObject, @@ -544,6 +558,8 @@ export function TrguiTable(props: { const [lastIndex, onRowClick] = useSelectHandler( table, props.selectedReducer, props.getRowId, props.setCurrent); + const tableDisplay = useContext(TableDisplayContext); + const [parentRef, rowHeight, virtualizer] = useTableVirtualizer(table.getRowModel().rows.length); const scrollToId = useMemo(() => { @@ -563,7 +579,7 @@ export function TrguiTable(props: { const [parentRefProxy, headerRefProxy, syncHeaderScroll] = useSynchronizedScroll(parentRef, headerRef); return ( -
+
; + } + // The upper bound is the server side seed ratio limit, the ratio at which + // the daemon stops seeding. It defaults to 2 when the daemon does not + // report one. + const limit = Number(session?.seedRatioLimit ?? 2); + let color: string | undefined = undefined; + if (colorizeUploadRatio) { + if (num < 1) { + color = "light-dark(var(--mantine-color-red-7), var(--mantine-color-red-4))"; + } else if (num < limit) { + color = "light-dark(var(--mantine-color-orange-7), var(--mantine-color-orange-4))"; + } else { + color = "light-dark(var(--mantine-color-green-7), var(--mantine-color-green-4))"; + } + } return ( -
- {num < 0 ? "" : Number(num).toFixed(2)} +
+ {Number(num).toFixed(2)}
); } diff --git a/src/components/toolbar.tsx b/src/components/toolbar.tsx index 9a46ba7a..001cb130 100644 --- a/src/components/toolbar.tsx +++ b/src/components/toolbar.tsx @@ -68,6 +68,8 @@ interface ToolbarProps { toggleDetailsPanel: () => void, toggleMainSplit: () => void, toggleTabStrip: () => void, + toggleColorizeUploadRatio: () => void, + toggleTableRowSeparators: () => void, } function useButtonHandlers( @@ -422,6 +424,16 @@ function Toolbar(props: ToolbarProps) { > Toggle tab strip } + + Toggle share ratio colouring + + + Toggle table row separators + Interface settings diff --git a/src/config.ts b/src/config.ts index 978b3146..f7e93709 100644 --- a/src/config.ts +++ b/src/config.ts @@ -161,6 +161,8 @@ interface Settings { compactDirectories: boolean, recursiveDirectories: boolean, showSizesInFilters: boolean, + colorizeUploadRatio: boolean, + showTableRowSeparators: boolean, statusBarSections: SectionsVisibility, statusBarGlobalSpeeds: boolean, showFiltersPanel: boolean, @@ -291,6 +293,8 @@ const DefaultSettings: Settings = { compactDirectories: false, recursiveDirectories: true, showSizesInFilters: true, + colorizeUploadRatio: true, + showTableRowSeparators: true, statusBarSections: StatusbarSections.map((section) => ({ section, visible: true, diff --git a/src/css/torrenttable.css b/src/css/torrenttable.css index 5992ce07..3c9b1301 100644 --- a/src/css/torrenttable.css +++ b/src/css/torrenttable.css @@ -80,6 +80,11 @@ width: 100%; } +/* Optional horizontal separator between table rows. */ +.torrent-table-container.with-row-separators .tr { + border-bottom: 1px solid light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.12)); +} + .torrent-table .tr:nth-child(odd)::before { content: ''; position: absolute;