Skip to content

Commit c554589

Browse files
committed
Add npm run dev alias
1 parent 1d2440f commit c554589

3 files changed

Lines changed: 394 additions & 3 deletions

File tree

App.tsx

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import InfoView from './components/InfoView';
1818
import UpdateNotification from './components/UpdateNotification';
1919
import CreateFromTemplateModal from './components/CreateFromTemplateModal';
2020
import DocumentHistoryView from './components/PromptHistoryView';
21+
import FolderOverview, { FolderOverviewMetrics } from './components/FolderOverview';
2122
import { PlusIcon, FolderPlusIcon, TrashIcon, GearIcon, InfoIcon, TerminalIcon, DocumentDuplicateIcon, PencilIcon, CopyIcon, CommandIcon, CodeIcon, FolderDownIcon, FormatIcon, SparklesIcon } from './components/Icons';
2223
import AboutModal from './components/AboutModal';
2324
import Header from './components/Header';
@@ -295,6 +296,151 @@ const MainApp: React.FC = () => {
295296
return { documentTree: finalTree, navigableItems: flatList };
296297
}, [itemsWithSearchMetadata, templates, searchTerm, expandedFolderIds]);
297298

299+
const activeFolderMetrics = useMemo<FolderOverviewMetrics | null>(() => {
300+
if (!activeNode || activeNode.type !== 'folder') {
301+
return null;
302+
}
303+
304+
const parseDate = (value?: string | null): Date | null => {
305+
if (!value) return null;
306+
const date = new Date(value);
307+
return Number.isNaN(date.getTime()) ? null : date;
308+
};
309+
310+
const computeFromTree = (folderNode: DocumentNode): FolderOverviewMetrics => {
311+
const recordLatest = (() => {
312+
let latest: Date | null = null;
313+
return {
314+
update(value?: string | null) {
315+
const parsed = parseDate(value);
316+
if (parsed && (!latest || parsed > latest)) {
317+
latest = parsed;
318+
}
319+
},
320+
getValue() {
321+
return latest;
322+
},
323+
};
324+
})();
325+
326+
recordLatest.update(folderNode.updatedAt);
327+
328+
const directDocumentCount = folderNode.children.filter(child => child.type === 'document').length;
329+
const directFolderCount = folderNode.children.filter(child => child.type === 'folder').length;
330+
331+
let totalDocumentCount = 0;
332+
let totalFolderCount = 0;
333+
const stack = [...folderNode.children];
334+
335+
while (stack.length > 0) {
336+
const current = stack.pop()!;
337+
recordLatest.update(current.updatedAt);
338+
if (current.type === 'document') {
339+
totalDocumentCount += 1;
340+
} else if (current.type === 'folder') {
341+
totalFolderCount += 1;
342+
stack.push(...current.children);
343+
}
344+
}
345+
346+
const latestDate = recordLatest.getValue();
347+
348+
return {
349+
directDocumentCount,
350+
directFolderCount,
351+
totalDocumentCount,
352+
totalFolderCount,
353+
totalItemCount: totalDocumentCount + totalFolderCount,
354+
lastUpdated: latestDate ? latestDate.toISOString() : null,
355+
};
356+
};
357+
358+
const buildChildMap = () => {
359+
const map = new Map<string | null, DocumentOrFolder[]>();
360+
for (const item of items) {
361+
const key = item.parentId;
362+
if (!map.has(key)) {
363+
map.set(key, []);
364+
}
365+
map.get(key)!.push(item);
366+
}
367+
return map;
368+
};
369+
370+
const computeFromList = (): FolderOverviewMetrics => {
371+
const childMap = buildChildMap();
372+
const directChildren = childMap.get(activeNode.id) ?? [];
373+
374+
const recordLatest = (() => {
375+
let latest: Date | null = null;
376+
return {
377+
update(value?: string | null) {
378+
const parsed = parseDate(value);
379+
if (parsed && (!latest || parsed > latest)) {
380+
latest = parsed;
381+
}
382+
},
383+
getValue() {
384+
return latest;
385+
},
386+
};
387+
})();
388+
389+
recordLatest.update(activeNode.updatedAt);
390+
391+
const directDocumentCount = directChildren.filter(child => child.type === 'document').length;
392+
const directFolderCount = directChildren.filter(child => child.type === 'folder').length;
393+
394+
let totalDocumentCount = 0;
395+
let totalFolderCount = 0;
396+
const stack = [...directChildren];
397+
398+
while (stack.length > 0) {
399+
const current = stack.pop()!;
400+
recordLatest.update(current.updatedAt);
401+
if (current.type === 'document') {
402+
totalDocumentCount += 1;
403+
} else {
404+
totalFolderCount += 1;
405+
const childItems = childMap.get(current.id) ?? [];
406+
stack.push(...childItems);
407+
}
408+
}
409+
410+
const latestDate = recordLatest.getValue();
411+
412+
return {
413+
directDocumentCount,
414+
directFolderCount,
415+
totalDocumentCount,
416+
totalFolderCount,
417+
totalItemCount: totalDocumentCount + totalFolderCount,
418+
lastUpdated: latestDate ? latestDate.toISOString() : null,
419+
};
420+
};
421+
422+
const findNodeInTree = (nodes: DocumentNode[]): DocumentNode | null => {
423+
for (const node of nodes) {
424+
if (node.id === activeNode.id) {
425+
return node;
426+
}
427+
if (node.type === 'folder') {
428+
const match = findNodeInTree(node.children);
429+
if (match) {
430+
return match;
431+
}
432+
}
433+
}
434+
return null;
435+
};
436+
437+
const folderNode = findNodeInTree(documentTree);
438+
if (folderNode) {
439+
return computeFromTree(folderNode);
440+
}
441+
return computeFromList();
442+
}, [activeNode, documentTree, items]);
443+
298444
useEffect(() => {
299445
if (window.electronAPI?.getAppVersion) {
300446
window.electronAPI.getAppVersion().then(setAppVersion);
@@ -390,6 +536,18 @@ const MainApp: React.FC = () => {
390536
}
391537
}, [addDocumentsFromFiles, setActiveNodeId, setSelectedIds, setLastClickedId, setActiveTemplateId, setDocumentView, setView]);
392538

539+
const handleImportFilesIntoFolder = useCallback((files: FileList, parentId: string) => {
540+
if (!files || files.length === 0) {
541+
return;
542+
}
543+
544+
const targetFolder = items.find(item => item.id === parentId && item.type === 'folder');
545+
const folderTitle = targetFolder?.title?.trim() || 'Untitled Folder';
546+
547+
addLog('INFO', `User action: Import ${files.length} file(s) into folder "${folderTitle}".`);
548+
void handleDropFiles(files, parentId);
549+
}, [items, addLog, handleDropFiles]);
550+
393551
useEffect(() => {
394552
const handleDragEnter = (e: DragEvent) => {
395553
if (e.dataTransfer?.types.includes('Files')) {
@@ -686,6 +844,20 @@ const MainApp: React.FC = () => {
686844
updateItem(id, { title });
687845
};
688846

847+
const handleStartRenamingNode = useCallback((id: string) => {
848+
const target = items.find(item => item.id === id) ?? null;
849+
if (target) {
850+
const trimmedTitle = target.title?.trim();
851+
const fallbackTitle = target.type === 'folder' ? 'Untitled Folder' : 'Untitled Document';
852+
const displayTitle = trimmedTitle && trimmedTitle.length > 0 ? trimmedTitle : fallbackTitle;
853+
addLog('INFO', `User action: Rename ${target.type} "${displayTitle}".`);
854+
ensureNodeVisible({ id: target.id, type: target.type, parentId: target.parentId ?? null });
855+
} else {
856+
addLog('INFO', 'User action: Rename item.');
857+
}
858+
setRenamingNodeId(id);
859+
}, [items, addLog, ensureNodeVisible]);
860+
689861
const handleRenameTemplate = (id: string, title: string) => {
690862
updateTemplate(id, { title });
691863
};
@@ -971,7 +1143,7 @@ const MainApp: React.FC = () => {
9711143
{ label: 'New from Template...', icon: DocumentDuplicateIcon, action: newFromTemplateAction, shortcut: getCommand('new-from-template')?.shortcutString },
9721144
{ type: 'separator' },
9731145
{ label: 'Format', icon: FormatIcon, action: handleFormatDocument, disabled: !isFormattable || currentSelection.size !== 1, shortcut: getCommand('format-document')?.shortcutString },
974-
{ label: 'Rename', icon: PencilIcon, action: () => setRenamingNodeId(nodeId), disabled: currentSelection.size !== 1 },
1146+
{ label: 'Rename', icon: PencilIcon, action: () => handleStartRenamingNode(nodeId), disabled: currentSelection.size !== 1 },
9751147
{ label: 'Duplicate', icon: DocumentDuplicateIcon, action: handleDuplicateSelection, disabled: currentSelection.size === 0, shortcut: getCommand('duplicate-item')?.shortcutString },
9761148
{ type: 'separator' },
9771149
{ label: 'Copy Content', icon: CopyIcon, action: () => hasDocuments && handleCopyNodeContent(selectedNodes.find(n => n.type === 'document')!.id), disabled: !hasDocuments},
@@ -992,7 +1164,7 @@ const MainApp: React.FC = () => {
9921164
position: { x: e.clientX, y: e.clientY },
9931165
items: menuItems
9941166
});
995-
}, [selectedIds, items, handleNewDocument, handleNewFolder, handleDuplicateSelection, handleDeleteSelection, handleCopyNodeContent, addLog, enrichedCommands, handleOpenNewCodeFileModal, handleFormatDocument]);
1167+
}, [selectedIds, items, handleNewDocument, handleNewFolder, handleDuplicateSelection, handleDeleteSelection, handleCopyNodeContent, addLog, enrichedCommands, handleOpenNewCodeFileModal, handleFormatDocument, handleStartRenamingNode]);
9961168

9971169

9981170
const handleSidebarMouseDown = useCallback((e: React.MouseEvent) => {
@@ -1128,7 +1300,27 @@ const MainApp: React.FC = () => {
11281300
/>
11291301
);
11301302
}
1131-
return <WelcomeScreen onNewDocument={() => handleNewDocument()} />;
1303+
if (activeNode.type === 'folder') {
1304+
const fallbackMetrics: FolderOverviewMetrics = {
1305+
directDocumentCount: 0,
1306+
directFolderCount: 0,
1307+
totalDocumentCount: 0,
1308+
totalFolderCount: 0,
1309+
totalItemCount: 0,
1310+
lastUpdated: activeNode.updatedAt,
1311+
};
1312+
return (
1313+
<FolderOverview
1314+
key={activeNode.id}
1315+
folder={activeNode}
1316+
metrics={activeFolderMetrics ?? fallbackMetrics}
1317+
onNewDocument={(parentId) => handleNewDocument(parentId)}
1318+
onNewSubfolder={(parentId) => handleNewFolder(parentId)}
1319+
onImportFiles={handleImportFilesIntoFolder}
1320+
onRenameFolder={handleStartRenamingNode}
1321+
/>
1322+
);
1323+
}
11321324
}
11331325
return <WelcomeScreen onNewDocument={() => handleNewDocument()} />;
11341326
};

0 commit comments

Comments
 (0)