From 99fa12a1be32c31c9637f61e681b724401807071 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Tue, 9 Dec 2025 00:03:55 +0100 Subject: [PATCH] feat: refactor frontend tag interaction handlers --- .../components/DesktopDocumentCard.tsx | 50 ++++++------------- .../desktop/components/DesktopWorkspace.tsx | 6 +-- .../documents/components/DocumentEntry.tsx | 8 +-- .../src/documents/components/DocumentTags.tsx | 13 ++--- .../components/DocumentsGridCard.tsx | 10 ++-- .../documents/components/DocumentsListRow.tsx | 10 ++-- .../src/documents/components/EntryTags.tsx | 6 +-- .../interactions/useTagInteractions.ts | 8 ++- .../documents/logic/useDocumentItemLogic.ts | 15 +++--- .../src/documents/panel/DocumentsPanel.tsx | 8 +-- .../panel/useDocumentsContextValues.ts | 9 ++-- 11 files changed, 63 insertions(+), 80 deletions(-) diff --git a/frontend/src/desktop/components/DesktopDocumentCard.tsx b/frontend/src/desktop/components/DesktopDocumentCard.tsx index d0d030c..4e2d6d8 100644 --- a/frontend/src/desktop/components/DesktopDocumentCard.tsx +++ b/frontend/src/desktop/components/DesktopDocumentCard.tsx @@ -1,11 +1,11 @@ import React, { useMemo } from 'react'; import DesktopPreviewCard from './DesktopPreviewCard'; import { resolveCorrespondents } from '../../documents/correspondents'; -import { getTagColorStyle } from '../../utils/colors'; -import type { DocumentId } from '../../types/identifiers'; import type { Document } from '../../types/documents'; import { LayoutCard } from '../logic/LayoutSystem'; import { useCardPointer } from '../interactions/useCardPointer'; +import DocumentTags from '../../documents/components/DocumentTags'; +import { TagInteractionHandlers } from '../../documents/interactions/useTagInteractions'; const preventAll = (event?: React.SyntheticEvent | Event | null) => { if (!event) return; @@ -27,12 +27,7 @@ interface DesktopDocumentCardProps { onDeselect: (ids: string[]) => void; selection: string[]; requestCanvasFocus?: () => void; - onTagDragEnter?: (event: React.DragEvent, docId: DocumentId) => void; - onTagDragOver?: (event: React.DragEvent, doc: Document) => void; - onTagDragLeave?: (event: React.DragEvent, docId: DocumentId) => void; - onTagDrop?: (event: React.DragEvent, doc: Document) => void; - onTagDragStart?: (event: React.DragEvent, doc: Document, tag: any) => void; - onTagDragEnd?: (event: React.DragEvent) => void; + tagHandlers?: TagInteractionHandlers; layoutCard: LayoutCard; } @@ -50,12 +45,7 @@ const DesktopDocumentCard: React.FC = ({ onDeselect, selection, requestCanvasFocus, - onTagDragEnter, - onTagDragOver, - onTagDragLeave, - onTagDrop, - onTagDragStart, - onTagDragEnd, + tagHandlers, layoutCard, }) => { const cardPointerHandlers = useCardPointer( @@ -89,10 +79,10 @@ const DesktopDocumentCard: React.FC = ({ aria-hidden={ariaHidden} ref={(node) => layoutCard?.setRef(node)} {...cardPointerHandlers} - onDragEnter={(event) => onTagDragEnter?.(event, doc.id!)} - onDragOver={(event) => onTagDragOver?.(event, doc)} - onDragLeave={(event) => onTagDragLeave?.(event, doc.id!)} - onDrop={(event) => onTagDrop?.(event, doc)} + onDragEnter={(event) => tagHandlers?.onTagDragEnter(event, doc.id!)} + onDragOver={(event) => tagHandlers?.onTagDragOver(event, doc)} + onDragLeave={(event) => tagHandlers?.onTagDragLeave(event, doc.id!)} + onDrop={(event) => tagHandlers?.onTagDrop(event, doc)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { preventAll(event); @@ -123,24 +113,12 @@ const DesktopDocumentCard: React.FC = ({ )} {tags.length > 0 && ( )} diff --git a/frontend/src/desktop/components/DesktopWorkspace.tsx b/frontend/src/desktop/components/DesktopWorkspace.tsx index beb81ae..c545dc6 100644 --- a/frontend/src/desktop/components/DesktopWorkspace.tsx +++ b/frontend/src/desktop/components/DesktopWorkspace.tsx @@ -9,7 +9,6 @@ import { LayoutStore, LayoutCard } from '../logic/LayoutSystem'; import DesktopDocumentCard from './DesktopDocumentCard'; import usePreviewMetadata from '../hooks/usePreviewMetadata'; import { - TagDragHandlers, useTagInteractions, } from '../../documents/interactions/useTagInteractions'; import './workspace-layout.css'; @@ -183,7 +182,7 @@ const DesktopWorkspaceContent: React.FC = ({ }, []); // Tag Interactions - const tagDragHandlers: TagDragHandlers = useTagInteractions({ + const tagHandlers = useTagInteractions({ onAssignTagToDocument: (docId: string, tagId: string) => { tags.onAttach?.(docId, tagId); }, @@ -191,6 +190,7 @@ const DesktopWorkspaceContent: React.FC = ({ tags.onDetach?.(docId, tagId); }, requestCanvasFocus: focusShell, + onTagClick: tags.onClick, }); useEffect(() => { @@ -392,7 +392,7 @@ const DesktopWorkspaceContent: React.FC = ({ openDocument(doc, isPreview ? 'preview' : 'sidepanel'); }} layoutCard={layoutCard} - {...tagDragHandlers} + tagHandlers={tagHandlers} onSelect={(ids, extend = false) => { if (!extend) { handleSelectionChange(ids); diff --git a/frontend/src/documents/components/DocumentEntry.tsx b/frontend/src/documents/components/DocumentEntry.tsx index 3e067b9..a557e9b 100644 --- a/frontend/src/documents/components/DocumentEntry.tsx +++ b/frontend/src/documents/components/DocumentEntry.tsx @@ -2,11 +2,11 @@ import React from 'react'; import type { DocumentViewLogic } from '../logic/useDocumentViewLogic'; import { useDocumentItemLogic } from '../logic/useDocumentItemLogic'; import EntryShell from './EntryShell'; -import type { TagDragHandlers } from '../interactions/useTagInteractions'; +import type { TagInteractionHandlers } from '../interactions/useTagInteractions'; interface DocumentEntryProps { doc: any; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; viewLogic: DocumentViewLogic; component: React.ElementType; className?: string; @@ -15,8 +15,8 @@ interface DocumentEntryProps { } const DocumentEntry: React.FC = (props) => { - const { doc, tagDragHandlers, component, className, role, children, viewLogic } = props; - const logic = useDocumentItemLogic({ doc, tagDragHandlers, viewLogic }); + const { doc, tagHandlers, component, className, role, children, viewLogic } = props; + const logic = useDocumentItemLogic({ doc, tagHandlers, viewLogic }); return ( | null; onTagClick?: (tagId: Identifier) => void; doc: Document; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; } const DocumentTags: React.FC = ({ @@ -17,7 +17,7 @@ const DocumentTags: React.FC = ({ tagLookupById, onTagClick, doc, - tagDragHandlers, + tagHandlers, }) => { const sortedTags = useMemo(() => { return [...tags].sort((a, b) => { @@ -38,12 +38,13 @@ const DocumentTags: React.FC = ({ const style = getTagColorStyle(colorSource); const tagId = tag?.id ?? null; const clickable = tagId != null && typeof onTagClick === 'function'; + const draggable = !!tagId; const key = tagId ?? `${doc.id}-tag-${index}`; return ( = ({ onTagClick?.(tagId); } : undefined} draggable={!!tagId} - onDragStart={(event) => tagId && tagDragHandlers?.onTagDragStart(event, doc, tag)} - onDragEnd={tagDragHandlers?.onTagDragEnd} + onDragStart={(event) => tagId && tagHandlers?.onTagDragStart(event, doc, tag)} + onDragEnd={tagHandlers?.onTagDragEnd} onKeyDown={clickable ? (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); diff --git a/frontend/src/documents/components/DocumentsGridCard.tsx b/frontend/src/documents/components/DocumentsGridCard.tsx index cc591a2..b9e537b 100644 --- a/frontend/src/documents/components/DocumentsGridCard.tsx +++ b/frontend/src/documents/components/DocumentsGridCard.tsx @@ -12,17 +12,17 @@ import EntryCorrespondents from './EntryCorrespondents'; import EntryTags from './EntryTags'; import FolderEntry from './FolderEntry'; import DocumentEntry from './DocumentEntry'; -import { TagDragHandlers } from '../interactions/useTagInteractions'; +import { TagInteractionHandlers } from '../interactions/useTagInteractions'; interface DocumentsGridCardProps { entry: DocumentsListEntry; viewLogic: DocumentViewLogic; iconSize?: number; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; } const DocumentsGridCard: React.FC = (props) => { - const { entry, iconSize, tagDragHandlers } = props; + const { entry, iconSize, tagHandlers } = props; const { ensureAssetUrl, getDocumentAsset } = useDocumentsAssetContext(); const { scrollRef, activeCorrespondentIdSet, tagLookupById } = useDocumentsViewStateContext(); const { @@ -80,7 +80,7 @@ const DocumentsGridCard: React.FC = (props) => { return ( = (props) => { tagLookupById={tagLookupById} onTagClick={onTagClick} doc={doc} - tagDragHandlers={logic.handlers.tagDragHandlers} + tagHandlers={logic.handlers.tagHandlers} /> diff --git a/frontend/src/documents/components/DocumentsListRow.tsx b/frontend/src/documents/components/DocumentsListRow.tsx index 5ff7598..be63d97 100644 --- a/frontend/src/documents/components/DocumentsListRow.tsx +++ b/frontend/src/documents/components/DocumentsListRow.tsx @@ -14,17 +14,17 @@ import EntryTags from './EntryTags'; import FolderEntry from './FolderEntry'; import DocumentEntry from './DocumentEntry'; -import { TagDragHandlers } from '../interactions/useTagInteractions'; +import { TagInteractionHandlers } from '../interactions/useTagInteractions'; interface DocumentsListRowProps { entry: DocumentsListEntry; viewLogic: DocumentViewLogic; iconSize?: number; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; } const DocumentsListRow: React.FC = (props) => { - const { entry, iconSize, tagDragHandlers } = props; + const { entry, iconSize, tagHandlers } = props; const { ensureAssetUrl, getDocumentAsset } = useDocumentsAssetContext(); const { scrollRef, activeCorrespondentIdSet, tagLookupById } = useDocumentsViewStateContext(); const { @@ -91,7 +91,7 @@ const DocumentsListRow: React.FC = (props) => { return ( = (props) => { tagLookupById={tagLookupById} onTagClick={onTagClick} doc={doc} - tagDragHandlers={logic.handlers.tagDragHandlers} + tagHandlers={logic.handlers.tagHandlers} /> diff --git a/frontend/src/documents/components/EntryTags.tsx b/frontend/src/documents/components/EntryTags.tsx index 84d45c5..140efb5 100644 --- a/frontend/src/documents/components/EntryTags.tsx +++ b/frontend/src/documents/components/EntryTags.tsx @@ -1,7 +1,7 @@ import React from 'react'; import DocumentTags from './DocumentTags'; import type { Document, DocumentTag } from '../../types/documents'; -import type { TagDragHandlers } from '../interactions/useTagInteractions'; +import type { TagInteractionHandlers } from '../interactions/useTagInteractions'; import type { Identifier } from '../../types/identifiers'; interface EntryTagsProps { @@ -9,7 +9,7 @@ interface EntryTagsProps { tagLookupById?: Map | null; onTagClick?: (tagId: Identifier) => void; doc: Document; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; } const EntryTags: React.FC = (props) => { @@ -23,7 +23,7 @@ const EntryTags: React.FC = (props) => { tagLookupById={props.tagLookupById} onTagClick={props.onTagClick} doc={props.doc} - tagDragHandlers={props.tagDragHandlers} + tagHandlers={props.tagHandlers} /> ); }; diff --git a/frontend/src/documents/interactions/useTagInteractions.ts b/frontend/src/documents/interactions/useTagInteractions.ts index 1cbf8cb..e0277e1 100644 --- a/frontend/src/documents/interactions/useTagInteractions.ts +++ b/frontend/src/documents/interactions/useTagInteractions.ts @@ -48,6 +48,7 @@ interface UseTagInteractionsArgs { onAssignTagToDocument?: (docId: Identifier, tagId: Identifier) => void; onRemoveTagFromDocument?: (docId: Identifier, tagId: Identifier) => void; requestCanvasFocus?: () => void; + onTagClick?: (tagId: Identifier) => void; } interface DraggingTagState { @@ -55,20 +56,22 @@ interface DraggingTagState { previewClone?: HTMLElement; } -export interface TagDragHandlers { +export interface TagInteractionHandlers { onTagDragEnter: (event: React.DragEvent, docId: Identifier) => void; onTagDragOver: (event: React.DragEvent, doc: Document) => void; onTagDragLeave: (event: React.DragEvent, docId: Identifier) => void; onTagDrop: (event: React.DragEvent, doc: Document) => void; onTagDragStart: (event: React.DragEvent, doc: Document, tag: DocumentTag) => void; onTagDragEnd: (event: React.DragEvent) => void; + onTagClick?: (tagId: Identifier) => void; } export const useTagInteractions = ({ onAssignTagToDocument, onRemoveTagFromDocument, requestCanvasFocus, -}: UseTagInteractionsArgs): TagDragHandlers => { + onTagClick, +}: UseTagInteractionsArgs): TagInteractionHandlers => { const draggingTagRef = useRef(null); const isTagTransfer = useCallback((event: React.DragEvent) => isTagTransferEvent(event), []); @@ -241,5 +244,6 @@ export const useTagInteractions = ({ onTagDrop, onTagDragStart, onTagDragEnd, + onTagClick, }; }; diff --git a/frontend/src/documents/logic/useDocumentItemLogic.ts b/frontend/src/documents/logic/useDocumentItemLogic.ts index 1d84df0..ba7f6cf 100644 --- a/frontend/src/documents/logic/useDocumentItemLogic.ts +++ b/frontend/src/documents/logic/useDocumentItemLogic.ts @@ -5,16 +5,15 @@ import type { Document } from '../../types/documents'; import { useDocumentsCommandContext } from '../context/DocumentsCommandContext'; import { useDocumentsViewStateContext } from '../context/DocumentsViewStateContext'; import type { DocumentViewLogic } from './useDocumentViewLogic'; - -import { TagDragHandlers } from '../interactions/useTagInteractions'; +import { TagInteractionHandlers } from '../interactions/useTagInteractions'; interface UseDocumentItemLogicArgs { doc: Document; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; viewLogic: DocumentViewLogic; // Retained from original props } -export const useDocumentItemLogic = ({ doc, tagDragHandlers, viewLogic }: UseDocumentItemLogicArgs) => { +export const useDocumentItemLogic = ({ doc, tagHandlers, viewLogic }: UseDocumentItemLogicArgs) => { const { draggingDocumentIdsSet } = useDocumentsViewStateContext(); @@ -70,10 +69,10 @@ export const useDocumentItemLogic = ({ doc, tagDragHandlers, viewLogic }: UseDoc }, onDragStart: (event: DragEvent) => onDocumentDragStart?.(event, doc), onDragEnd: (event: DragEvent) => onDocumentDragEnd?.(event), - onDragOver: (event: DragEvent) => tagDragHandlers?.onTagDragOver(event, doc), - onDragLeave: (event: DragEvent) => tagDragHandlers?.onTagDragLeave(event, doc.id), - onDrop: (event: DragEvent) => { tagDragHandlers?.onTagDrop(event, doc); }, - tagDragHandlers, + onDragOver: (event: DragEvent) => tagHandlers?.onTagDragOver(event, doc), + onDragLeave: (event: DragEvent) => tagHandlers?.onTagDragLeave(event, doc.id), + onDrop: (event: DragEvent) => { tagHandlers?.onTagDrop(event, doc); }, + tagHandlers, onRenameChange: setDocumentDraft, onRenameSubmit: () => submitDocumentEditing(doc), onRenameCancel: (event?: React.SyntheticEvent) => cancelDocumentEditing(event), diff --git a/frontend/src/documents/panel/DocumentsPanel.tsx b/frontend/src/documents/panel/DocumentsPanel.tsx index 7da3850..4d8a4ee 100644 --- a/frontend/src/documents/panel/DocumentsPanel.tsx +++ b/frontend/src/documents/panel/DocumentsPanel.tsx @@ -48,11 +48,11 @@ interface DocumentsPanelProps extends DocumentsPanelInnerProps { selectionValue: WorkspaceSelectionValue; } -import type { TagDragHandlers } from '../interactions/useTagInteractions'; +import type { TagInteractionHandlers } from '../interactions/useTagInteractions'; export interface DocumentsViewProps { entries: DocumentsListEntry[]; - tagDragHandlers?: TagDragHandlers; + tagHandlers?: TagInteractionHandlers; } const DocumentsPanelInner: React.FC = (props) => { @@ -88,7 +88,7 @@ const DocumentsPanelInner: React.FC = (props) => { assetContextValue, viewStateContextValue, commandContextValue, - tagDragHandlers, + tagHandlers, scrollRef, hasDocumentEntries, } = useDocumentsContextValues(props); @@ -246,7 +246,7 @@ const DocumentsPanelInner: React.FC = (props) => { const viewProps = { entries, viewId: currentFolderId, - tagDragHandlers, + tagHandlers, }; const [iconSizes] = useState({ diff --git a/frontend/src/documents/panel/useDocumentsContextValues.ts b/frontend/src/documents/panel/useDocumentsContextValues.ts index 4595d48..b3aae5e 100644 --- a/frontend/src/documents/panel/useDocumentsContextValues.ts +++ b/frontend/src/documents/panel/useDocumentsContextValues.ts @@ -38,9 +38,10 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => { } = useDocumentsFilter(); // Handlers - const tagDragHandlers = useTagInteractions({ + const tagHandlers = useTagInteractions({ onAssignTagToDocument: props.onDocumentTagAttach, onRemoveTagFromDocument: props.onDocumentTagDetach, + onTagClick: toggleTagFilter, }); // Derived State @@ -147,7 +148,7 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => { handleFolderClick, handleDocumentDragStartLocal, handleDocumentDragEndLocal, - tagDragHandlers, + tagHandlers, toggleTagFilter, toggleCorrespondentFilter, }); @@ -158,7 +159,7 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => { handleFolderClick, handleDocumentDragStartLocal, handleDocumentDragEndLocal, - tagDragHandlers, + tagHandlers, toggleTagFilter, toggleCorrespondentFilter, }; @@ -198,7 +199,7 @@ export const useDocumentsContextValues = (props: DocumentsPanelInnerProps) => { assetContextValue, viewStateContextValue, commandContextValue, - tagDragHandlers, + tagHandlers, scrollRef, hasDocumentEntries, isSearchLoading,