From b09f6e9f2207da05632af07347376fca478b6310 Mon Sep 17 00:00:00 2001 From: qu1ck Date: Tue, 19 Mar 2024 01:55:24 -0700 Subject: [PATCH] Add option to show file list instead of tree in details Issue #176 --- src/cachedfiletree.ts | 61 ++++++++++++++++--------- src/components/tables/common.tsx | 2 +- src/components/tables/filetreetable.tsx | 14 ++++++ src/config.ts | 2 + src/queries.ts | 11 +++-- 5 files changed, 65 insertions(+), 25 deletions(-) diff --git a/src/cachedfiletree.ts b/src/cachedfiletree.ts index c76460e..e934168 100644 --- a/src/cachedfiletree.ts +++ b/src/cachedfiletree.ts @@ -395,29 +395,48 @@ export class CachedFileTree { return result; } - getView(): FileDirEntryView[] { - const toView: (e: FileDirEntry) => FileDirEntryView = (e) => { - const subrows: FileDirEntryView[] = []; - if (isDirEntry(e)) { - subrows.push(...Array.from(e.subdirs.values()).map(toView)); - subrows.push(...Array.from(e.files.values()).map(toView)); - } - return { - name: e.name, - level: e.level, - fullpath: e.fullpath, - size: e.size, - done: e.done, - percent: e.percent, - wantedUpdating: e.wantedUpdating, - want: e.want, - priority: e.priority, - subrows, - }; + getView(flat: boolean): FileDirEntryView[] { + const result: FileDirEntryView[] = []; + const getName = (e: FileDirEntry) => { + if (!flat || e.fullpath === e.name) return e.name; + return e.fullpath.split("/").slice(1).join("/"); }; + const copyEntry: (e: FileDirEntry, subrows: FileDirEntryView[]) => FileDirEntryView = (e, subrows) => ({ + name: getName(e), + level: flat ? 0 : e.level, + fullpath: e.fullpath, + size: e.size, + done: e.done, + percent: e.percent, + wantedUpdating: e.wantedUpdating, + want: e.want, + priority: e.priority, + subrows, + }); + + if (flat) { + const flatten = (e: FileDirEntry) => { + if (isDirEntry(e)) { + for (const subdir of e.subdirs.values()) flatten(subdir); + result.push(...Array.from(e.files.values()).map((e) => copyEntry(e, []))); + } else { + result.push(copyEntry(e, [])); + } + }; + flatten(this.tree); + } else { + const toView: (e: FileDirEntry) => FileDirEntryView = (e) => { + const subrows: FileDirEntryView[] = []; + if (isDirEntry(e)) { + subrows.push(...Array.from(e.subdirs.values()).map(toView)); + subrows.push(...Array.from(e.files.values()).map(toView)); + } + return copyEntry(e, subrows); + }; - const result = Array.from(this.tree.subdirs.values()).map(toView); - result.push(...Array.from(this.tree.files.values()).map(toView)); + result.push(...Array.from(this.tree.subdirs.values()).map(toView)); + result.push(...Array.from(this.tree.files.values()).map(toView)); + } return result; } diff --git a/src/components/tables/common.tsx b/src/components/tables/common.tsx index e46f744..1838926 100644 --- a/src/components/tables/common.tsx +++ b/src/components/tables/common.tsx @@ -628,7 +628,7 @@ export function EditableNameField(props: EditableNameFieldProps) { const renameHandler = useCallback((e?: React.MouseEvent) => { e?.stopPropagation(); setRenaming(true); - setNewName(props.currentName); + setNewName(props.currentName.split("/").pop() ?? ""); }, [props.currentName]); const ref = useRef(null); diff --git a/src/components/tables/filetreetable.tsx b/src/components/tables/filetreetable.tsx index 8448540..d76dd74 100644 --- a/src/components/tables/filetreetable.tsx +++ b/src/components/tables/filetreetable.tsx @@ -432,6 +432,8 @@ function FiletreeContextMenu(props: { setExpanded?: (state: boolean) => void, toggleFileSearchBox: () => void, }) { + const config = useContext(ConfigContext); + const { onEntryOpen } = props; const onOpen = useCallback((reveal: boolean) => { const entry = props.fileTree.findEntry(props.currentRow); @@ -494,6 +496,13 @@ function FiletreeContextMenu(props: { ); }, [mutate, props.fileTree, props.selected]); + const [flatFileTree, toggleFlatFileTree] = useReducer((value: boolean) => { + value = !value; + refreshFileTree("filetree"); + config.values.interface.flatFileTree = value; + return value; + }, config.values.interface.flatFileTree); + return ( {TAURI && <> @@ -559,6 +568,11 @@ function FiletreeContextMenu(props: { icon={}> Toggle search + }> + Show as tree + ); } diff --git a/src/config.ts b/src/config.ts index 2a1de0c..bd24978 100644 --- a/src/config.ts +++ b/src/config.ts @@ -149,6 +149,7 @@ interface Settings { showDetailsPanel: boolean, detailsTabs: SectionsVisibility, showFilesSearchBox: boolean, + flatFileTree: boolean, mainSplit: SplitType, skipAddDialog: boolean, deleteTorrentData: DeleteTorrentDataOption, @@ -262,6 +263,7 @@ const DefaultSettings: Settings = { visible: true, })), showFilesSearchBox: false, + flatFileTree: false, mainSplit: "vertical", skipAddDialog: false, deleteTorrentData: "default off", diff --git a/src/queries.ts b/src/queries.ts index c555194..c049e59 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -18,7 +18,7 @@ import { QueryClient, useMutation, useQuery } from "@tanstack/react-query"; import type { CachedFileTree } from "cachedfiletree"; -import { ServerConfigContext } from "config"; +import { ConfigContext, ServerConfigContext } from "config"; import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import type { SessionInfo, TorrentActionMethodsType, TorrentAddParams } from "rpc/client"; import { useTransmissionClient } from "rpc/client"; @@ -346,13 +346,18 @@ export function useBandwidthGroups(enabled: boolean) { } export function useFileTree(name: string, fileTree: CachedFileTree) { - const initialData = useMemo(() => fileTree.getView(), [fileTree]); + const config = useContext(ConfigContext); + + const initialData = useMemo( + () => fileTree.getView(config.values.interface.flatFileTree), + [fileTree, config]); + return useQuery({ queryKey: [name], initialData, staleTime: Infinity, refetchOnWindowFocus: false, - queryFn: () => fileTree.getView(), + queryFn: () => fileTree.getView(config.values.interface.flatFileTree), }); }