diff --git a/src/App.tsx b/src/App.tsx index 51ad94b..8594ae7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,6 +30,7 @@ import { Play, Plug, PlusSquare, + RefreshCw, Search, Settings, Sidebar, @@ -2256,6 +2257,28 @@ const ProjectWorkspace = forwardRef< [loadDirectory, refreshGitStatuses], ); + const refreshFileExplorerTree = useCallback(() => { + if (!project.rootFolder) { + return; + } + + for (const timerId of fileExplorerRefreshTimersRef.current.values()) { + window.clearTimeout(timerId); + } + fileExplorerRefreshTimersRef.current.clear(); + + const directories = new Set([ + project.rootFolder, + ...Object.keys(directoryChildren), + ]); + + void Promise.all( + Array.from(directories, (dirPath) => loadDirectory(dirPath)), + ).then(() => { + void refreshGitStatuses(); + }); + }, [directoryChildren, loadDirectory, project.rootFolder, refreshGitStatuses]); + const toggleDirectory = useCallback( (dirPath: string) => { const shouldExpand = !expandedPaths[dirPath]; @@ -6171,6 +6194,17 @@ const ProjectWorkspace = forwardRef< : 'expanded', }); }} + actions={ + + } > ( (total, document) => ({ @@ -60,6 +67,42 @@ function combineStats(documents: FolderTaskDocument[]): TaskStats { ); } +function compareDocumentsByName( + a: FolderTaskDocument, + b: FolderTaskDocument, +): number { + return a.relativePath.localeCompare(b.relativePath, undefined, { + numeric: true, + sensitivity: 'base', + }); +} + +function compareDocumentsByProgress( + a: FolderTaskDocument, + b: FolderTaskDocument, +): number { + const progressDelta = percent(b.tree.stats) - percent(a.tree.stats); + if (progressDelta !== 0) { + return progressDelta; + } + + const remainingDelta = a.tree.stats.remaining - b.tree.stats.remaining; + if (remainingDelta !== 0) { + return remainingDelta; + } + + return compareDocumentsByName(a, b); +} + +function sortDocumentsByMode( + documents: FolderTaskDocument[], + sort: FolderTaskSort, +): FolderTaskDocument[] { + return [...documents].sort( + sort === 'progress' ? compareDocumentsByProgress : compareDocumentsByName, + ); +} + function FileCheckIcon() { return ( 0 && document.relativeDirectory !== '.'; return (
{document.name} - - {document.relativeDirectory} - + {showRelativeDirectory ? ( + + {document.relativeDirectory} + + ) : null} {complete ? ( Done @@ -237,7 +284,12 @@ export function FolderTasksViewer({ const [filter, setFilter] = useState('all'); const [query, setQuery] = useState(''); const [view, setView] = useState('list'); + const [sort, setSort] = useState('progress'); const [groupByFile, setGroupByFile] = useState(false); + const sortedDocuments = useMemo( + () => sortDocumentsByMode(documents, sort), + [documents, sort], + ); const toggle = (id: string) => { setCollapsed((previous) => { @@ -258,12 +310,22 @@ export function FolderTasksViewer({ const activeCollapsed = isSearching ? NO_COLLAPSE : collapsed; const predicate = buildPredicate(filter, query); const visibleCards = allCards.filter((card) => cardMatchesQuery(card, query)); - const visibleLanes = cardsByDocument - .map((entry) => ({ - document: entry.document, - cards: entry.cards.filter((card) => cardMatchesQuery(card, query)), - })) - .filter((entry) => entry.cards.length > 0); + const visibleLanes = sortedDocuments + .map((document) => { + const entry = cardsByDocument.find( + (candidate) => candidate.document.path === document.path, + ); + return entry + ? { + document, + cards: entry.cards.filter((card) => cardMatchesQuery(card, query)), + } + : null; + }) + .filter( + (entry): entry is { document: FolderTaskDocument; cards: TaskCard[] } => + entry !== null && entry.cards.length > 0, + ); const hasVisibleDocuments = documents.some((document) => { const root = document.tree.root; return ( @@ -331,8 +393,8 @@ export function FolderTasksViewer({ @@ -341,11 +403,6 @@ export function FolderTasksViewer({ All {stats.total} tasks across {documents.length}{' '} {documents.length === 1 ? 'file' : 'files'} complete. - ) : filesComplete > 0 ? ( - - {filesComplete} of {documents.length} files fully complete · {stats.remaining}{' '} - tasks remaining. - ) : null} + {isKanban ? (
@@ -431,7 +497,7 @@ export function FolderTasksViewer({ : 'No completed tasks yet.'}
) : null} - {documents.map((document) => ( + {sortedDocuments.map((document) => (