Skip to content

Commit 512b63c

Browse files
committed
Add option to show sizes in filters
Issue #325
1 parent 3ad3cc0 commit 512b63c

3 files changed

Lines changed: 93 additions & 38 deletions

File tree

src/components/filters.tsx

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import * as StatusIcons from "./statusicons";
2424
import type { FilterSectionName, SectionsVisibility, StatusFilterName } from "../config";
2525
import { ConfigContext, ServerConfigContext } from "../config";
2626
import { Box, Divider, Flex, Menu } from "@mantine/core";
27-
import { eventHasModKey, useForceRender } from "trutil";
27+
import { bytesToHumanReadableStr, eventHasModKey } from "trutil";
2828
import { useContextMenu } from "./contextmenu";
2929
import { MemoSectionsContextMenu, getSectionsMap } from "./sectionscontextmenu";
3030

@@ -131,7 +131,11 @@ interface FiltersProps extends WithCurrentFilters {
131131
interface FilterRowProps extends WithCurrentFilters {
132132
id: string,
133133
filter: NamedFilter,
134-
count: number,
134+
stats: {
135+
count: number,
136+
size: number,
137+
},
138+
showSizes: boolean,
135139
}
136140

137141
function focusNextFilter(element: HTMLElement, next: boolean) {
@@ -191,7 +195,11 @@ const FilterRow = React.memo(function FilterRow(props: FilterRowProps) {
191195
onKeyDown={filterOnKeyDown}>
192196
<div className="icon-container"><props.filter.icon /></div>
193197
<div style={{ flexShrink: 1, overflow: "hidden", textOverflow: "ellipsis" }}>{props.filter.name}</div>
194-
<div style={{ flexShrink: 0 }}>{`(${props.count})`}</div>
198+
<div style={{ flexShrink: 0 }}>{`(${props.stats.count})`}</div>
199+
{props.showSizes && <>
200+
<div style={{ flexGrow: 1 }}></div>
201+
<div style={{ flexShrink: 0 }}>{`[${bytesToHumanReadableStr(props.stats.size, 1)}]`}</div>
202+
</>}
195203
</Flex>;
196204
});
197205

@@ -215,6 +223,7 @@ interface DirFilterRowProps extends FiltersProps {
215223
id: string,
216224
dir: Directory,
217225
expandedReducer: ({ verb, value }: { verb: "add" | "remove", value: string }) => void,
226+
showSizes: boolean,
218227
}
219228

220229
function DirFilterRow(props: DirFilterRowProps) {
@@ -253,11 +262,17 @@ function DirFilterRow(props: DirFilterRowProps) {
253262
}
254263
}, [expandable, props]);
255264

256-
const count = useMemo(() => {
265+
const [count, size] = useMemo(() => {
257266
if (config.values.interface.recursiveDirectories || props.dir.count === props.dir.recursiveCount) {
258-
return `(${props.dir.recursiveCount})`;
267+
return [
268+
`(${props.dir.recursiveCount})`,
269+
`[${bytesToHumanReadableStr(props.dir.size, 1)}]`,
270+
];
259271
}
260-
return `(${props.dir.count}/${props.dir.recursiveCount})`;
272+
return [
273+
`(${props.dir.count}/${props.dir.recursiveCount})`,
274+
`[${bytesToHumanReadableStr(props.dir.size, 1)}]`,
275+
];
261276
}, [config.values.interface.recursiveDirectories, props.dir]);
262277

263278
return (
@@ -281,6 +296,10 @@ function DirFilterRow(props: DirFilterRowProps) {
281296
</div>
282297
<div style={{ flexShrink: 1, overflow: "hidden", textOverflow: "ellipsis" }}>{props.dir.name}</div>
283298
<div style={{ flexShrink: 0 }}>{count}</div>
299+
{props.showSizes && <>
300+
<div style={{ flexGrow: 1 }}></div>
301+
<div style={{ flexShrink: 0 }}>{size}</div>
302+
</>}
284303
</Flex>
285304
);
286305
}
@@ -292,6 +311,7 @@ interface Directory {
292311
expanded: boolean,
293312
count: number,
294313
recursiveCount: number,
314+
size: number,
295315
level: number,
296316
}
297317

@@ -302,13 +322,14 @@ const DefaultRoot: Directory = {
302322
expanded: true,
303323
count: 0,
304324
recursiveCount: 0,
325+
size: 0,
305326
level: -1,
306327
};
307328

308-
function buildDirTree(paths: string[], expanded: string[], compactDirectories: boolean): Directory {
329+
function buildDirTree(pathsAndSizes: Array<[string, number]>, expanded: string[], compactDirectories: boolean): Directory {
309330
const root: Directory = { ...DefaultRoot, subdirs: new Map() };
310331

311-
paths.forEach((path) => {
332+
pathsAndSizes.forEach(([path, size]) => {
312333
const parts = path.split("/");
313334
let dir = root;
314335
let currentPath = "";
@@ -323,11 +344,13 @@ function buildDirTree(paths: string[], expanded: string[], compactDirectories: b
323344
expanded: expanded.includes(currentPath),
324345
count: 0,
325346
recursiveCount: 0,
347+
size: 0,
326348
level: dir.level + 1,
327349
});
328350
}
329351
dir = dir.subdirs.get(part) as Directory;
330352
dir.recursiveCount++;
353+
dir.size += size;
331354
}
332355
dir.count++;
333356
});
@@ -374,10 +397,16 @@ function flattenTree(root: Directory): Directory[] {
374397
return result;
375398
}
376399

400+
function statCount(torrents: Torrent[]) {
401+
const count = torrents.length;
402+
const size = torrents.reduce((c, t) => c + t.sizeWhenDone, 0);
403+
return { count, size };
404+
}
405+
377406
export const Filters = React.memo(function Filters({ torrents, currentFilters, setCurrentFilters }: FiltersProps) {
378407
const config = useContext(ConfigContext);
379408
const serverConfig = useContext(ServerConfigContext);
380-
const forceRender = useForceRender();
409+
const [renderKey, forceRender] = useReducer((oldVal: number) => oldVal + 1, 0);
381410

382411
const expandedReducer = useCallback(
383412
({ verb, value }: { verb: "add" | "remove" | "set", value: string | string[] }) => {
@@ -393,28 +422,30 @@ export const Filters = React.memo(function Filters({ torrents, currentFilters, s
393422
forceRender();
394423
}, [forceRender, serverConfig]);
395424

396-
const paths = useMemo(
397-
() => torrents.map((t) => t.downloadDir as string).sort(),
425+
const pathsAndSizes = useMemo(
426+
() => torrents.map((t) => [t.downloadDir, t.sizeWhenDone] as [string, number]).sort(),
398427
[torrents]);
399428

400429
const dirs = useMemo<Directory[]>(() => {
401-
const tree = buildDirTree(paths, serverConfig.expandedDirFilters, config.values.interface.compactDirectories);
430+
const tree = buildDirTree(pathsAndSizes, serverConfig.expandedDirFilters, config.values.interface.compactDirectories);
402431
return flattenTree(tree);
403-
}, [paths, serverConfig.expandedDirFilters, config.values.interface.compactDirectories]);
432+
}, [pathsAndSizes, serverConfig.expandedDirFilters, config.values.interface.compactDirectories]);
404433

405434
const [labels, trackers] = useMemo(() => {
406-
const labels: Record<string, number> = {};
407-
const trackers: Record<string, number> = {};
408-
config.values.interface.preconfiguredLabels.forEach((label) => { labels[label] = 0; });
435+
const labels: Record<string, { count: number, size: number }> = {};
436+
const trackers: Record<string, { count: number, size: number }> = {};
437+
config.values.interface.preconfiguredLabels.forEach((label) => { labels[label] = { count: 0, size: 0 }; });
409438

410439
torrents.forEach((t) => t.labels?.forEach((l: string) => {
411-
if (!(l in labels)) labels[l] = 0;
412-
labels[l] = labels[l] + 1;
440+
if (!(l in labels)) labels[l] = { count: 0, size: 0 };
441+
labels[l].count += 1;
442+
labels[l].size += t.sizeWhenDone;
413443
}));
414444

415445
torrents.forEach((t) => {
416-
if (!(t.cachedMainTracker in trackers)) trackers[t.cachedMainTracker] = 0;
417-
trackers[t.cachedMainTracker] = trackers[t.cachedMainTracker] + 1;
446+
if (!(t.cachedMainTracker in trackers)) trackers[t.cachedMainTracker] = { count: 0, size: 0 };
447+
trackers[t.cachedMainTracker].count += 1;
448+
trackers[t.cachedMainTracker].size += t.sizeWhenDone;
418449
});
419450

420451
return [labels, trackers];
@@ -429,14 +460,16 @@ export const Filters = React.memo(function Filters({ torrents, currentFilters, s
429460
const [statusFiltersVisibility, setStatusFiltersVisibility] = useState(config.values.interface.statusFiltersVisibility);
430461
const [compactDirectories, setCompactDirectories] = useState(config.values.interface.compactDirectories);
431462
const [recursiveDirectories, setRecursiveDirectories] = useState(config.values.interface.recursiveDirectories);
463+
const [showSizes, setShowSizes] = useState(config.values.interface.showSizesInFilters);
432464

433465
useEffect(() => {
434466
config.values.interface.filterSections = sections;
435467
config.values.interface.statusFiltersVisibility = statusFiltersVisibility;
436468
config.values.interface.compactDirectories = compactDirectories;
437469
config.values.interface.recursiveDirectories = recursiveDirectories;
470+
config.values.interface.showSizesInFilters = showSizes;
438471
setSectionsMap(getSectionsMap(sections));
439-
}, [config, sections, statusFiltersVisibility, compactDirectories, recursiveDirectories]);
472+
}, [config, sections, statusFiltersVisibility, compactDirectories, recursiveDirectories, showSizes]);
440473

441474
const [info, setInfo, handler] = useContextMenu();
442475

@@ -465,6 +498,12 @@ export const Filters = React.memo(function Filters({ torrents, currentFilters, s
465498
setCurrentFilters({ verb: "set", filter: { id: "", filter: DefaultFilter } });
466499
}, [recursiveDirectories, setCurrentFilters]);
467500

501+
const onShowSizesClick = useCallback((e: React.MouseEvent) => {
502+
e.stopPropagation();
503+
setShowSizes(!showSizes);
504+
forceRender();
505+
}, [showSizes, forceRender]);
506+
468507
return (
469508
<Flex direction="column" onContextMenu={handler}
470509
style={{
@@ -518,38 +557,51 @@ export const Filters = React.memo(function Filters({ torrents, currentFilters, s
518557
>
519558
Recursive Directories
520559
</Menu.Item>
560+
<Menu.Item
561+
leftSection={showSizes ? <Icon.Check size="1rem" /> : <Box miw="1rem" />}
562+
onClick={onShowSizesClick}
563+
>
564+
Show sizes
565+
</Menu.Item>
521566
</MemoSectionsContextMenu>
522567
{sections[sectionsMap.Status].visible && <div style={{ order: sectionsMap.Status }}>
523568
<Divider mx="sm" label="Status" labelPosition="center" />
524569
{statusFilters.map((f) =>
525-
(f.required === true || statusFiltersVisibility[f.name]) && <FilterRow key={`status-${f.name}`}
570+
(f.required === true || statusFiltersVisibility[f.name])
571+
&& <FilterRow key={`status-${f.name}`}
526572
id={`status-${f.name}`} filter={f}
527-
count={torrents.filter(f.filter).length}
528-
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters} />)}
573+
stats={statCount(torrents.filter(f.filter))}
574+
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters}
575+
showSizes={showSizes} />)}
529576
</div>}
530577
{sections[sectionsMap.Directories].visible && <div style={{ order: sectionsMap.Directories }}>
531578
<Divider mx="sm" mt="md" label="Directories" labelPosition="center" />
532579
{dirs.map((d) =>
533580
<DirFilterRow key={`dir-${d.path}`} id={`dir-${d.path}`}
534-
dir={d} expandedReducer={expandedReducer} {...{ torrents, currentFilters, setCurrentFilters }} />)}
581+
dir={d} expandedReducer={expandedReducer}
582+
{...{ torrents, currentFilters, setCurrentFilters }}
583+
showSizes={showSizes} />)}
535584
</div>}
536585
{sections[sectionsMap.Labels].visible && <div style={{ order: sectionsMap.Labels }}>
537586
<Divider mx="sm" mt="md" label="Labels" labelPosition="center" />
538587
<FilterRow
539588
id="nolabels" filter={noLabelsFilter}
540-
count={torrents.filter(noLabelsFilter.filter).length}
541-
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters} />
589+
stats={statCount(torrents.filter(noLabelsFilter.filter))}
590+
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters}
591+
showSizes={showSizes} />
542592
{Object.keys(labels).sort().map((label) =>
543593
<LabelFilterRow key={`labels-${label}`} label={label}
544-
count={labels[label]}
545-
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters} />)}
594+
stats={labels[label]}
595+
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters}
596+
showSizes={showSizes} />)}
546597
</div>}
547598
{sections[sectionsMap.Trackers].visible && <div style={{ order: sectionsMap.Trackers }}>
548599
<Divider mx="sm" mt="md" label="Trackers" labelPosition="center" />
549600
{Object.keys(trackers).sort().map((tracker) =>
550-
<TrackerFilterRow key={`trackers-${tracker}`} tracker={tracker}
551-
count={trackers[tracker]}
552-
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters} />)}
601+
<TrackerFilterRow key={`trackers-${tracker}-${renderKey}`} tracker={tracker}
602+
stats={trackers[tracker]}
603+
currentFilters={currentFilters} setCurrentFilters={setCurrentFilters}
604+
showSizes={showSizes} />)}
553605
</div>}
554606
</Flex>
555607
);

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ interface Settings {
160160
statusFiltersVisibility: StatusFiltersVisibility,
161161
compactDirectories: boolean,
162162
recursiveDirectories: boolean,
163+
showSizesInFilters: boolean,
163164
statusBarSections: SectionsVisibility<StatusbarSectionName>,
164165
statusBarGlobalSpeeds: boolean,
165166
showFiltersPanel: boolean,
@@ -289,6 +290,7 @@ const DefaultSettings: Settings = {
289290
) as Record<StatusFilterName, boolean>,
290291
compactDirectories: false,
291292
recursiveDirectories: true,
293+
showSizesInFilters: true,
292294
statusBarSections: StatusbarSections.map((section) => ({
293295
section,
294296
visible: true,

src/trutil.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { formatDate } from "date-fns";
2525

2626
const SIUnits = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB"];
2727

28-
export function bytesToHumanReadableStr(value: number): string {
28+
export function bytesToHumanReadableStr(value: number, maxPrecision: number = 2): string {
2929
let unit = "";
3030
let divisor = 1.0;
3131

@@ -35,13 +35,14 @@ export function bytesToHumanReadableStr(value: number): string {
3535
}
3636

3737
const tmp = value / divisor;
38+
let precision = maxPrecision;
3839

39-
let fp = 2;
40-
if (tmp >= 100) fp = 1;
41-
if (tmp >= 1000) fp = 0;
42-
if (unit === "B") fp = 0;
40+
if (tmp >= 100) precision -= 1;
41+
if (tmp >= 1000) precision -= 0;
42+
if (unit === "B") precision = 0;
43+
if (precision < 0) precision = 0;
4344

44-
return `${tmp.toFixed(fp)} ${unit}`;
45+
return `${tmp.toFixed(precision)} ${unit}`;
4546
}
4647

4748
export function byteRateToHumanReadableStr(value: number): string {

0 commit comments

Comments
 (0)