-
Notifications
You must be signed in to change notification settings - Fork 88
feat(webui): Use DirectoryTree for file path selection (closes #1779). #1787
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
72440cc
0191e35
70b4c68
bbc713b
0bac6fa
35f6ca1
75dbe7f
2eb655c
c90c142
6595015
490a748
c358b32
11b44e4
d434990
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import React, { | ||
| useCallback, | ||
| useEffect, | ||
| useState, | ||
| } from "react"; | ||
|
|
||
| import {Tree} from "antd"; | ||
| import type {TreeDataNode} from "antd/es"; | ||
| import type {TreeProps} from "antd/es/tree"; | ||
|
|
||
| import styles from "./index.module.css"; | ||
| import {getListHeight} from "./utils"; | ||
|
|
||
|
|
||
| const {DirectoryTree} = Tree; | ||
|
|
||
|
|
||
| interface DirectoryTreePopupProps { | ||
| checkedKeys: string[]; | ||
| expandedKeys: string[]; | ||
| treeData: TreeDataNode[]; | ||
| onCheck: (keys: string[]) => void; | ||
| onExpand: (keys: string[]) => void; | ||
| onLoadData: (path: string) => Promise<void>; | ||
| } | ||
|
|
||
| /** | ||
| * Renders a popup component containing a DirectoryTree for path selection. | ||
| * | ||
| * @param props | ||
| * @param props.checkedKeys | ||
| * @param props.expandedKeys | ||
| * @param props.treeData | ||
| * @param props.onCheck | ||
| * @param props.onExpand | ||
| * @param props.onLoadData | ||
| * @return | ||
| */ | ||
| const DirectoryTreePopup = ({ | ||
| checkedKeys, | ||
| expandedKeys, | ||
| treeData, | ||
| onCheck, | ||
| onExpand, | ||
| onLoadData, | ||
| }: DirectoryTreePopupProps) => { | ||
| const [height, setHeight] = useState<number>(getListHeight); | ||
|
|
||
| const handleCheck: TreeProps["onCheck"] = useCallback(( | ||
| checked: React.Key[] | {checked: React.Key[]; halfChecked: React.Key[]} | ||
| ) => { | ||
| const keys = Array.isArray(checked) ? | ||
| checked : | ||
| checked.checked; | ||
|
|
||
| onCheck(keys as string[]); | ||
| }, [onCheck]); | ||
|
|
||
| const handleExpand = useCallback((keys: React.Key[]) => { | ||
| onExpand(keys as string[]); | ||
| }, [onExpand]); | ||
|
|
||
| const handleLoadData = useCallback(async (node: TreeDataNode) => { | ||
| await onLoadData(node.key as string); | ||
| }, [onLoadData]); | ||
|
|
||
| useEffect(() => { | ||
| const handleResize = () => { | ||
| setHeight(getListHeight()); | ||
| }; | ||
|
|
||
| window.addEventListener("resize", handleResize); | ||
|
|
||
| return () => { | ||
| window.removeEventListener("resize", handleResize); | ||
| }; | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div | ||
| className={styles["directoryTreePopup"]} | ||
| style={{height: height, overflow: "auto"}} | ||
| > | ||
| <DirectoryTree | ||
| checkable={true} | ||
| checkedKeys={checkedKeys} | ||
| expandedKeys={expandedKeys} | ||
| loadData={handleLoadData} | ||
| treeData={treeData} | ||
| onCheck={handleCheck} | ||
| onExpand={handleExpand}/> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
|
|
||
| export default DirectoryTreePopup; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,107 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| useCallback, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| useMemo, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {Select} from "antd"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type {TreeDataNode} from "antd/es"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| import DirectoryTreePopup from "./DirectoryTreePopup"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {TreeNode} from "./typings"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| filterToParents, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| flatToHierarchy, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| removeWithDescendants, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "./utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface DirectoryTreeSelectProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkedKeys: string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| expandedKeys: string[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| treeData: TreeNode[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck: (keys: string[]) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onExpand: (keys: string[]) => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLoadData: (path: string) => Promise<void>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Renders a select component that uses DirectoryTree for the dropdown. Provides folder/file | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * icons and intuitive directory navigation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props.checkedKeys | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props.expandedKeys | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props.treeData | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props.onCheck | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props.onExpand | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param props.onLoadData | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Remove the empty The JSDoc includes an 📝 Proposed fix📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const DirectoryTreeSelect = ({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkedKeys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| expandedKeys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| treeData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onExpand, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLoadData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: DirectoryTreeSelectProps) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hierarchicalTreeData: TreeDataNode[] = useMemo( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => flatToHierarchy(treeData), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| [treeData] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const selectOptions = useMemo( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => treeData.map((node) => ({label: node.value, value: node.value})), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| [treeData] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const displayValue = useMemo( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => filterToParents(treeData, checkedKeys), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkedKeys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| treeData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleClear = useCallback(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [onCheck]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleDeselect = useCallback((value: string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck(removeWithDescendants(treeData, checkedKeys, value)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkedKeys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| treeData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const renderDropdown = useCallback(() => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DirectoryTreePopup | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkedKeys={checkedKeys} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| expandedKeys={expandedKeys} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| treeData={hierarchicalTreeData} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck={onCheck} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onExpand={onExpand} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLoadData={onLoadData}/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ), [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkedKeys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| expandedKeys, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| hierarchicalTreeData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCheck, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onExpand, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onLoadData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Select | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| allowClear={true} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| mode={"multiple"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| options={selectOptions} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder={"Select paths to compress"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| popupRender={renderDropdown} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={displayValue} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClear={handleClear} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| onDeselect={handleDeselect}/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default DirectoryTreeSelect; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| .directoryTreePopup { | ||
| padding: 4px; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider memoizing the inline style object.
The component is well-structured with the DirectoryTree properly configured. However, the inline style object is recreated on every render.
♻️ Optional optimization to memoize the style object
🤖 Prompt for AI Agents