FileTree can reveal a path
For a tree whose selection is driven from outside — a nav beside it, a URL — where a folder left shut around the open file hides the one thing being looked at. Every directory on the way opens, loading as it goes. It only ever opens. A node that closed itself when the selection moved elsewhere would collapse the tree under somebody as they clicked around it.
This commit is contained in:
@@ -21,6 +21,14 @@ type FileTreeProps = {
|
|||||||
* an agent had just written to showing the listing from before its turn.
|
* an agent had just written to showing the listing from before its turn.
|
||||||
*/
|
*/
|
||||||
refreshToken?: number;
|
refreshToken?: number;
|
||||||
|
/**
|
||||||
|
* A file to reveal: every directory on the way to it opens, loading as it goes.
|
||||||
|
*
|
||||||
|
* For a tree whose selection is driven from OUTSIDE — a nav beside it, a URL — where a folder left
|
||||||
|
* shut around the open file hides the one thing being looked at. Expanding is still disclosure and
|
||||||
|
* still remembered, so anything opened by hand stays open.
|
||||||
|
*/
|
||||||
|
revealPath?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TreeNodeProps = {
|
type TreeNodeProps = {
|
||||||
@@ -31,6 +39,7 @@ type TreeNodeProps = {
|
|||||||
searchForFile?: ((path: string) => string) | null;
|
searchForFile?: ((path: string) => string) | null;
|
||||||
depth: number;
|
depth: number;
|
||||||
refreshToken?: number;
|
refreshToken?: number;
|
||||||
|
revealPath?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileIcon = ({ name }: { name: string }) => {
|
const FileIcon = ({ name }: { name: string }) => {
|
||||||
@@ -44,7 +53,16 @@ const sortEntries = (entries: DirEntry[]) => {
|
|||||||
return [...dirs, ...files];
|
return [...dirs, ...files];
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeNode = ({ entry, parentPath, root, onOpenFile, searchForFile, depth, refreshToken }: TreeNodeProps) => {
|
const TreeNode = ({
|
||||||
|
entry,
|
||||||
|
parentPath,
|
||||||
|
root,
|
||||||
|
onOpenFile,
|
||||||
|
searchForFile,
|
||||||
|
depth,
|
||||||
|
refreshToken,
|
||||||
|
revealPath,
|
||||||
|
}: TreeNodeProps) => {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [children, setChildren] = useState<DirEntry[] | null>(null);
|
const [children, setChildren] = useState<DirEntry[] | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -73,6 +91,32 @@ const TreeNode = ({ entry, parentPath, root, onOpenFile, searchForFile, depth, r
|
|||||||
};
|
};
|
||||||
}, [refreshToken]);
|
}, [refreshToken]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
On the way to the revealed file: open, and load if this is the first time.
|
||||||
|
|
||||||
|
Only ever opens. A node that closes itself when the selection moves elsewhere would collapse the
|
||||||
|
tree under somebody as they clicked around it.
|
||||||
|
*/
|
||||||
|
const onTheWay = !!revealPath && isDir && revealPath.startsWith(`${fullPath}/`);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!onTheWay) return;
|
||||||
|
setExpanded(true);
|
||||||
|
if (children !== null) return;
|
||||||
|
|
||||||
|
let alive = true;
|
||||||
|
listDirRef
|
||||||
|
.current(fullPath)
|
||||||
|
.then((res) => alive && setChildren(sortEntries(res.entries)))
|
||||||
|
.catch(() => alive && setChildren([]));
|
||||||
|
return () => {
|
||||||
|
alive = false;
|
||||||
|
};
|
||||||
|
// `children` is deliberately absent: this must run when the PATH changes, not when the load it
|
||||||
|
// started completes.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [onTheWay, fullPath]);
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
if (!isDir) {
|
if (!isDir) {
|
||||||
onOpenFile(fullPath, entry.name);
|
onOpenFile(fullPath, entry.name);
|
||||||
@@ -140,6 +184,7 @@ const TreeNode = ({ entry, parentPath, root, onOpenFile, searchForFile, depth, r
|
|||||||
searchForFile={searchForFile}
|
searchForFile={searchForFile}
|
||||||
depth={depth + 1}
|
depth={depth + 1}
|
||||||
refreshToken={refreshToken}
|
refreshToken={refreshToken}
|
||||||
|
revealPath={revealPath}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -148,7 +193,7 @@ const TreeNode = ({ entry, parentPath, root, onOpenFile, searchForFile, depth, r
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshToken }: FileTreeProps) => {
|
export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshToken, revealPath }: FileTreeProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { listDir } = useFilesAPI(root);
|
const { listDir } = useFilesAPI(root);
|
||||||
const [entries, setEntries] = useState<DirEntry[] | null>(null);
|
const [entries, setEntries] = useState<DirEntry[] | null>(null);
|
||||||
@@ -208,6 +253,7 @@ export const FileTree = ({ root, basePath, onOpenFile, searchForFile, refreshTok
|
|||||||
searchForFile={searchForFile}
|
searchForFile={searchForFile}
|
||||||
depth={0}
|
depth={0}
|
||||||
refreshToken={refreshToken}
|
refreshToken={refreshToken}
|
||||||
|
revealPath={revealPath}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user