move folder creation to sidebar

This commit is contained in:
2025-10-30 02:18:33 +01:00
parent e5d44c13b4
commit 9fe97f2b8a
3 changed files with 51 additions and 156 deletions
-26
View File
@@ -10,7 +10,6 @@ import {
FolderIcon, FolderIcon,
TrashIcon, TrashIcon,
RefreshIcon, RefreshIcon,
FolderPlusIcon,
MinusVerticalIcon, MinusVerticalIcon,
ArrowUpIcon, ArrowUpIcon,
} from '../ui/icons'; } from '../ui/icons';
@@ -199,8 +198,6 @@ const DocumentsTable = ({
currentFolderName, currentFolderName,
onRefresh, onRefresh,
onShowSkeuoWorkspace = () => {}, onShowSkeuoWorkspace = () => {},
onRequestCreateFolder,
creatingFolder = false,
subfolders, subfolders,
documents, documents,
searchResults, searchResults,
@@ -512,13 +509,6 @@ const DocumentsTable = ({
<ViewGridIcon className="view-toggle__icon" size={18} /> <ViewGridIcon className="view-toggle__icon" size={18} />
</button> </button>
</div> </div>
<button
type="button"
onClick={onRequestCreateFolder}
disabled={creatingFolder}
>
{creatingFolder ? 'Creating…' : 'New folder'}
</button>
<button className="secondary" onClick={onRefresh}> <button className="secondary" onClick={onRefresh}>
Refresh Refresh
</button> </button>
@@ -1056,8 +1046,6 @@ export { DocumentThumbnailImage };
export const createDocumentsTableHeaderActions = ({ export const createDocumentsTableHeaderActions = ({
viewMode, viewMode,
onViewModeChange, onViewModeChange,
onRequestCreateFolder,
creatingFolder,
onRefresh, onRefresh,
onShowSkeuoWorkspace, onShowSkeuoWorkspace,
}) => { }) => {
@@ -1088,16 +1076,6 @@ export const createDocumentsTableHeaderActions = ({
<span className="main-content__actions-divider" aria-hidden="true"> <span className="main-content__actions-divider" aria-hidden="true">
<MinusVerticalIcon /> <MinusVerticalIcon />
</span> </span>
<button
type="button"
className="icon-button"
onClick={onRequestCreateFolder}
disabled={creatingFolder}
aria-label={creatingFolder ? 'Creating folder…' : 'Create folder'}
title={creatingFolder ? 'Creating folder…' : 'Create folder'}
>
<FolderPlusIcon />
</button>
<button <button
type="button" type="button"
className="icon-button" className="icon-button"
@@ -1127,8 +1105,6 @@ export const createDocumentsSurface = ({
searchResults, searchResults,
viewMode, viewMode,
onViewModeChange, onViewModeChange,
onRequestCreateFolder,
creatingFolder,
onRefresh, onRefresh,
onShowSkeuoWorkspace, onShowSkeuoWorkspace,
} = tableProps; } = tableProps;
@@ -1141,8 +1117,6 @@ export const createDocumentsSurface = ({
const actions = createDocumentsTableHeaderActions({ const actions = createDocumentsTableHeaderActions({
viewMode, viewMode,
onViewModeChange, onViewModeChange,
onRequestCreateFolder,
creatingFolder,
onRefresh, onRefresh,
onShowSkeuoWorkspace, onShowSkeuoWorkspace,
}); });
+27 -130
View File
@@ -440,14 +440,10 @@ const AppLayout = () => {
[reportApiError], [reportApiError],
); );
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isCreateFolderModalOpen, setCreateFolderModalOpen] = useState(false);
const [isTagsModalOpen, setTagsModalOpen] = useState(false); const [isTagsModalOpen, setTagsModalOpen] = useState(false);
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false); const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
const [isSettingsModalOpen, setSettingsModalOpen] = useState(false); const [isSettingsModalOpen, setSettingsModalOpen] = useState(false);
const [newFolderName, setNewFolderName] = useState('');
const [createFolderError, setCreateFolderError] = useState('');
const [creatingFolder, setCreatingFolder] = useState(false); const [creatingFolder, setCreatingFolder] = useState(false);
const createFolderInputRef = useRef(null);
const [folderNodes, setFolderNodes] = useState(() => { const [folderNodes, setFolderNodes] = useState(() => {
const rootNode = createRootNode(); const rootNode = createRootNode();
return new Map([[rootNode.id, rootNode]]); return new Map([[rootNode.id, rootNode]]);
@@ -3852,7 +3848,7 @@ const AppLayout = () => {
); );
const handleFolderCreate = useCallback( const handleFolderCreate = useCallback(
async (name, onSuccess) => { async (name) => {
if (!token) { if (!token) {
setStatusMessage('Log in to create folders.', 'error'); setStatusMessage('Log in to create folders.', 'error');
return false; return false;
@@ -3869,7 +3865,6 @@ const AppLayout = () => {
let succeeded = false; let succeeded = false;
try { try {
const { data } = await api.post('/folders', payload); const { data } = await api.post('/folders', payload);
onSuccess();
setStatusMessage('Folder created.', 'success'); setStatusMessage('Folder created.', 'success');
setFolderNodes((prev) => { setFolderNodes((prev) => {
const next = new Map(prev); const next = new Map(prev);
@@ -3895,6 +3890,7 @@ const AppLayout = () => {
return next; return next;
}); });
await ensureFolderData(selectedFolder, { force: true, prefetchDepth: 1 }); await ensureFolderData(selectedFolder, { force: true, prefetchDepth: 1 });
await selectFolder(data.folder.id, { immediate: true });
succeeded = true; succeeded = true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to create folder.'; const message = error.response?.data?.error || 'Failed to create folder.';
@@ -3905,23 +3901,32 @@ const AppLayout = () => {
} }
return succeeded; return succeeded;
}, },
[token, selectedFolder, ensureFolderData, notifyApiError, setStatusMessage], [token, selectedFolder, ensureFolderData, notifyApiError, setStatusMessage, selectFolder],
); );
const openCreateFolderModal = useCallback(() => { const handlePromptCreateFolder = useCallback(async () => {
setNewFolderName('');
setCreateFolderError('');
setCreateFolderModalOpen(true);
}, []);
const closeCreateFolderModal = useCallback(() => {
if (creatingFolder) { if (creatingFolder) {
return; return;
} }
setCreateFolderModalOpen(false); const input = window.prompt('New folder name');
setNewFolderName(''); if (!input) {
setCreateFolderError(''); return;
}, [creatingFolder]); }
const trimmed = input.trim();
if (!trimmed) {
setStatusMessage('Folder name cannot be empty.', 'error');
return;
}
setCreatingFolder(true);
try {
const success = await handleFolderCreate(trimmed);
if (!success) {
setStatusMessage('Unable to create folder. Check the status message for details.', 'error');
}
} finally {
setCreatingFolder(false);
}
}, [creatingFolder, handleFolderCreate, setStatusMessage]);
const openTagsModal = useCallback(() => { const openTagsModal = useCallback(() => {
setCorrespondentsModalOpen(false); setCorrespondentsModalOpen(false);
@@ -3949,66 +3954,6 @@ const AppLayout = () => {
setSettingsModalOpen(false); setSettingsModalOpen(false);
}, []); }, []);
const handleCreateFolderSubmit = useCallback(
async (event) => {
event.preventDefault();
const trimmed = newFolderName.trim();
if (!trimmed) {
setCreateFolderError('Folder name cannot be empty.');
return;
}
if (!token) {
setCreateFolderError('Log in to create folders.');
return;
}
setCreatingFolder(true);
setCreateFolderError('');
try {
const success = await handleFolderCreate(trimmed, () => {
setCreateFolderModalOpen(false);
setNewFolderName('');
setCreateFolderError('');
});
if (!success) {
setCreateFolderError('Unable to create folder. Check the status message for details.');
}
} finally {
setCreatingFolder(false);
}
},
[newFolderName, handleFolderCreate, token],
);
useEffect(() => {
if (!isCreateFolderModalOpen) {
return;
}
const node = createFolderInputRef.current;
if (node) {
node.focus();
node.select();
}
}, [isCreateFolderModalOpen]);
useEffect(() => {
if (!isCreateFolderModalOpen) {
return;
}
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
event.preventDefault();
closeCreateFolderModal();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [isCreateFolderModalOpen, closeCreateFolderModal]);
useEffect(() => { useEffect(() => {
setTagsModalOpen(false); setTagsModalOpen(false);
setCorrespondentsModalOpen(false); setCorrespondentsModalOpen(false);
@@ -4985,8 +4930,6 @@ const AppLayout = () => {
breadcrumbs, breadcrumbs,
onRefresh: refreshCurrentFolder, onRefresh: refreshCurrentFolder,
onShowSkeuoWorkspace: showSkeuoWorkspace, onShowSkeuoWorkspace: showSkeuoWorkspace,
onRequestCreateFolder: openCreateFolderModal,
creatingFolder,
subfolders: currentSubfolders, subfolders: currentSubfolders,
documents, documents,
searchResults, searchResults,
@@ -5059,8 +5002,6 @@ const AppLayout = () => {
handleFolderRename, handleFolderRename,
handleFolderRowClick, handleFolderRowClick,
isFilterActive, isFilterActive,
creatingFolder,
openCreateFolderModal,
openDocumentPreview, openDocumentPreview,
refreshCurrentFolder, refreshCurrentFolder,
searchLoading, searchLoading,
@@ -5092,6 +5033,8 @@ const AppLayout = () => {
onFolderDragStart: handleFolderDragStart, onFolderDragStart: handleFolderDragStart,
onFolderDragEnd: handleFolderDragEnd, onFolderDragEnd: handleFolderDragEnd,
draggedFolderId, draggedFolderId,
onCreateFolder: handlePromptCreateFolder,
creatingFolder,
tags, tags,
activeTagIds: activeTagFilters, activeTagIds: activeTagFilters,
onToggleTagFilter: toggleTagFilter, onToggleTagFilter: toggleTagFilter,
@@ -5148,6 +5091,8 @@ const AppLayout = () => {
toggleTagFilter, toggleTagFilter,
handleTagCreate, handleTagCreate,
handleCorrespondentCreate, handleCorrespondentCreate,
handlePromptCreateFolder,
creatingFolder,
], ],
); );
@@ -5360,54 +5305,6 @@ const AppLayout = () => {
folderName={dropOverlayState.folderName} folderName={dropOverlayState.folderName}
/> />
<Outlet /> <Outlet />
{isCreateFolderModalOpen && (
<div
className="modal-backdrop"
role="presentation"
onClick={closeCreateFolderModal}
>
<div
className="modal"
role="dialog"
aria-modal="true"
aria-labelledby="create-folder-title"
onClick={(event) => event.stopPropagation()}
>
<h3 id="create-folder-title">Create folder</h3>
<form className="modal__form" onSubmit={handleCreateFolderSubmit}>
<label htmlFor="new-folder-name">Folder name</label>
<input
id="new-folder-name"
ref={createFolderInputRef}
value={newFolderName}
onChange={(event) => {
setNewFolderName(event.target.value);
if (createFolderError) {
setCreateFolderError('');
}
}}
placeholder="Enter folder name"
disabled={creatingFolder}
autoComplete="off"
/>
{createFolderError && <p className="modal__error">{createFolderError}</p>}
<div className="modal__actions">
<button
type="button"
className="secondary"
onClick={closeCreateFolderModal}
disabled={creatingFolder}
>
Cancel
</button>
<button type="submit" disabled={creatingFolder}>
{creatingFolder ? 'Creating…' : 'Create'}
</button>
</div>
</form>
</div>
</div>
)}
{isTagsModalOpen && ( {isTagsModalOpen && (
<div <div
className="modal-backdrop" className="modal-backdrop"
+24
View File
@@ -10,6 +10,7 @@ import {
SettingsIcon, SettingsIcon,
CheckIcon, CheckIcon,
PlusIcon, PlusIcon,
FolderPlusIcon,
} from '../ui/icons'; } from '../ui/icons';
import { getTagColorStyle } from '../utils/colors'; import { getTagColorStyle } from '../utils/colors';
@@ -139,6 +140,8 @@ const Sidebar = ({
onFolderDragStart, onFolderDragStart,
onFolderDragEnd, onFolderDragEnd,
draggedFolderId, draggedFolderId,
onCreateFolder,
creatingFolder = false,
tags = [], tags = [],
activeTagIds = [], activeTagIds = [],
onToggleTagFilter, onToggleTagFilter,
@@ -209,6 +212,14 @@ const Sidebar = ({
console.error('[sidebar] failed to create correspondent', error); console.error('[sidebar] failed to create correspondent', error);
} }
}, [onCreateCorrespondent]); }, [onCreateCorrespondent]);
const handleCreateFolder = useCallback(() => {
if (creatingFolder) {
return;
}
onCreateFolder?.();
}, [creatingFolder, onCreateFolder]);
const handleSearchInputChange = useCallback( const handleSearchInputChange = useCallback(
(event) => { (event) => {
onSearchChange?.(event.target.value); onSearchChange?.(event.target.value);
@@ -442,6 +453,19 @@ const Sidebar = ({
<div className="sidebar-section sidebar-section--folders"> <div className="sidebar-section sidebar-section--folders">
<div className="sidebar-section__header"> <div className="sidebar-section__header">
<h3>Folders</h3> <h3>Folders</h3>
{onCreateFolder ? (
<div className="sidebar-section__actions">
<button
type="button"
className="icon-button"
onClick={handleCreateFolder}
aria-label="Create folder"
disabled={creatingFolder}
>
<FolderPlusIcon size={16} />
</button>
</div>
) : null}
</div> </div>
<ul className="folder-tree"> <ul className="folder-tree">
{rootNode && renderNodes([rootNode.id], 0)} {rootNode && renderNodes([rootNode.id], 0)}