refactor: Introduce dedicated Document and Asset types and migrate codebase from DocumentLike.
This commit is contained in:
@@ -5,12 +5,7 @@ import { getTagColorStyle } from '../utils/colors';
|
||||
import { preventAll } from './events';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: string;
|
||||
title?: string;
|
||||
tags?: Array<{ id?: string; label?: string; color?: string | null }>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface PendingRemovalTag {
|
||||
docId?: string;
|
||||
@@ -18,7 +13,7 @@ interface PendingRemovalTag {
|
||||
}
|
||||
|
||||
interface DesktopDocumentCardProps {
|
||||
doc: DocumentLike;
|
||||
doc: Document;
|
||||
style?: React.CSSProperties;
|
||||
shouldLoad?: boolean;
|
||||
dragging?: boolean;
|
||||
@@ -35,9 +30,9 @@ interface DesktopDocumentCardProps {
|
||||
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
|
||||
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
|
||||
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: DocumentId) => void;
|
||||
onTagDrop?: (event: React.DragEvent<HTMLDivElement>, doc: DocumentLike) => void;
|
||||
onDocTagPointerDown?: (event: React.PointerEvent<HTMLElement>, doc: DocumentLike, tag: any) => void;
|
||||
onDocTagDragStart?: (event: React.DragEvent<HTMLElement>, doc: DocumentLike, tag: any) => void;
|
||||
onTagDrop?: (event: React.DragEvent<HTMLDivElement>, doc: Document) => void;
|
||||
onDocTagPointerDown?: (event: React.PointerEvent<HTMLElement>, doc: Document, tag: any) => void;
|
||||
onDocTagDragStart?: (event: React.DragEvent<HTMLElement>, doc: Document, tag: any) => void;
|
||||
onDocTagDrag?: (event: React.DragEvent<HTMLElement>) => void;
|
||||
onDocTagDragEnd?: (event: React.DragEvent<HTMLElement>) => void;
|
||||
pendingRemovalTag?: PendingRemovalTag | null;
|
||||
|
||||
@@ -2,12 +2,7 @@ import { useEffect } from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import { useAssetNavigator } from '../hooks/useAssetNavigator';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier;
|
||||
title?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface AssetLike {
|
||||
id?: Identifier;
|
||||
@@ -23,7 +18,7 @@ type EnsureAssetUrl = (
|
||||
options?: { force?: boolean;[key: string]: unknown },
|
||||
) => Promise<unknown>;
|
||||
|
||||
type GetDocumentAsset = (document: DocumentLike | null, assetType: string) => AssetLike | null;
|
||||
type GetDocumentAsset = (document: Document | null, assetType: string) => AssetLike | null;
|
||||
|
||||
interface NavigatorSnapshot {
|
||||
url: string | null;
|
||||
@@ -33,7 +28,7 @@ interface NavigatorSnapshot {
|
||||
}
|
||||
|
||||
interface DesktopPreviewCardProps {
|
||||
doc: DocumentLike | null;
|
||||
doc: Document | null;
|
||||
title?: string;
|
||||
ensureAssetUrl?: EnsureAssetUrl | null;
|
||||
getDocumentAsset: GetDocumentAsset;
|
||||
|
||||
@@ -173,6 +173,7 @@ interface DesktopWorkspaceViewProps extends Omit<DocumentsViewProps, 'entries' |
|
||||
markLayoutDirty: () => void;
|
||||
onSelect?: (descriptor: unknown, event?: unknown) => void;
|
||||
onPromoteSelection?: (docId: Identifier, event?: unknown) => void;
|
||||
selectionOrderRef: React.MutableRefObject<string[]>;
|
||||
}
|
||||
|
||||
const defaultGetDocumentAsset: GetAsset = () => null;
|
||||
@@ -194,6 +195,7 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
|
||||
clearSelection,
|
||||
handleEntrySelection,
|
||||
promoteSelectionOrder,
|
||||
selectionOrderRef,
|
||||
configureSelectionEnvironment,
|
||||
} = useWorkspaceSelectionContext();
|
||||
const items = useMemo<DeskDocument[]>(
|
||||
@@ -814,6 +816,7 @@ const DesktopWorkspace: React.FC<DocumentsViewProps> = ({
|
||||
recalcVisibleDocIds,
|
||||
dragSettings,
|
||||
markLayoutDirty,
|
||||
selectionOrderRef,
|
||||
};
|
||||
return <DesktopWorkspaceView {...viewProps} />;
|
||||
};
|
||||
@@ -850,13 +853,14 @@ function DesktopWorkspaceView({
|
||||
resolveBaseMetrics,
|
||||
bringToFront,
|
||||
setDraggingId,
|
||||
canvasSize,
|
||||
canvasSize: _canvasSize,
|
||||
openOverlayForDoc,
|
||||
recalcVisibleDocIds,
|
||||
dragSettings,
|
||||
onDocumentActivate,
|
||||
markLayoutDirty,
|
||||
onSelect,
|
||||
selectionOrderRef,
|
||||
}: DesktopWorkspaceViewProps) {
|
||||
const handleDeskDocumentActivate = useCallback(
|
||||
(docId: Identifier) => {
|
||||
@@ -883,19 +887,18 @@ function DesktopWorkspaceView({
|
||||
engine,
|
||||
layoutRef,
|
||||
dragTransformsRef,
|
||||
itemRefs,
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
resolveBaseMetrics,
|
||||
bringToFront,
|
||||
setDraggingId,
|
||||
canvasSize,
|
||||
openOverlayForDoc,
|
||||
recalcVisibleDocIds,
|
||||
settings: dragSettings,
|
||||
containerRef,
|
||||
onDocumentActivate: handleDeskDocumentActivate,
|
||||
markLayoutDirty,
|
||||
selectionOrderRef,
|
||||
selectedDocumentIds,
|
||||
}) as {
|
||||
handlePointerDown: (event: React.PointerEvent<HTMLElement>, docId: Identifier | null, options: PointerDownOptions) => void;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
interface DocumentLike {
|
||||
id?: string;
|
||||
current_version?: unknown;
|
||||
tags?: unknown;
|
||||
}
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
interface AssetLike {
|
||||
id?: string;
|
||||
@@ -17,11 +13,11 @@ interface PreviewMetadataEntry {
|
||||
height: number;
|
||||
}
|
||||
|
||||
type GetDocumentAsset = (doc: DocumentLike, type: string) => AssetLike | null;
|
||||
type GetDocumentAsset = (doc: Document, type: string) => AssetLike | null;
|
||||
type EnsureAssetUrl = (docId: DocumentId, asset: AssetLike, options?: { force?: boolean }) => Promise<AssetLike | null>;
|
||||
|
||||
const usePreviewMetadata = (
|
||||
documents: DocumentLike[] | null,
|
||||
documents: Document[] | null,
|
||||
getDocumentAsset?: GetDocumentAsset,
|
||||
ensureAssetUrl?: EnsureAssetUrl,
|
||||
) => {
|
||||
@@ -37,7 +33,7 @@ const usePreviewMetadata = (
|
||||
};
|
||||
}
|
||||
|
||||
const fetchMetadataForDoc = async (doc: DocumentLike) => {
|
||||
const fetchMetadataForDoc = async (doc: Document) => {
|
||||
if (!doc?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import {
|
||||
} from 'react';
|
||||
import type { PointerEvent as ReactPointerEvent } from 'react';
|
||||
import { preventAll } from './events';
|
||||
import { clamp } from '../utils/math';
|
||||
import usePointerTap from '../ui/usePointerTap';
|
||||
import {
|
||||
applyDomTransform,
|
||||
type WorkspaceEngine,
|
||||
type ActiveDragSession,
|
||||
type DragGroupItem,
|
||||
@@ -18,18 +16,10 @@ import {
|
||||
CARD_BASE_WEIGHT_GRAMS,
|
||||
CARD_PAGE_WEIGHT_GRAMS,
|
||||
} from './workspaceEngine';
|
||||
import { DRAG_HYSTERESIS_SQUARED, EDGE_COLLISION_THRESHOLD } from '../constants/desktop';
|
||||
import type { DocumentId, Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier | null;
|
||||
title?: string;
|
||||
current_version?: {
|
||||
metadata?: { page_count?: number | string | null } | null;
|
||||
} | null;
|
||||
metadata?: { page_count?: number | string | null } | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import { DRAG_HYSTERESIS_SQUARED } from '../constants/desktop';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
import type { Document } from '../types/documents';
|
||||
import { getEntryId, isDocumentEntry } from '../app/entryKey';
|
||||
|
||||
interface DocumentSizeInfo {
|
||||
width: number;
|
||||
@@ -56,10 +46,10 @@ interface DragTransform {
|
||||
|
||||
|
||||
|
||||
type EnsureDocumentSizeFn = (doc: DocumentLike | null) => DocumentSizeInfo | null;
|
||||
type EnsureDocumentSizeFn = (doc: Document | null) => DocumentSizeInfo | null;
|
||||
|
||||
type ResolveBaseMetricsFn = (
|
||||
doc: DocumentLike | null,
|
||||
doc: Document | null,
|
||||
width: number,
|
||||
height: number,
|
||||
) => { baseWidth: number; baseHeight: number; baseScale: number };
|
||||
@@ -81,13 +71,12 @@ interface UseDocumentDragOptions {
|
||||
engine?: WorkspaceEngine | null;
|
||||
layoutRef: MutableRefObject<Map<string, LayoutEntry>>;
|
||||
dragTransformsRef: MutableRefObject<Map<string, DragTransform>>;
|
||||
itemRefs: MutableRefObject<Map<string, HTMLElement | null>>;
|
||||
documentLookup: Map<string, DocumentLike>;
|
||||
selectionOrderRef?: MutableRefObject<string[]>;
|
||||
documentLookup: Map<string, Document>;
|
||||
ensureDocumentSize: EnsureDocumentSizeFn;
|
||||
resolveBaseMetrics: ResolveBaseMetricsFn;
|
||||
bringToFront: (docId: Identifier | null) => void;
|
||||
setDraggingId: (docKey: string | null) => void;
|
||||
canvasSize: { width: number; height: number };
|
||||
openOverlayForDoc?: (
|
||||
docId: Identifier | null,
|
||||
originInfo?: { rotation: number; scale: number; width: number; height: number },
|
||||
@@ -107,8 +96,8 @@ interface DragTapMetadata {
|
||||
docTitle: string;
|
||||
}
|
||||
|
||||
const getDocumentPageCount = (doc?: DocumentLike | null): number | null => {
|
||||
const raw = doc?.current_version?.metadata?.page_count ?? doc?.metadata?.page_count;
|
||||
const getDocumentPageCount = (doc?: Document | null): number | null => {
|
||||
const raw = doc?.current_version?.metadata?.page_count ?? (doc?.metadata as { page_count?: unknown })?.page_count;
|
||||
if (raw == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -116,7 +105,7 @@ const getDocumentPageCount = (doc?: DocumentLike | null): number | null => {
|
||||
return Number.isFinite(value) ? value : null;
|
||||
};
|
||||
|
||||
const computeDocumentMassGrams = (doc?: DocumentLike | null): number => {
|
||||
const computeDocumentMassGrams = (doc?: Document | null): number => {
|
||||
const pages = Math.max(1, Math.round(getDocumentPageCount(doc) ?? 1));
|
||||
return CARD_BASE_WEIGHT_GRAMS + pages * CARD_PAGE_WEIGHT_GRAMS;
|
||||
};
|
||||
@@ -145,19 +134,18 @@ const useDocumentDrag = (options: UseDocumentDragOptions & { selectedDocumentIds
|
||||
engine,
|
||||
layoutRef,
|
||||
dragTransformsRef,
|
||||
itemRefs,
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
resolveBaseMetrics,
|
||||
bringToFront,
|
||||
setDraggingId,
|
||||
canvasSize,
|
||||
openOverlayForDoc,
|
||||
recalcVisibleDocIds,
|
||||
settings,
|
||||
containerRef: providedContainerRef,
|
||||
onDocumentActivate,
|
||||
markLayoutDirty,
|
||||
selectionOrderRef,
|
||||
selectedDocumentIds,
|
||||
} = options;
|
||||
|
||||
@@ -166,8 +154,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions & { selectedDocumentIds
|
||||
|
||||
const {
|
||||
canvasPadding = 24,
|
||||
defaultCanvasWidth = 1024,
|
||||
defaultCanvasHeight = 680,
|
||||
debugDrag = false,
|
||||
} = settings || {};
|
||||
|
||||
@@ -195,21 +181,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions & { selectedDocumentIds
|
||||
const dragStateRef = useRef<ActiveDragSession | null>(null);
|
||||
const pendingDragRef = useRef<PendingDrag | null>(null);
|
||||
|
||||
const setDragTransform = useCallback((docKey: Identifier | null, transform: DragTransform | null) => {
|
||||
if (!docKey) {
|
||||
return;
|
||||
}
|
||||
const map = dragTransformsRef?.current;
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
if (transform) {
|
||||
map.set(String(docKey), transform);
|
||||
} else {
|
||||
map.delete(String(docKey));
|
||||
}
|
||||
}, [dragTransformsRef]);
|
||||
|
||||
const clearDragTransforms = useCallback(() => {
|
||||
const map = dragTransformsRef?.current;
|
||||
if (!map?.clear) {
|
||||
@@ -268,27 +239,19 @@ const useDocumentDrag = (options: UseDocumentDragOptions & { selectedDocumentIds
|
||||
);
|
||||
|
||||
const startDragSession = useCallback((pending: PendingDrag, event: PointerEventLike) => {
|
||||
const { docId: docIdInput, modifierActive, wasSelected } = pending;
|
||||
const { docId: docIdInput, modifierActive } = pending;
|
||||
|
||||
// 1. Get current global selection
|
||||
let selectionIds: string[] = (selectedDocumentIds || []).map(String);
|
||||
const docKey = String(docIdInput);
|
||||
const selectionFromRef: string[] = Array.isArray(selectionOrderRef?.current)
|
||||
? selectionOrderRef.current
|
||||
.map((key) => (isDocumentEntry(key) ? getEntryId(key) : null))
|
||||
.filter((id): id is string => Boolean(id))
|
||||
.map(String)
|
||||
: [];
|
||||
|
||||
// 2. Check if clicked doc was already selected BEFORE the click
|
||||
if (!wasSelected) {
|
||||
// Not selected before click. Determine what SHOULD be dragged.
|
||||
const targets = (pending.stackHits && pending.stackHits.length > 0)
|
||||
? pending.stackHits
|
||||
: [docKey];
|
||||
|
||||
if (modifierActive) {
|
||||
// Add to selection
|
||||
selectionIds = [...selectionIds, ...targets];
|
||||
} else {
|
||||
// Replace selection
|
||||
selectionIds = targets;
|
||||
}
|
||||
}
|
||||
// 1. Get current global selection (prefer ref for immediate updates)
|
||||
let selectionIds: string[] = selectionFromRef.length
|
||||
? selectionFromRef
|
||||
: (selectedDocumentIds || []).map(String);
|
||||
|
||||
// 3. Filter for valid documents
|
||||
selectionIds = selectionIds.filter((id, index, array) => array.indexOf(id) === index && documentLookup.has(id));
|
||||
@@ -472,6 +435,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions & { selectedDocumentIds
|
||||
|
||||
}, [
|
||||
selectedDocumentIds,
|
||||
selectionOrderRef,
|
||||
documentLookup,
|
||||
layoutRef,
|
||||
ensureDocumentSize,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { clamp, formatTransform, toNumber } from '../utils/math';
|
||||
import { clamp, formatTransform } from '../utils/math';
|
||||
import {
|
||||
Point,
|
||||
Polygon,
|
||||
clipPolygon,
|
||||
isPointInsideConvex,
|
||||
polygonCentroid,
|
||||
iterateEdges,
|
||||
forEachVertex,
|
||||
} from './utils/geometry';
|
||||
import { computeCardBounds } from './utils/layoutUtils';
|
||||
import { fetchLayoutRecords, upsertLayoutRecords } from './db';
|
||||
@@ -631,7 +628,7 @@ export class WorkspaceEngine {
|
||||
return this.state.type === 'dragging' ? this.state.session : null;
|
||||
}
|
||||
|
||||
beginDrag(docIds: Array<string | null> = []): void {
|
||||
beginDrag(_docIds: Array<string | null> = []): void {
|
||||
// Legacy method support or internal helper
|
||||
// If we are starting a drag, we should transition state
|
||||
// But this method was used to set flags.
|
||||
|
||||
Reference in New Issue
Block a user