move to sidebar
This commit is contained in:
+151
-133
@@ -345,6 +345,8 @@ const AppLayout = () => {
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isCreateFolderModalOpen, setCreateFolderModalOpen] = useState(false);
|
||||
const [isTagsModalOpen, setTagsModalOpen] = useState(false);
|
||||
const [isCorrespondentsModalOpen, setCorrespondentsModalOpen] = useState(false);
|
||||
const [newFolderName, setNewFolderName] = useState('');
|
||||
const [createFolderError, setCreateFolderError] = useState('');
|
||||
const [creatingFolder, setCreatingFolder] = useState(false);
|
||||
@@ -423,13 +425,9 @@ const AppLayout = () => {
|
||||
const documentsRouteMatch = useMatch('/documents');
|
||||
const documentsFolderRouteMatch = useMatch('/documents/folder/:folderId');
|
||||
const documentsDetailRouteMatch = useMatch('/documents/:documentId');
|
||||
const tagsRouteMatch = useMatch('/tags');
|
||||
const correspondentsRouteMatch = useMatch('/correspondents');
|
||||
const isDocumentsRoute = Boolean(
|
||||
documentsRouteMatch || documentsFolderRouteMatch || documentsDetailRouteMatch,
|
||||
);
|
||||
const isTagsRoute = Boolean(tagsRouteMatch);
|
||||
const isCorrespondentsRoute = Boolean(correspondentsRouteMatch);
|
||||
const toggleTagFilter = useCallback((tagId) => {
|
||||
if (!tagId) return;
|
||||
setActiveTagFilters((previous) =>
|
||||
@@ -455,6 +453,10 @@ const AppLayout = () => {
|
||||
setSearchLoading(false);
|
||||
}, []);
|
||||
|
||||
const handleSearchChange = useCallback((value) => {
|
||||
setSearchQuery(value);
|
||||
}, []);
|
||||
|
||||
const handleSearchSubmit = useCallback(() => {
|
||||
if (!navigate) return;
|
||||
const targetFolder = selectedFolder && selectedFolder !== 'root' ? selectedFolder : 'root';
|
||||
@@ -3682,6 +3684,24 @@ const AppLayout = () => {
|
||||
setCreateFolderError('');
|
||||
}, [creatingFolder]);
|
||||
|
||||
const openTagsModal = useCallback(() => {
|
||||
setCorrespondentsModalOpen(false);
|
||||
setTagsModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const closeTagsModal = useCallback(() => {
|
||||
setTagsModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const openCorrespondentsModal = useCallback(() => {
|
||||
setTagsModalOpen(false);
|
||||
setCorrespondentsModalOpen(true);
|
||||
}, []);
|
||||
|
||||
const closeCorrespondentsModal = useCallback(() => {
|
||||
setCorrespondentsModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleCreateFolderSubmit = useCallback(
|
||||
async (event) => {
|
||||
event.preventDefault();
|
||||
@@ -3742,6 +3762,31 @@ const AppLayout = () => {
|
||||
};
|
||||
}, [isCreateFolderModalOpen, closeCreateFolderModal]);
|
||||
|
||||
useEffect(() => {
|
||||
setTagsModalOpen(false);
|
||||
setCorrespondentsModalOpen(false);
|
||||
}, [location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTagsModalOpen && !isCorrespondentsModalOpen) {
|
||||
return;
|
||||
}
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
if (isTagsModalOpen) {
|
||||
closeTagsModal();
|
||||
} else if (isCorrespondentsModalOpen) {
|
||||
closeCorrespondentsModal();
|
||||
}
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [isTagsModalOpen, isCorrespondentsModalOpen, closeTagsModal, closeCorrespondentsModal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return undefined;
|
||||
|
||||
@@ -4442,6 +4487,16 @@ const AppLayout = () => {
|
||||
correspondents,
|
||||
activeCorrespondentIds: activeCorrespondentFilters,
|
||||
onToggleCorrespondentFilter: toggleCorrespondentFilter,
|
||||
appStatus,
|
||||
loading,
|
||||
previewActive,
|
||||
searchQuery,
|
||||
onSearchChange: handleSearchChange,
|
||||
onSearchSubmit: handleSearchSubmit,
|
||||
onSearchClear: clearFilters,
|
||||
isFilterActive,
|
||||
onLogout: handleLogout,
|
||||
status,
|
||||
};
|
||||
|
||||
const resolveThumbnailUrlForDoc = useCallback(
|
||||
@@ -4585,7 +4640,6 @@ const AppLayout = () => {
|
||||
tags,
|
||||
refreshTags,
|
||||
handleTagUpdate,
|
||||
handleTagCreate,
|
||||
handleTagDelete,
|
||||
handleDocumentTagAttach,
|
||||
correspondents,
|
||||
@@ -4610,6 +4664,8 @@ const AppLayout = () => {
|
||||
ensurePreviewData,
|
||||
notifyApiError,
|
||||
resolveApiPath,
|
||||
openTagsModal,
|
||||
openCorrespondentsModal,
|
||||
}),
|
||||
[
|
||||
token,
|
||||
@@ -4622,7 +4678,6 @@ const AppLayout = () => {
|
||||
tags,
|
||||
refreshTags,
|
||||
handleTagUpdate,
|
||||
handleTagCreate,
|
||||
handleTagDelete,
|
||||
handleDocumentTagAttach,
|
||||
correspondents,
|
||||
@@ -4647,6 +4702,8 @@ const AppLayout = () => {
|
||||
ensurePreviewData,
|
||||
notifyApiError,
|
||||
resolveApiPath,
|
||||
openTagsModal,
|
||||
openCorrespondentsModal,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -4667,81 +4724,6 @@ const AppLayout = () => {
|
||||
active={dropOverlayState.active}
|
||||
folderName={dropOverlayState.folderName}
|
||||
/>
|
||||
<header className="app-bar">
|
||||
<div className="app-bar__main">
|
||||
<div className="app-bar__meta">
|
||||
<h1>Papercrate</h1>
|
||||
<span className="app-bar__hint">
|
||||
{appStatus === 'bootstrapping' && loading
|
||||
? 'Loading your library…'
|
||||
: previewActive
|
||||
? 'Viewing document preview. Press ← Back to return to the library.'
|
||||
: 'Drag files here to upload.'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="app-bar__search">
|
||||
<input
|
||||
type="search"
|
||||
value={searchQuery}
|
||||
onChange={(event) => setSearchQuery(event.target.value)}
|
||||
placeholder="Search documents"
|
||||
aria-label="Search documents"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handleSearchSubmit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isFilterActive && (
|
||||
<button
|
||||
type="button"
|
||||
className="app-bar__search-clear"
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="app-bar__right">
|
||||
<div className="app-bar__links">
|
||||
<button
|
||||
type="button"
|
||||
className={`app-bar__link${isDocumentsRoute ? ' active' : ''}`}
|
||||
onClick={() => navigate('/documents')}
|
||||
>
|
||||
Documents
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`app-bar__link${isTagsRoute ? ' active' : ''}`}
|
||||
onClick={() => navigate('/tags')}
|
||||
>
|
||||
Tags
|
||||
<span className="app-bar__link-count">{tags.length}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`app-bar__link${isCorrespondentsRoute ? ' active' : ''}`}
|
||||
onClick={() => navigate('/correspondents')}
|
||||
>
|
||||
Correspondents
|
||||
<span className="app-bar__link-count">{correspondents.length}</span>
|
||||
</button>
|
||||
</div>
|
||||
{status && (
|
||||
<div className="app-bar__status">
|
||||
<StatusBanner status={status} />
|
||||
</div>
|
||||
)}
|
||||
<div className="app-bar__actions">
|
||||
<button className="secondary" onClick={handleLogout}>
|
||||
Log out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<Outlet />
|
||||
{isCreateFolderModalOpen && (
|
||||
<div
|
||||
@@ -4791,6 +4773,74 @@ const AppLayout = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isTagsModalOpen && (
|
||||
<div
|
||||
className="modal-backdrop"
|
||||
role="presentation"
|
||||
onClick={closeTagsModal}
|
||||
>
|
||||
<div
|
||||
className="modal modal--panel"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="tags-modal-title"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="panel-modal__header">
|
||||
<h3 id="tags-modal-title">Manage Tags</h3>
|
||||
<button type="button" className="secondary" onClick={closeTagsModal}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
<div className="panel-modal__body">
|
||||
<TagsPanel
|
||||
tags={tags}
|
||||
onRefresh={refreshTags}
|
||||
onCreateTag={handleTagCreate}
|
||||
onUpdateTag={handleTagUpdate}
|
||||
onDeleteTag={handleTagDelete}
|
||||
onNotify={setStatusMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isCorrespondentsModalOpen && (
|
||||
<div
|
||||
className="modal-backdrop"
|
||||
role="presentation"
|
||||
onClick={closeCorrespondentsModal}
|
||||
>
|
||||
<div
|
||||
className="modal modal--panel"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="correspondents-modal-title"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="panel-modal__header">
|
||||
<h3 id="correspondents-modal-title">Manage Correspondents</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={closeCorrespondentsModal}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
<div className="panel-modal__body">
|
||||
<CorrespondentsPanel
|
||||
correspondents={correspondents}
|
||||
onRefresh={refreshCorrespondents}
|
||||
onCreate={handleCorrespondentCreate}
|
||||
onUpdate={handleCorrespondentUpdate}
|
||||
onDelete={handleCorrespondentDelete}
|
||||
onNotify={setStatusMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AppShellContext.Provider>
|
||||
);
|
||||
@@ -4803,20 +4853,35 @@ const DocumentsRoute = () => {
|
||||
detailPanelProps,
|
||||
workspaceMode,
|
||||
skeuoWorkspaceProps,
|
||||
openTagsModal,
|
||||
openCorrespondentsModal,
|
||||
} = useAppShell();
|
||||
|
||||
const sidebarPropsWithActions = useMemo(
|
||||
() => ({
|
||||
...sidebarProps,
|
||||
onManageTags: openTagsModal,
|
||||
onManageCorrespondents: openCorrespondentsModal,
|
||||
}),
|
||||
[sidebarProps, openTagsModal, openCorrespondentsModal],
|
||||
);
|
||||
|
||||
if (workspaceMode === 'skeuo') {
|
||||
return (
|
||||
<DocumentsLayout sidebarProps={sidebarProps}>
|
||||
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
|
||||
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
|
||||
<div className="main-content">
|
||||
<SkeuomorphicWorkspace {...skeuoWorkspaceProps} />
|
||||
</div>
|
||||
</DocumentsLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentsLayout sidebarProps={sidebarProps}>
|
||||
<DocumentsTable {...documentsTableProps} />
|
||||
<DetailPanel {...detailPanelProps} />
|
||||
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
|
||||
<div className="main-content main-content--documents">
|
||||
<DocumentsTable {...documentsTableProps} />
|
||||
<DetailPanel {...detailPanelProps} />
|
||||
</div>
|
||||
</DocumentsLayout>
|
||||
);
|
||||
};
|
||||
@@ -4951,51 +5016,6 @@ const LoginRoute = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function TagsRoute() {
|
||||
const {
|
||||
tags,
|
||||
refreshTags,
|
||||
handleTagUpdate,
|
||||
handleTagDelete,
|
||||
setStatusMessage,
|
||||
} = useAppShell();
|
||||
return (
|
||||
<main className="panels-main">
|
||||
<TagsPanel
|
||||
tags={tags}
|
||||
onRefresh={refreshTags}
|
||||
onUpdateTag={handleTagUpdate}
|
||||
onDeleteTag={handleTagDelete}
|
||||
onNotify={setStatusMessage}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function CorrespondentsRoute() {
|
||||
const {
|
||||
correspondents,
|
||||
refreshCorrespondents,
|
||||
handleCorrespondentCreate,
|
||||
handleCorrespondentUpdate,
|
||||
handleCorrespondentDelete,
|
||||
setStatusMessage,
|
||||
} = useAppShell();
|
||||
|
||||
return (
|
||||
<main className="panels-main">
|
||||
<CorrespondentsPanel
|
||||
correspondents={correspondents}
|
||||
onRefresh={refreshCorrespondents}
|
||||
onCreate={handleCorrespondentCreate}
|
||||
onUpdate={handleCorrespondentUpdate}
|
||||
onDelete={handleCorrespondentDelete}
|
||||
onNotify={setStatusMessage}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
const AppRouter = () => (
|
||||
<Routes>
|
||||
<Route path="/account/login" element={<LoginRoute />} />
|
||||
@@ -5004,8 +5024,6 @@ const AppRouter = () => (
|
||||
<Route path="/documents" element={<DocumentsRoute />} />
|
||||
<Route path="/documents/folder/:folderId" element={<DocumentsRoute />} />
|
||||
<Route path="/documents/:documentId" element={<DocumentViewerRoute />} />
|
||||
<Route path="/tags" element={<TagsRoute />} />
|
||||
<Route path="/correspondents" element={<CorrespondentsRoute />} />
|
||||
<Route path="*" element={<Navigate to="/documents" replace />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
|
||||
Reference in New Issue
Block a user