refactor(documents): encapsulate panel logic and state into dedicated React contexts

This commit is contained in:
2025-12-08 12:31:30 +01:00
parent 44cf4603fe
commit 73468bbb02
13 changed files with 524 additions and 338 deletions
@@ -1,10 +1,9 @@
import React from 'react';
import type { DocumentsViewProps } from '../panel/DocumentsPanel';
import type { DocumentViewLogic } from '../logic/useDocumentViewLogic';
import { useDocumentItemLogic } from '../logic/useDocumentItemLogic';
import EntryShell from './EntryShell';
interface DocumentEntryProps extends DocumentsViewProps {
interface DocumentEntryProps {
doc: any;
viewLogic: DocumentViewLogic;
component: React.ElementType;
@@ -15,7 +14,7 @@ interface DocumentEntryProps extends DocumentsViewProps {
const DocumentEntry: React.FC<DocumentEntryProps> = (props) => {
const { doc, component, className, role, children, viewLogic } = props;
const logic = useDocumentItemLogic({ doc, viewLogic, ...props });
const logic = useDocumentItemLogic({ doc, viewLogic });
return (
<EntryShell
@@ -3,7 +3,9 @@ import { FolderIcon } from '../../components/icons';
import DocumentThumbnailImage from '../DocumentThumbnailImage';
import { resolveCorrespondents } from '../correspondents';
import type { DocumentsListEntry } from '../../types/documents';
import type { DocumentsViewProps } from '../panel/DocumentsPanel';
import { useDocumentsAssetContext } from '../context/DocumentsAssetContext';
import { useDocumentsViewStateContext } from '../context/DocumentsViewStateContext';
import { useDocumentsCommandContext } from '../context/DocumentsCommandContext';
import type { DocumentViewLogic } from '../logic/useDocumentViewLogic';
import EditableEntryTitle from './EditableEntryTitle';
import EntryCorrespondents from './EntryCorrespondents';
@@ -11,7 +13,7 @@ import EntryTags from './EntryTags';
import FolderEntry from './FolderEntry';
import DocumentEntry from './DocumentEntry';
interface DocumentsGridCardProps extends DocumentsViewProps {
interface DocumentsGridCardProps {
entry: DocumentsListEntry;
viewLogic: DocumentViewLogic;
iconSize?: number;
@@ -19,6 +21,12 @@ interface DocumentsGridCardProps extends DocumentsViewProps {
const DocumentsGridCard: React.FC<DocumentsGridCardProps> = (props) => {
const { entry, iconSize } = props;
const { ensureAssetUrl, getDocumentAsset } = useDocumentsAssetContext();
const { scrollRef, activeCorrespondentIdSet, tagLookupById } = useDocumentsViewStateContext();
const {
correspondents: { onClick: onCorrespondentClick },
tags: { onClick: onTagClick, onDetach: onDocumentTagDetach }
} = useDocumentsCommandContext();
if (entry.type === 'folder') {
const folder = entry.folder;
@@ -26,8 +34,8 @@ const DocumentsGridCard: React.FC<DocumentsGridCardProps> = (props) => {
return (
<FolderEntry
{...props}
folder={folder}
viewLogic={props.viewLogic}
component="div"
className="document-card folder-card"
role="listitem"
@@ -69,8 +77,8 @@ const DocumentsGridCard: React.FC<DocumentsGridCardProps> = (props) => {
return (
<DocumentEntry
{...props}
doc={doc}
viewLogic={props.viewLogic}
component="div"
className="document-card document"
role="listitem"
@@ -79,18 +87,18 @@ const DocumentsGridCard: React.FC<DocumentsGridCardProps> = (props) => {
<>
<DocumentThumbnailImage
document={doc}
ensureAssetUrl={props.ensureAssetUrl}
getAsset={props.getDocumentAsset}
ensureAssetUrl={ensureAssetUrl}
getAsset={getDocumentAsset}
alt={`Thumbnail for ${doc.title}`}
maxSize={iconSize}
scrollRootRef={props.scrollRef}
scrollRootRef={scrollRef}
/>
<div className="document-card__meta">
<div className="document-card__title" title={doc.title}>
<EntryCorrespondents
correspondents={correspondents}
activeCorrespondentIdSet={props.activeCorrespondentIdSet}
onCorrespondentClick={props.onCorrespondentClick}
activeCorrespondentIdSet={activeCorrespondentIdSet}
onCorrespondentClick={onCorrespondentClick}
/>
<div className="document-card__title-row">
<EditableEntryTitle
@@ -113,10 +121,10 @@ const DocumentsGridCard: React.FC<DocumentsGridCardProps> = (props) => {
<div className="document-card__tags">
<EntryTags
tags={doc.tags || []}
tagLookupById={props.tagLookupById}
onTagClick={props.onTagClick}
tagLookupById={tagLookupById}
onTagClick={onTagClick}
docId={doc.id}
onDocumentTagDetach={props.onDocumentTagDetach}
onDocumentTagDetach={onDocumentTagDetach}
onTagDragStart={logic.handlers.onTagDragStart}
onTagDragEnd={logic.handlers.onTagDragEnd}
/>
@@ -128,4 +136,5 @@ const DocumentsGridCard: React.FC<DocumentsGridCardProps> = (props) => {
);
};
export default DocumentsGridCard;
@@ -4,7 +4,9 @@ import { formatDate } from '../../utils/date';
import DocumentThumbnailImage from '../DocumentThumbnailImage';
import { resolveCorrespondents } from '../correspondents';
import type { DocumentsListEntry } from '../../types/documents';
import type { DocumentsViewProps } from '../panel/DocumentsPanel';
import { useDocumentsAssetContext } from '../context/DocumentsAssetContext';
import { useDocumentsViewStateContext } from '../context/DocumentsViewStateContext';
import { useDocumentsCommandContext } from '../context/DocumentsCommandContext';
import type { DocumentViewLogic } from '../logic/useDocumentViewLogic';
import EditableEntryTitle from './EditableEntryTitle';
import EntryCorrespondents from './EntryCorrespondents';
@@ -12,7 +14,7 @@ import EntryTags from './EntryTags';
import FolderEntry from './FolderEntry';
import DocumentEntry from './DocumentEntry';
interface DocumentsListRowProps extends DocumentsViewProps {
interface DocumentsListRowProps {
entry: DocumentsListEntry;
viewLogic: DocumentViewLogic;
iconSize?: number;
@@ -20,6 +22,12 @@ interface DocumentsListRowProps extends DocumentsViewProps {
const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
const { entry, iconSize } = props;
const { ensureAssetUrl, getDocumentAsset } = useDocumentsAssetContext();
const { scrollRef, activeCorrespondentIdSet, tagLookupById } = useDocumentsViewStateContext();
const {
correspondents: { onClick: onCorrespondentClick },
tags: { onClick: onTagClick, onDetach: onDocumentTagDetach }
} = useDocumentsCommandContext();
if (entry.type === 'folder') {
const folder = entry.folder;
@@ -27,8 +35,8 @@ const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
return (
<FolderEntry
{...props}
folder={folder}
viewLogic={props.viewLogic}
component="tr"
className="folder"
>
@@ -79,8 +87,8 @@ const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
return (
<DocumentEntry
{...props}
doc={doc}
viewLogic={props.viewLogic}
component="tr"
className="document"
>
@@ -89,10 +97,10 @@ const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
<td className="thumb-cell">
<DocumentThumbnailImage
document={doc}
ensureAssetUrl={props.ensureAssetUrl}
getAsset={props.getDocumentAsset}
ensureAssetUrl={ensureAssetUrl}
getAsset={getDocumentAsset}
alt={`Thumbnail for ${doc.title}`}
scrollRootRef={props.scrollRef}
scrollRootRef={scrollRef}
maxSize={props.iconSize}
/>
</td>
@@ -102,8 +110,8 @@ const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
<span className="doc-name__title">
<EntryCorrespondents
correspondents={correspondents}
activeCorrespondentIdSet={props.activeCorrespondentIdSet}
onCorrespondentClick={props.onCorrespondentClick}
activeCorrespondentIdSet={activeCorrespondentIdSet}
onCorrespondentClick={onCorrespondentClick}
/>
<span className="doc-name__primary">
<EditableEntryTitle
@@ -127,10 +135,10 @@ const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
<div className="doc-name__tags">
<EntryTags
tags={doc.tags || []}
tagLookupById={props.tagLookupById}
onTagClick={props.onTagClick}
tagLookupById={tagLookupById}
onTagClick={onTagClick}
docId={doc.id}
onDocumentTagDetach={props.onDocumentTagDetach}
onDocumentTagDetach={onDocumentTagDetach}
onTagDragStart={logic.handlers.onTagDragStart}
onTagDragEnd={logic.handlers.onTagDragEnd}
/>
@@ -145,4 +153,5 @@ const DocumentsListRow: React.FC<DocumentsListRowProps> = (props) => {
);
};
export default DocumentsListRow;
@@ -1,10 +1,9 @@
import React from 'react';
import type { DocumentsViewProps } from '../panel/DocumentsPanel';
import type { DocumentViewLogic } from '../logic/useDocumentViewLogic';
import { useFolderItemLogic } from '../features/folders/useFolderItemLogic';
import EntryShell from './EntryShell';
interface FolderEntryProps extends DocumentsViewProps {
interface FolderEntryProps {
folder: any;
viewLogic: DocumentViewLogic;
component: React.ElementType;
@@ -15,7 +14,7 @@ interface FolderEntryProps extends DocumentsViewProps {
const FolderEntry: React.FC<FolderEntryProps> = (props) => {
const { folder, component, className, role, children, viewLogic } = props;
const logic = useFolderItemLogic({ folder, viewLogic, ...props });
const logic = useFolderItemLogic({ folder, viewLogic });
return (
<EntryShell