Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
Play,
Plug,
PlusSquare,
RefreshCw,
Search,
Settings,
Sidebar,
Expand Down Expand Up @@ -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<string>([
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];
Expand Down Expand Up @@ -6171,6 +6194,17 @@ const ProjectWorkspace = forwardRef<
: 'expanded',
});
}}
actions={
<button
type="button"
className="sidebar-pane__action-button"
onClick={refreshFileExplorerTree}
aria-label="Reload explorer"
title="Reload explorer"
>
<RefreshCw size={14} aria-hidden="true" />
</button>
}
>
<FileExplorerTree
directoryChildren={directoryChildren}
Expand Down
9 changes: 7 additions & 2 deletions src/components/file-viewer/fileViewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -1058,8 +1058,13 @@

.file-tasks__chevron {
flex: none;
width: 12px;
font-size: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
font-size: 11px;
line-height: 1;
color: rgba(196, 206, 224, 0.5);
transition: transform 0.15s ease;
}
Expand Down
106 changes: 86 additions & 20 deletions src/components/folder-viewer/FolderTasksViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ type FolderTasksViewerProps = {
scannedMarkdownCount: number;
};

type FolderTaskSort = 'progress' | 'name';

const FOLDER_TASK_SORT_OPTIONS: { id: FolderTaskSort; label: string }[] = [
{ id: 'progress', label: 'Progress' },
{ id: 'name', label: 'Name' },
];

function combineStats(documents: FolderTaskDocument[]): TaskStats {
return documents.reduce<TaskStats>(
(total, document) => ({
Expand All @@ -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 (
<svg
Expand Down Expand Up @@ -125,6 +168,8 @@ function FileTaskGroup({

const stats = document.tree.stats;
const complete = isComplete(stats);
const showRelativeDirectory =
document.relativeDirectory.length > 0 && document.relativeDirectory !== '.';

return (
<section
Expand All @@ -151,9 +196,11 @@ function FileTaskGroup({
</span>
<span className="folder-tasks__file-main">
<span className="folder-tasks__file-name">{document.name}</span>
<span className="folder-tasks__file-path">
{document.relativeDirectory}
</span>
{showRelativeDirectory ? (
<span className="folder-tasks__file-path">
{document.relativeDirectory}
</span>
) : null}
</span>
{complete ? (
<span className="folder-tasks__file-done">Done</span>
Expand Down Expand Up @@ -237,7 +284,12 @@ export function FolderTasksViewer({
const [filter, setFilter] = useState<TaskFilter>('all');
const [query, setQuery] = useState('');
const [view, setView] = useState<TaskView>('list');
const [sort, setSort] = useState<FolderTaskSort>('progress');
const [groupByFile, setGroupByFile] = useState(false);
const sortedDocuments = useMemo(
() => sortDocumentsByMode(documents, sort),
[documents, sort],
);

const toggle = (id: string) => {
setCollapsed((previous) => {
Expand All @@ -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 (
Expand Down Expand Up @@ -331,8 +393,8 @@ export function FolderTasksViewer({
<StatTile tone="remaining" value={stats.remaining} label="remaining" />
<StatTile
tone="files"
value={documents.length}
label={documents.length === 1 ? 'file' : 'files'}
value={`${filesComplete}/${documents.length}`}
label="files complete"
/>
</TaskHero>

Expand All @@ -341,11 +403,6 @@ export function FolderTasksViewer({
All {stats.total} tasks across {documents.length}{' '}
{documents.length === 1 ? 'file' : 'files'} complete.
</TaskCallout>
) : filesComplete > 0 ? (
<TaskCallout tone="info" icon="✓">
{filesComplete} of {documents.length} files fully complete · {stats.remaining}{' '}
tasks remaining.
</TaskCallout>
) : null}

<TaskToolbar
Expand All @@ -357,6 +414,18 @@ export function FolderTasksViewer({
onQueryChange={setQuery}
showFilter={!isKanban}
>
<select
className="folder-tasks__sort"
value={sort}
onChange={(event) => setSort(event.target.value as FolderTaskSort)}
aria-label="Sort files"
>
{FOLDER_TASK_SORT_OPTIONS.map((option) => (
<option key={option.id} value={option.id}>
{option.label}
</option>
))}
</select>
{isKanban ? (
<button
type="button"
Expand All @@ -377,9 +446,6 @@ export function FolderTasksViewer({
{allCollapsed ? 'Expand all' : 'Collapse all'}
</button>
) : null}
<button type="button" className="file-tasks__action" onClick={onRefresh}>
Refresh
</button>
</TaskToolbar>

<div className="file-tasks__scroll">
Expand Down Expand Up @@ -431,7 +497,7 @@ export function FolderTasksViewer({
: 'No completed tasks yet.'}
</div>
) : null}
{documents.map((document) => (
{sortedDocuments.map((document) => (
<FileTaskGroup
key={document.path}
collapsed={activeCollapsed}
Expand Down
31 changes: 31 additions & 0 deletions src/components/folder-viewer/folderViewer.css
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,31 @@
gap: 8px;
}

.folder-tasks__sort {
flex: none;
min-width: 112px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
background: rgba(255, 255, 255, 0.04);
color: rgba(238, 244, 255, 0.9);
padding: 5px 28px 5px 10px;
font-size: 12.5px;
font-weight: 700;
cursor: pointer;
outline: none;
}

.folder-tasks__sort:hover,
.folder-tasks__sort:focus {
border-color: rgba(87, 183, 255, 0.35);
background: rgba(87, 183, 255, 0.08);
}

.folder-tasks__sort option {
background: #101820;
color: #eef4ff;
}

.folder-tasks__file {
margin-bottom: 10px;
border: 1px solid rgba(255, 255, 255, 0.07);
Expand Down Expand Up @@ -419,6 +444,12 @@
margin-left: 0;
}

.folder-tasks__file-header .file-tasks__chevron {
width: 18px;
height: 18px;
font-size: 13px;
}

.folder-tasks__file-icon {
flex: none;
display: flex;
Expand Down
Loading