refactor: Introduce dedicated Document and Asset types and migrate codebase from DocumentLike.
This commit is contained in:
@@ -9,13 +9,7 @@ import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
type FolderId = DocumentId | 'root';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: DocumentId;
|
||||
folder_id?: FolderId | null;
|
||||
filename?: string | null;
|
||||
current_version?: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
type DocumentLink = {
|
||||
url?: string;
|
||||
@@ -29,11 +23,11 @@ type NavigateHandler = (path: string, options?: { replace?: boolean }) => void;
|
||||
interface UseDocumentPreviewArgs {
|
||||
routeDocumentId?: DocumentId | null;
|
||||
documentsManager: {
|
||||
getById: (id: DocumentId) => DocumentLike | null;
|
||||
ensure: (id: DocumentId) => Promise<DocumentLike | null>;
|
||||
getMany: (ids: DocumentId[]) => DocumentLike[];
|
||||
getById: (id: DocumentId) => Document | null;
|
||||
ensure: (id: DocumentId) => Promise<Document | null>;
|
||||
getMany: (ids: DocumentId[]) => Document[];
|
||||
subscribe: (listener: () => void) => () => void;
|
||||
ingest: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
|
||||
ingest: (docs: unknown[]) => { canonical: Document[]; changed: boolean };
|
||||
};
|
||||
selectedFolder?: FolderId | null;
|
||||
notifyApiError: (error: unknown, message: string) => void;
|
||||
@@ -50,7 +44,7 @@ interface UseDocumentPreviewArgs {
|
||||
interface UseDocumentPreviewResult {
|
||||
documentLinks: Map<DocumentId, DocumentLink>;
|
||||
ensureDownloadUrl: (documentId: DocumentId, options?: { force?: boolean }) => Promise<DocumentLink | null>;
|
||||
ensurePreviewData: (documentId: DocumentId) => Promise<DocumentLike | null>;
|
||||
ensurePreviewData: (documentId: DocumentId) => Promise<Document | null>;
|
||||
openDocumentPreview: (documentId: DocumentId, options?: { replace?: boolean }) => void;
|
||||
closeDocumentPreview: (folderId?: FolderId) => void;
|
||||
resetPreviewState: () => void;
|
||||
@@ -149,7 +143,7 @@ const useDocumentPreview = ({
|
||||
);
|
||||
|
||||
const ensurePreviewData = useCallback(
|
||||
async (documentId: DocumentId): Promise<DocumentLike | null> => {
|
||||
async (documentId: DocumentId): Promise<Document | null> => {
|
||||
if (!documentId) return null;
|
||||
|
||||
const findInCache = () => documentsManager.getById(documentId);
|
||||
@@ -163,7 +157,7 @@ const useDocumentPreview = ({
|
||||
if (!doc) {
|
||||
const fetched = await fetchDocument(documentId);
|
||||
const { canonical } = documentsManager.ingest([fetched as unknown]);
|
||||
doc = (canonical[0] as DocumentLike | undefined) || null;
|
||||
doc = (canonical[0] as Document | undefined) || null;
|
||||
if (!doc) {
|
||||
throw new Error('Document metadata unavailable.');
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { TAG_FILTER_UNTAGGED } from './workspaceUtils';
|
||||
import { listDocuments } from '../lib/apiClient';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = { id?: Identifier } & Record<string, unknown>;
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
type ApiClient = {
|
||||
get: <T = unknown>(url: string, config?: { params?: Record<string, unknown> }) => Promise<{ data: T }>;
|
||||
@@ -23,7 +23,7 @@ interface UseDocumentsSearchArgs {
|
||||
notifyApiError: (error: unknown, message: string) => void;
|
||||
setSearchIncludeDescendants: (value: boolean) => void;
|
||||
documentsManager: {
|
||||
ingest: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
|
||||
ingest: (docs: unknown[]) => { canonical: Document[]; changed: boolean };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,10 @@
|
||||
import type { Identifier } from './types/identifiers';
|
||||
import type { AssetObject, AssetLike } from './types/assets';
|
||||
import type { DocumentVersion, Document } from './types/documents';
|
||||
|
||||
type Nullable<T> = T | null;
|
||||
|
||||
export interface AssetObject {
|
||||
ordinal?: number;
|
||||
url?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
expires_at?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface AssetLike {
|
||||
id?: Identifier;
|
||||
asset_type?: string;
|
||||
cardinality?: number | null;
|
||||
download?: { url: string; expires_at: number } | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
assets?: Record<string, AssetLike> | AssetLike[] | null;
|
||||
objects?: AssetObject[] | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface DocumentVersionLike {
|
||||
assets?: Record<string, AssetLike> | AssetLike[] | null;
|
||||
metadata?: Record<string, unknown> & { page_count?: number } | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface DocumentLike {
|
||||
id?: Identifier;
|
||||
current_version?: DocumentVersionLike | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
export type { AssetObject, AssetLike, DocumentVersion as DocumentVersionLike, Document };
|
||||
|
||||
export const resolveAssetExpiresAt = (asset?: { download?: { expires_at: number } | null } | null): number | null =>
|
||||
asset?.download?.expires_at ?? null;
|
||||
@@ -45,7 +18,7 @@ export type EnsureAssetUrl = (
|
||||
options?: { force?: boolean;[key: string]: unknown },
|
||||
) => Promise<unknown>;
|
||||
|
||||
export type GetAsset = (document: DocumentLike, assetType: string) => Nullable<AssetLike>;
|
||||
export type GetAsset = (document: Document, assetType: string) => Nullable<AssetLike>;
|
||||
|
||||
export const getAssetFromGroup = (
|
||||
assets?: AssetLike[] | Record<string, AssetLike> | null,
|
||||
@@ -62,7 +35,7 @@ export const getAssetFromGroup = (
|
||||
return assets?.[assetType] || null;
|
||||
};
|
||||
|
||||
export const getAssetFromVersion = (currentVersion: Nullable<DocumentVersionLike>, assetType: string) => {
|
||||
export const getAssetFromVersion = (currentVersion: Nullable<DocumentVersion>, assetType: string) => {
|
||||
if (!currentVersion) {
|
||||
return null;
|
||||
}
|
||||
@@ -162,7 +135,7 @@ export class AssetView {
|
||||
export const createAssetView = (asset?: AssetLike | null): AssetView => new AssetView(asset);
|
||||
|
||||
export const resolveDocumentAssetUrl = (
|
||||
doc: Nullable<DocumentLike>,
|
||||
doc: Nullable<Document>,
|
||||
type: string,
|
||||
{
|
||||
ensureAssetUrl,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,17 +3,12 @@ import { createPortal } from 'react-dom';
|
||||
import { clamp } from '../utils/math';
|
||||
import PdfViewer from '../preview/PdfViewer';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: string;
|
||||
title?: string;
|
||||
mime_type?: string | null;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface PreviewZoomOverlayProps {
|
||||
open?: boolean;
|
||||
onClose?: () => void;
|
||||
document?: DocumentLike | null;
|
||||
document?: Document | null;
|
||||
}
|
||||
|
||||
type NaturalSize = { width: number | null; height: number | null };
|
||||
@@ -26,7 +21,7 @@ type DocumentLink = {
|
||||
mimeType?: string | null;
|
||||
};
|
||||
|
||||
type DocumentLikeWithPreview = DocumentLike & { documentLink?: DocumentLink };
|
||||
type DocumentWithPreview = Document & { documentLink?: DocumentLink };
|
||||
|
||||
const determineDisplayKind = (entry?: DocumentLink | null): DisplayKind => {
|
||||
const type = entry?.mimeType?.toLowerCase?.() || '';
|
||||
@@ -55,7 +50,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||
const [naturalSize, setNaturalSize] = useState<NaturalSize>({ width: null, height: null });
|
||||
const [renderBackdrop, setRenderBackdrop] = useState(false);
|
||||
const [isBackdropVisible, setBackdropVisible] = useState(false);
|
||||
const [documentSnapshot, setDocumentSnapshot] = useState<DocumentLikeWithPreview | null>(null);
|
||||
const [documentSnapshot, setDocumentSnapshot] = useState<DocumentWithPreview | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const mediaRef = useRef<HTMLImageElement | null>(null);
|
||||
const focusRef = useRef<FocusPoint>(null);
|
||||
@@ -63,7 +58,7 @@ const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||
const visibilityTimerRef = useRef<number | null>(null);
|
||||
const displayTimerRef = useRef<number | null>(null);
|
||||
|
||||
const currentDocument = overlayDocument as DocumentLikeWithPreview | null;
|
||||
const currentDocument = overlayDocument;
|
||||
|
||||
useEffect(() => {
|
||||
if (currentDocument?.documentLink?.url) {
|
||||
|
||||
@@ -7,13 +7,7 @@ import { getEntryId, isDocumentEntry } from '../app/entryKey';
|
||||
import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel';
|
||||
import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier;
|
||||
folder_id?: Identifier | 'root';
|
||||
title?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface FolderNode {
|
||||
id: Identifier | 'root';
|
||||
@@ -22,10 +16,10 @@ interface FolderNode {
|
||||
}
|
||||
|
||||
interface UseDetailWorkspaceArgs {
|
||||
documents: DocumentLike[];
|
||||
documents: Document[];
|
||||
selectionOrder: string[];
|
||||
selectedDocumentIds: Identifier[];
|
||||
documentLookup: Map<Identifier, DocumentLike>;
|
||||
documentLookup: Map<Identifier, Document>;
|
||||
folderNodes: Map<Identifier | 'root', FolderNode>;
|
||||
ensureFolderData: (folderId: Identifier | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
|
||||
detailPanelControlRef: MutableRefObject<{ open?: (args?: { documentIds?: Identifier[] }) => void; close?: () => void } | null>;
|
||||
@@ -35,7 +29,7 @@ interface UseDetailWorkspaceArgs {
|
||||
openDocumentPreview?: (args: { documentIds: Identifier[] }) => void;
|
||||
handleDocumentTitleUpdate?: (docId: Identifier, title: string) => Promise<boolean> | boolean;
|
||||
handleDocumentIssuedUpdate?: (docId: Identifier, issued: number | null) => Promise<boolean> | boolean;
|
||||
handleDocumentTagAdd?: (doc: DocumentLike, value: string, context?: { option?: unknown }) => void;
|
||||
handleDocumentTagAdd?: (doc: Document, value: string, context?: { option?: unknown }) => void;
|
||||
handleTagRemove?: (...args: unknown[]) => void;
|
||||
ensureAssetUrl?: EnsureAssetUrl;
|
||||
getDocumentAsset?: GetDocumentAsset;
|
||||
@@ -55,8 +49,8 @@ interface UseDetailWorkspaceResult {
|
||||
handleDetailPanelClose: () => void;
|
||||
inspectDocument: (docId: Identifier | null) => void;
|
||||
previewActive: boolean;
|
||||
previewWorkspaceDocument: DocumentLike | null;
|
||||
resolveThumbnailUrlForDoc: (doc: DocumentLike | null) => string | null;
|
||||
previewWorkspaceDocument: Document | null;
|
||||
resolveThumbnailUrlForDoc: (doc: Document | null) => string | null;
|
||||
resolveFolderPath: (folderId?: Identifier | 'root') => Array<{ id: Identifier | 'root'; name: string }>;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,16 +28,7 @@ interface CorrespondentEntry {
|
||||
count?: number;
|
||||
}
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier;
|
||||
title?: string;
|
||||
issued_at?: string | null;
|
||||
folder_id?: FolderId | null;
|
||||
current_version?: { version_number?: number } | null;
|
||||
tags?: TagEntry[];
|
||||
correspondents?: CorrespondentEntry[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface TagSectionProps {
|
||||
tags?: TagEntry[];
|
||||
@@ -62,14 +53,14 @@ interface CorrespondentSectionProps {
|
||||
}
|
||||
|
||||
export interface DocumentSummarySectionProps {
|
||||
document?: DocumentLike | null;
|
||||
document?: Document | null;
|
||||
tagLookupById?: Map<TagId, TagEntry>;
|
||||
tagOptions?: SelectionAssignmentMenuItem[];
|
||||
onTagAdd?: (doc: DocumentLike, value: string, context?: { option?: unknown }) => void;
|
||||
onTagAdd?: (doc: Document, value: string, context?: { option?: unknown }) => void;
|
||||
onTagRemove?: (docId: Identifier | undefined, tagId: TagId | undefined) => void;
|
||||
correspondents?: CorrespondentEntry[];
|
||||
correspondentOptions?: SelectionAssignmentMenuItem[];
|
||||
onCorrespondentAdd?: (payload: { document: DocumentLike; name: string; option?: unknown }) => void;
|
||||
onCorrespondentAdd?: (payload: { document: Document; name: string; option?: unknown }) => void;
|
||||
onCorrespondentRemove?: (payload: { documentId: Identifier | undefined; correspondentId: Identifier | undefined }) => void;
|
||||
onUpdateTitle?: (docId: Identifier | undefined, title: string) => Promise<boolean> | boolean;
|
||||
onUpdateIssued?: (docId: Identifier | undefined, timestamp: number | null) => Promise<boolean> | boolean;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { CSSProperties, JSX, MutableRefObject } from 'react';
|
||||
import type { Document } from '../types/documents';
|
||||
import {
|
||||
getAssetFromVersion,
|
||||
resolveDocumentAssetUrl,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
} from '../asset_manager';
|
||||
import { DEFAULT_THUMBNAIL_SIZE } from '../constants/documents';
|
||||
import type {
|
||||
DocumentLike as AssetManagerDocumentLike,
|
||||
AssetLike as AssetManagerAssetLike,
|
||||
EnsureAssetUrl as AssetManagerEnsureAssetUrl,
|
||||
GetAsset as AssetManagerGetAsset,
|
||||
@@ -65,18 +65,19 @@ const useLazyVisibility = (
|
||||
return { ref: targetRef, isVisible };
|
||||
};
|
||||
|
||||
const getPageCount = (doc?: DocumentLike | null) => {
|
||||
|
||||
|
||||
const getPageCount = (doc?: Document | null) => {
|
||||
const count = doc?.current_version?.metadata?.page_count;
|
||||
return Number.isFinite(count) ? Number(count) : null;
|
||||
};
|
||||
|
||||
type DocumentLike = AssetManagerDocumentLike;
|
||||
type AssetLike = AssetManagerAssetLike;
|
||||
type EnsureAssetUrl = AssetManagerEnsureAssetUrl;
|
||||
type GetDocumentAsset = AssetManagerGetAsset;
|
||||
|
||||
interface DocumentThumbnailImageProps {
|
||||
document?: DocumentLike | null;
|
||||
document?: Document | null;
|
||||
ensureAssetUrl?: EnsureAssetUrl;
|
||||
getDocumentAsset?: GetDocumentAsset;
|
||||
alt?: string;
|
||||
|
||||
@@ -9,9 +9,9 @@ import useInlineRename from './useInlineRename';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
|
||||
import type {
|
||||
FolderLike,
|
||||
DocumentLike,
|
||||
} from './DocumentsList';
|
||||
Folder as FolderLike,
|
||||
Document,
|
||||
} from '../types/documents';
|
||||
import type { DocumentsViewProps } from './panel/DocumentsPanel';
|
||||
|
||||
interface DocumentsGridProps extends DocumentsViewProps {
|
||||
@@ -63,9 +63,9 @@ const DocumentsGrid: React.FC<DocumentsGridProps> = ({
|
||||
submitEditing: submitDocumentEditing,
|
||||
savingId: savingDocumentId,
|
||||
attachInputRef: attachDocumentInputRef,
|
||||
} = useInlineRename<DocumentLike>(onDocumentRename, {
|
||||
getCurrentValue: (doc: DocumentLike) => doc?.title ?? '',
|
||||
getEntityId: (doc: DocumentLike) => doc?.id ?? null,
|
||||
} = useInlineRename<Document>(onDocumentRename, {
|
||||
getCurrentValue: (doc: Document) => doc?.title ?? '',
|
||||
getEntityId: (doc: Document) => doc?.id ?? null,
|
||||
});
|
||||
|
||||
const {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { FolderIcon, CheckIcon, CloseIcon } from '../ui/icons';
|
||||
import { getTagColorStyle } from '../utils/colors';
|
||||
import { formatDate } from '../utils/date';
|
||||
@@ -9,54 +8,12 @@ import { resolveCorrespondents } from './correspondents';
|
||||
import { writeTagTransferData, parseTagTransferPayload } from './tagTransfer';
|
||||
import useInlineRename from './useInlineRename';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
import type { DocumentsViewProps } from './panel/DocumentsPanel';
|
||||
|
||||
export interface FolderLike {
|
||||
id?: Identifier | 'root';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface DocumentTag {
|
||||
id?: Identifier;
|
||||
label?: string;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export interface DocumentCorrespondent {
|
||||
id?: Identifier;
|
||||
name?: string;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface DocumentLike {
|
||||
id?: Identifier;
|
||||
title?: string;
|
||||
issued_at?: string | null;
|
||||
created_at?: string | null;
|
||||
uploaded_at?: string | null;
|
||||
tags?: DocumentTag[] | null;
|
||||
correspondents?: DocumentCorrespondent[] | null;
|
||||
}
|
||||
|
||||
export type FolderEntry = {
|
||||
type: 'folder';
|
||||
id: Identifier | 'root';
|
||||
key: string;
|
||||
folder: FolderLike;
|
||||
};
|
||||
|
||||
export type DocumentEntry = {
|
||||
type: 'document';
|
||||
id: Identifier;
|
||||
key: string;
|
||||
document: DocumentLike;
|
||||
};
|
||||
|
||||
export type DocumentsListEntry = FolderEntry | DocumentEntry;
|
||||
|
||||
export type FolderEventHandler = (folder: FolderLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
||||
export type DocumentEventHandler = (document: DocumentLike, event: MouseEvent<HTMLTableRowElement>) => void;
|
||||
import type {
|
||||
Document,
|
||||
Folder,
|
||||
} from '../types/documents';
|
||||
|
||||
const DocumentsList: React.FC<DocumentsViewProps> = ({
|
||||
entries,
|
||||
@@ -105,9 +62,9 @@ const DocumentsList: React.FC<DocumentsViewProps> = ({
|
||||
submitEditing: submitDocumentEditing,
|
||||
savingId: savingDocumentId,
|
||||
attachInputRef: attachDocumentInputRef,
|
||||
} = useInlineRename<DocumentLike>(onDocumentRename, {
|
||||
getCurrentValue: (doc: DocumentLike) => doc?.title ?? '',
|
||||
getEntityId: (doc: DocumentLike) => doc?.id ?? null,
|
||||
} = useInlineRename<Document>(onDocumentRename, {
|
||||
getCurrentValue: (doc: Document) => doc?.title ?? '',
|
||||
getEntityId: (doc: Document) => doc?.id ?? null,
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -119,9 +76,9 @@ const DocumentsList: React.FC<DocumentsViewProps> = ({
|
||||
submitEditing: submitFolderEditing,
|
||||
savingId: savingFolderId,
|
||||
attachInputRef: attachFolderInputRef,
|
||||
} = useInlineRename<FolderLike>(onFolderRename, {
|
||||
getCurrentValue: (folder: FolderLike) => folder?.name ?? '',
|
||||
getEntityId: (folder: FolderLike) => folder?.id ?? null,
|
||||
} = useInlineRename<Folder>(onFolderRename, {
|
||||
getCurrentValue: (folder: Folder) => folder?.name ?? '',
|
||||
getEntityId: (folder: Folder) => folder?.id ?? null,
|
||||
});
|
||||
|
||||
|
||||
@@ -310,7 +267,6 @@ const DocumentsList: React.FC<DocumentsViewProps> = ({
|
||||
data-doc-id={doc.id}
|
||||
onClick={(event) => onDocumentClick?.(doc, event)}
|
||||
onDoubleClick={(event) => onDocumentActivate?.(doc, event)}
|
||||
draggable
|
||||
onDragStart={(event) => onDocumentDragStart?.(event, doc)}
|
||||
onDragEnd={(event) => onDocumentDragEnd?.(event)}
|
||||
onDragOver={onDocumentTagDragOver}
|
||||
|
||||
@@ -40,12 +40,7 @@ interface CorrespondentOption {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId;
|
||||
tags?: TagOption[];
|
||||
correspondents?: CorrespondentOption[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface BulkTagMutationArgs {
|
||||
label: string;
|
||||
@@ -68,7 +63,7 @@ export interface SelectionFloatingActionsProps {
|
||||
selectionCount?: number;
|
||||
selectedDocumentIds?: SelectedIdList;
|
||||
selectedFolderIds?: SelectedIdList;
|
||||
documentLookup?: Map<DocumentId, DocumentLike> | null;
|
||||
documentLookup?: Map<DocumentId, Document> | null;
|
||||
tags?: TagOption[] | null;
|
||||
tagLookupById?: Map<DocumentId, TagOption> | null;
|
||||
correspondents?: CorrespondentOption[] | null;
|
||||
@@ -135,7 +130,7 @@ const buildFolderTreeOptions = (tree?: FolderTreeNode[] | null): SelectionAssign
|
||||
};
|
||||
|
||||
const buildTagAssignments = (
|
||||
selectedDocuments: DocumentLike[],
|
||||
selectedDocuments: Document[],
|
||||
tagLookupById: Map<DocumentId, TagOption> | null,
|
||||
tags: TagOption[] | null,
|
||||
total: number,
|
||||
@@ -200,7 +195,7 @@ const buildTagAssignments = (
|
||||
};
|
||||
|
||||
const buildCorrespondentAssignments = (
|
||||
selectedDocuments: DocumentLike[],
|
||||
selectedDocuments: Document[],
|
||||
correspondents: CorrespondentOption[] | null,
|
||||
total: number,
|
||||
): SelectionAssignmentMenuItem[] => {
|
||||
@@ -280,7 +275,7 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
||||
const tenantId = tenant?.id ?? null;
|
||||
|
||||
const documentLookupMap = useMemo(() => (
|
||||
documentLookup instanceof Map ? documentLookup : new Map<DocumentId, DocumentLike>()
|
||||
documentLookup instanceof Map ? documentLookup : new Map<DocumentId, Document>()
|
||||
), [documentLookup]);
|
||||
const tagLookupMap = tagLookupById instanceof Map ? tagLookupById : null;
|
||||
|
||||
@@ -350,13 +345,13 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
||||
const folderCount = folderIdList.length;
|
||||
const totalCount = selectionCount ?? documentCount + folderCount;
|
||||
|
||||
const selectedDocuments = useMemo<DocumentLike[]>(() => {
|
||||
const selectedDocuments = useMemo<Document[]>(() => {
|
||||
if (!documentIdList.length || !(documentLookupMap instanceof Map)) {
|
||||
return [];
|
||||
}
|
||||
return documentIdList
|
||||
.map((id) => documentLookupMap.get(id))
|
||||
.filter((doc): doc is DocumentLike => Boolean(doc));
|
||||
.filter((doc): doc is Document => Boolean(doc));
|
||||
}, [documentIdList, documentLookupMap]);
|
||||
|
||||
const selectedDocCount = selectedDocuments.length;
|
||||
|
||||
@@ -4,9 +4,7 @@ export interface CorrespondentReference {
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export interface DocumentLike {
|
||||
correspondents?: CorrespondentReference[];
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
export interface ResolvedCorrespondent {
|
||||
id?: string | null;
|
||||
@@ -14,7 +12,7 @@ export interface ResolvedCorrespondent {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export const resolveCorrespondents = (doc?: DocumentLike | null): ResolvedCorrespondent[] => {
|
||||
export const resolveCorrespondents = (doc?: Document | null): ResolvedCorrespondent[] => {
|
||||
if (!doc || !Array.isArray(doc.correspondents)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@ import type {
|
||||
EnsureAssetUrl,
|
||||
EnsurePreviewData,
|
||||
GetDocumentAsset,
|
||||
DocumentLike as OcrDocumentLike,
|
||||
} from '../utils/ocr';
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
export type DocumentLike = OcrDocumentLike;
|
||||
export type { Document };
|
||||
|
||||
const asyncFalse = async () => false;
|
||||
|
||||
const resolveDocumentDownloadHref = (document?: DocumentLike | null): string | null => {
|
||||
const resolveDocumentDownloadHref = (document?: Document | null): string | null => {
|
||||
if (!document) {
|
||||
return null;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ const resolveDocumentDownloadHref = (document?: DocumentLike | null): string | n
|
||||
return downloadUrl;
|
||||
};
|
||||
|
||||
const hasDocumentOcrAsset = (document?: DocumentLike | null, getDocumentAsset?: GetDocumentAsset | null): boolean => {
|
||||
const hasDocumentOcrAsset = (document?: Document | null, getDocumentAsset?: GetDocumentAsset | null): boolean => {
|
||||
if (!document || !getDocumentAsset) {
|
||||
return false;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ const hasDocumentOcrAsset = (document?: DocumentLike | null, getDocumentAsset?:
|
||||
};
|
||||
|
||||
interface CreateDocumentActionStateArgs {
|
||||
document: DocumentLike | null;
|
||||
document: Document | null;
|
||||
ensurePreviewData: EnsurePreviewData;
|
||||
ensureAssetUrl: EnsureAssetUrl;
|
||||
getDocumentAsset?: GetDocumentAsset | null;
|
||||
|
||||
@@ -2,39 +2,7 @@ import { formatFileSize } from '../utils/format';
|
||||
import { formatDateTime as defaultFormatDateTime } from '../utils/date';
|
||||
import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
|
||||
|
||||
interface DocumentPageMetadata {
|
||||
page_count?: number | string | null;
|
||||
}
|
||||
|
||||
interface DocumentVersion {
|
||||
size_bytes?: number | string | null;
|
||||
metadata?: DocumentPageMetadata | null;
|
||||
checksum?: string | null;
|
||||
}
|
||||
|
||||
interface TagEntry {
|
||||
label?: string | null;
|
||||
}
|
||||
|
||||
interface CorrespondentEntry {
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface SummaryDocument {
|
||||
title?: string | null;
|
||||
original_name?: string | null;
|
||||
filename?: string | null;
|
||||
mime_type?: string | null;
|
||||
folder_id?: string | null;
|
||||
folder_name?: string;
|
||||
current_version?: DocumentVersion | null;
|
||||
created_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
issued_at?: string | null;
|
||||
folder_path?: string;
|
||||
tags?: TagEntry[] | null;
|
||||
correspondents?: CorrespondentEntry[] | null;
|
||||
}
|
||||
import type { DocumentTag, DocumentCorrespondent, Document } from '../types/documents';
|
||||
|
||||
interface DescribeSummaryOptions {
|
||||
formatDateTime?: typeof defaultFormatDateTime;
|
||||
@@ -51,7 +19,7 @@ export interface DocumentSummaryRow {
|
||||
|
||||
export type DocumentSummary = DocumentSummaryRow[];
|
||||
|
||||
const coercePageCount = (metadata?: DocumentPageMetadata | null): number | null => {
|
||||
const coercePageCount = (metadata?: { page_count?: number | string | null } | null): number | null => {
|
||||
const raw = metadata?.page_count;
|
||||
if (raw == null || raw === '') {
|
||||
return null;
|
||||
@@ -67,31 +35,26 @@ interface DocumentMetadataPayload {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface MetadataDocumentLike {
|
||||
created_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
filename?: string | null;
|
||||
original_name?: string | null;
|
||||
mime_type?: string | null;
|
||||
metadata?: DocumentMetadataPayload | null;
|
||||
current_version?: { checksum?: string | null } | null;
|
||||
}
|
||||
|
||||
export const describeDocumentSummary = (document?: SummaryDocument | null, options: DescribeSummaryOptions = {}): DocumentSummary => {
|
||||
export const describeDocumentSummary = (document?: Document | null, options: DescribeSummaryOptions = {}): DocumentSummary => {
|
||||
const {
|
||||
formatDateTime = defaultFormatDateTime,
|
||||
} = options;
|
||||
|
||||
const formatDateLabel = (value?: string | null) => formatDateTime(value) || '—';
|
||||
const doc = document ?? {};
|
||||
const formatDateLabel = (value?: string | number | null) => {
|
||||
if (typeof value === 'number') {
|
||||
return formatDateTime(new Date(value)) || '—';
|
||||
}
|
||||
return formatDateTime(value) || '—';
|
||||
};
|
||||
const doc = document ?? ({} as Document);
|
||||
const sizeBytes = Number(doc.current_version?.size_bytes);
|
||||
const sizeLabel = Number.isFinite(sizeBytes) && sizeBytes > 0 ? formatFileSize(sizeBytes) : '—';
|
||||
const metadata = doc.current_version?.metadata || null;
|
||||
const pageCount = coercePageCount(metadata);
|
||||
const pageCountLabel = Number.isFinite(pageCount) ? String(pageCount) : '—';
|
||||
const folderLabel = doc.folder_id == null ? DEFAULT_FOLDER_NAME : `Folder ${doc.folder_id}`;
|
||||
const tags = sanitizeArray<TagEntry>(doc.tags);
|
||||
const correspondents = sanitizeArray<CorrespondentEntry>(doc.correspondents);
|
||||
const tags = sanitizeArray<DocumentTag>(doc.tags);
|
||||
const correspondents = sanitizeArray<DocumentCorrespondent>(doc.correspondents);
|
||||
const tagLabels = tags.map((tag) => tag.label).filter(Boolean) as string[];
|
||||
const correspondentLabels = correspondents.map((entry) => entry.name).filter(Boolean) as string[];
|
||||
const tagsSummary = tagLabels.length ? tagLabels.join(', ') : '—';
|
||||
@@ -113,13 +76,14 @@ export const describeDocumentSummary = (document?: SummaryDocument | null, optio
|
||||
];
|
||||
};
|
||||
|
||||
export const extractDocumentMetadataPayload = (document?: MetadataDocumentLike | null): DocumentMetadataPayload | null => {
|
||||
if (!document?.metadata) {
|
||||
export const extractDocumentMetadataPayload = (document?: Document | null): DocumentMetadataPayload | null => {
|
||||
const metadata = document?.['metadata'] as DocumentMetadataPayload | undefined;
|
||||
if (!metadata) {
|
||||
return null;
|
||||
}
|
||||
const keys = Object.keys(document.metadata);
|
||||
const keys = Object.keys(metadata);
|
||||
if (!keys.length) {
|
||||
return null;
|
||||
}
|
||||
return document.metadata;
|
||||
return metadata;
|
||||
};
|
||||
|
||||
@@ -6,9 +6,9 @@ import type {
|
||||
DocumentsListEntry,
|
||||
FolderEventHandler,
|
||||
DocumentEventHandler,
|
||||
DocumentLike,
|
||||
Document,
|
||||
DocumentTag,
|
||||
} from '../DocumentsList';
|
||||
} from '../../types/documents';
|
||||
import DesktopWorkspace from '../../desktop/DesktopWorkspace';
|
||||
import { isTagTransferEvent } from '../tagTransfer';
|
||||
import PreviewZoomOverlay from '../../detail/PreviewZoomOverlay';
|
||||
@@ -64,7 +64,7 @@ export interface DocumentsViewProps {
|
||||
onFolderRename?: (folderId: Identifier | 'root', nextName: string) => Promise<boolean> | boolean;
|
||||
onDocumentClick?: DocumentEventHandler;
|
||||
onDocumentActivate?: DocumentEventHandler;
|
||||
onDocumentDragStart?: (event: DragEvent<HTMLElement>, document: DocumentLike) => void;
|
||||
onDocumentDragStart?: (event: DragEvent<HTMLElement>, document: Document) => void;
|
||||
onDocumentDragEnd?: (event: DragEvent<HTMLElement>) => void;
|
||||
onDocumentTagDragOver?: (event: DragEvent<HTMLElement>) => void;
|
||||
onDocumentTagDragLeave?: (event: DragEvent<HTMLElement>) => void;
|
||||
|
||||
@@ -6,11 +6,7 @@ import type { FolderId, Identifier } from '../../types/identifiers';
|
||||
type FolderIdentifier = FolderId | 'root';
|
||||
type FolderInput = FolderIdentifier | number;
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier | null;
|
||||
title?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
type ApplySelectionFn = (
|
||||
keys: string[],
|
||||
@@ -28,7 +24,7 @@ interface UseDocumentDragHandlersOptions {
|
||||
selectedFolderIds: FolderInput[];
|
||||
applySelection: ApplySelectionFn;
|
||||
handleEntrySelection: HandleEntrySelectionFn;
|
||||
documentLookup: Map<Identifier, DocumentLike>;
|
||||
documentLookup: Map<Identifier, Document>;
|
||||
setDraggedDocumentIds: (ids: Identifier[] | []) => void;
|
||||
setDraggedFolderId: (id: FolderIdentifier | null) => void;
|
||||
documentsViewMode: string;
|
||||
@@ -62,7 +58,7 @@ const useDocumentDragHandlers = ({
|
||||
useEffect(() => destroyDragPreview, [destroyDragPreview]);
|
||||
|
||||
const createDragPreview = useCallback(
|
||||
({ documents = [], folders = [] }: { documents?: DocumentLike[]; folders?: FolderIdentifier[] } = {}) => {
|
||||
({ documents = [], folders = [] }: { documents?: Document[]; folders?: FolderIdentifier[] } = {}) => {
|
||||
destroyDragPreview();
|
||||
|
||||
const docEntries = (documents || []).filter(Boolean);
|
||||
@@ -202,9 +198,9 @@ const useDocumentDragHandlers = ({
|
||||
);
|
||||
|
||||
const handleDocumentDragStart = useCallback(
|
||||
(event: DragEvent<HTMLElement>, documentOrId: DocumentLike | Identifier | null) => {
|
||||
(event: DragEvent<HTMLElement>, documentOrId: Document | Identifier | null) => {
|
||||
const documentId: Identifier | null = Object(documentOrId) === documentOrId
|
||||
? (documentOrId as DocumentLike)?.id ?? null
|
||||
? (documentOrId as Document)?.id ?? null
|
||||
: (documentOrId as Identifier | null);
|
||||
if (!documentId) {
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
updateDocument,
|
||||
} from '../../lib/apiClient';
|
||||
import type { DocumentId, FolderId as FolderIdentifier } from '../../types/identifiers';
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
type NullableFolderId = FolderId | null;
|
||||
@@ -21,8 +22,8 @@ type NullableFolderId = FolderId | null;
|
||||
type StatusLevel = 'success' | 'error' | 'info' | string;
|
||||
|
||||
type DocumentCacheMapper = (
|
||||
doc: DocumentLike | null,
|
||||
) => DocumentLike | null;
|
||||
doc: Document | null,
|
||||
) => Document | null;
|
||||
|
||||
type MapDocumentCaches = (mapper: DocumentCacheMapper) => void;
|
||||
|
||||
@@ -53,19 +54,8 @@ interface Tag {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId;
|
||||
folder_id?: NullableFolderId;
|
||||
folder_path?: string | null;
|
||||
folder_name?: string | null;
|
||||
issued_at?: number | null;
|
||||
title?: string;
|
||||
tags?: Tag[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface FolderContents {
|
||||
documents?: DocumentLike[];
|
||||
documents?: Document[];
|
||||
subfolders?: Array<{ id?: FolderId;[key: string]: unknown }>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -109,12 +99,12 @@ interface FolderDeleteOptions {
|
||||
|
||||
interface UseDocumentMutationsArgs {
|
||||
token?: string | null;
|
||||
documentLookup: Map<DocumentId, DocumentLike>;
|
||||
documentLookup: Map<DocumentId, Document>;
|
||||
folderLabelMap: Map<FolderId, string>;
|
||||
ensureFolderData: EnsureFolderData;
|
||||
selectedFolder: FolderId;
|
||||
setSelectedFolder: Dispatch<SetStateAction<FolderId>>;
|
||||
setDocuments: Dispatch<SetStateAction<DocumentLike[]>>;
|
||||
setDocuments: Dispatch<SetStateAction<Document[]>>;
|
||||
setFolderContents: Dispatch<SetStateAction<Map<FolderId, FolderContents>>>;
|
||||
setSearchResultIds: Dispatch<SetStateAction<DocumentId[] | null>>;
|
||||
setSelectedEntries: Dispatch<SetStateAction<string[]>>;
|
||||
@@ -140,13 +130,13 @@ interface UseDocumentMutationsArgs {
|
||||
tags: Tag[];
|
||||
refreshTags: () => Promise<void>;
|
||||
tagManager: TagManager;
|
||||
extractDocumentFromResponse?: (payload: unknown) => DocumentLike | null;
|
||||
ingestDocuments?: (docs: unknown[]) => { canonical: DocumentLike[]; changed: boolean };
|
||||
extractDocumentFromResponse?: (payload: unknown) => Document | null;
|
||||
ingestDocuments?: (docs: unknown[]) => { canonical: Document[]; changed: boolean };
|
||||
}
|
||||
|
||||
interface UseDocumentMutationsResult {
|
||||
moveDocumentsToFolder: (
|
||||
documentIds: Array<DocumentId | DocumentLike>,
|
||||
documentIds: Array<DocumentId | Document>,
|
||||
targetFolderId?: NullableFolderId,
|
||||
) => Promise<void>;
|
||||
handleThumbnailRegeneration: (documentId: DocumentId) => Promise<void>;
|
||||
@@ -155,7 +145,7 @@ interface UseDocumentMutationsResult {
|
||||
options?: DeleteOptions,
|
||||
) => Promise<boolean>;
|
||||
handleDocumentTagAdd: (
|
||||
document: DocumentLike,
|
||||
document: Document,
|
||||
label: string,
|
||||
extras?: DocumentTagExtras | null,
|
||||
) => Promise<void>;
|
||||
@@ -218,7 +208,7 @@ const useDocumentMutations = ({
|
||||
ingestDocuments,
|
||||
}: UseDocumentMutationsArgs): UseDocumentMutationsResult => {
|
||||
const moveDocumentsToFolder = useCallback(
|
||||
async (documentIds: Array<DocumentId | DocumentLike>, targetFolderId?: NullableFolderId) => {
|
||||
async (documentIds: Array<DocumentId | Document>, targetFolderId?: NullableFolderId) => {
|
||||
const uniqueIds = Array.from(
|
||||
new Set((documentIds || []).map((value) => normalizeDocumentId(value)).filter(Boolean) as DocumentId[]),
|
||||
);
|
||||
@@ -241,9 +231,9 @@ const useDocumentMutations = ({
|
||||
document: doc,
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as Array<{ id: DocumentId; sourceFolderId: NullableFolderId; document: DocumentLike }>;
|
||||
.filter(Boolean) as Array<{ id: DocumentId; sourceFolderId: NullableFolderId; document: Document }>;
|
||||
|
||||
const updatedDocsMap = new Map<DocumentId, DocumentLike>();
|
||||
const updatedDocsMap = new Map<DocumentId, Document>();
|
||||
const resolveTargetName = () => {
|
||||
if (!targetLabel) {
|
||||
return null;
|
||||
@@ -257,7 +247,7 @@ const useDocumentMutations = ({
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
const updated: DocumentLike = {
|
||||
const updated: Document = {
|
||||
...document,
|
||||
folder_id: target,
|
||||
};
|
||||
@@ -572,7 +562,7 @@ const useDocumentMutations = ({
|
||||
);
|
||||
|
||||
const handleDocumentTagAdd = useCallback(
|
||||
async (document: DocumentLike, label: string, extras: DocumentTagExtras | null = null) => {
|
||||
async (document: Document, label: string, extras: DocumentTagExtras | null = null) => {
|
||||
const normalizedLabel = tagManager.normalizeLabel(label);
|
||||
const optionCandidate = extras?.option ?? null;
|
||||
const input = extras?.input ?? null;
|
||||
|
||||
@@ -8,20 +8,16 @@ import {
|
||||
} from 'react';
|
||||
import DocumentsManager from '../../documents/DocumentsManager';
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
interface FolderContentsEntry {
|
||||
documents?: DocumentLike[];
|
||||
documents?: Document[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface UseDocumentsOptions {
|
||||
setFolderContents: Dispatch<SetStateAction<Map<string, FolderContentsEntry>>>;
|
||||
fetchDocumentById?: (id: DocumentId) => Promise<DocumentLike | null>;
|
||||
fetchDocumentById?: (id: DocumentId) => Promise<Document | null>;
|
||||
}
|
||||
|
||||
const useDocuments = ({
|
||||
@@ -29,16 +25,16 @@ const useDocuments = ({
|
||||
fetchDocumentById,
|
||||
}: UseDocumentsOptions) => {
|
||||
const managerRef = useRef(
|
||||
new DocumentsManager<DocumentLike>(fetchDocumentById),
|
||||
new DocumentsManager<Document>(fetchDocumentById),
|
||||
);
|
||||
const [documents, setDocumentsState] = useState<DocumentLike[]>([]);
|
||||
const [documents, setDocumentsState] = useState<Document[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
managerRef.current.setFetcher(fetchDocumentById);
|
||||
}, [fetchDocumentById]);
|
||||
|
||||
const setDocuments = useCallback(
|
||||
(value: DocumentLike[] | ((prev: DocumentLike[]) => DocumentLike[])) => {
|
||||
(value: Document[] | ((prev: Document[]) => Document[])) => {
|
||||
setDocumentsState((prev) => {
|
||||
const resolved = typeof value === 'function' ? value(prev) : value;
|
||||
if (!Array.isArray(resolved)) {
|
||||
@@ -52,7 +48,7 @@ const useDocuments = ({
|
||||
);
|
||||
|
||||
const mapDocumentCaches = useCallback(
|
||||
(mapper: (doc: DocumentLike) => DocumentLike | undefined) => {
|
||||
(mapper: (doc: Document) => Document | undefined) => {
|
||||
managerRef.current.map(mapper);
|
||||
const lookupSnapshot = managerRef.current.getSnapshot();
|
||||
|
||||
@@ -64,7 +60,7 @@ const useDocuments = ({
|
||||
const next = prev.map((doc) => {
|
||||
const id = doc?.id;
|
||||
if (id != null && lookupSnapshot.has(id as DocumentId)) {
|
||||
const canonical = lookupSnapshot.get(id as DocumentId) as DocumentLike;
|
||||
const canonical = lookupSnapshot.get(id as DocumentId) as Document;
|
||||
if (canonical !== doc) {
|
||||
changed = true;
|
||||
}
|
||||
@@ -95,7 +91,7 @@ const useDocuments = ({
|
||||
const updatedDocs = docs.map((doc) => {
|
||||
const id = doc?.id;
|
||||
if (id != null && lookupSnapshot.has(id as DocumentId)) {
|
||||
const canonical = lookupSnapshot.get(id as DocumentId) as DocumentLike;
|
||||
const canonical = lookupSnapshot.get(id as DocumentId) as Document;
|
||||
if (canonical !== doc) {
|
||||
docsChanged = true;
|
||||
}
|
||||
|
||||
@@ -62,15 +62,11 @@ const noop = () => { };
|
||||
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId | null;
|
||||
title?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
interface FolderContentsEntry {
|
||||
folder?: { id?: FolderId; name?: string | null } | null;
|
||||
documents?: DocumentLike[];
|
||||
documents?: Document[];
|
||||
subfolders?: Array<{ id?: FolderId; name?: string | null;[key: string]: unknown }>;
|
||||
__includesDocuments?: boolean;
|
||||
__sortField?: string | null;
|
||||
@@ -397,7 +393,7 @@ const useDocumentsWorkspace = ({
|
||||
() =>
|
||||
visibleDocumentIds
|
||||
.map((id) => documentLookup.get(id) || null)
|
||||
.filter((doc): doc is DocumentLike => Boolean(doc)),
|
||||
.filter((doc): doc is Document => Boolean(doc)),
|
||||
[visibleDocumentIds, documentLookup],
|
||||
);
|
||||
|
||||
|
||||
@@ -9,14 +9,10 @@ import {
|
||||
createFolderEntryKey,
|
||||
} from '../../app/entryKey';
|
||||
import type { FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
||||
import type { Document } from '../../types/documents';
|
||||
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface FolderSummary {
|
||||
id?: FolderId;
|
||||
name?: string;
|
||||
@@ -31,7 +27,7 @@ interface FolderSummary {
|
||||
|
||||
interface FolderContentsEntry {
|
||||
folder?: FolderSummary | null;
|
||||
documents?: DocumentLike[];
|
||||
documents?: Document[];
|
||||
subfolders?: FolderSummary[];
|
||||
__includesDocuments?: boolean;
|
||||
__sortField?: string | null;
|
||||
@@ -67,7 +63,7 @@ interface UseFolderTreeOptions {
|
||||
documentsSortFieldRef: MutableRefObject<string>;
|
||||
documentsSortDirectionRef: MutableRefObject<string>;
|
||||
selectionHelpers: SelectionHelpers;
|
||||
setDocuments: Dispatch<SetStateAction<DocumentLike[]>>;
|
||||
setDocuments: Dispatch<SetStateAction<Document[]>>;
|
||||
setFolderContents: Dispatch<SetStateAction<Map<FolderId, FolderContentsEntry>>>;
|
||||
folderContentsRef: MutableRefObject<Map<FolderId, FolderContentsEntry>>;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { resolveAssetUrl } from '../asset_manager';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: Identifier;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
type AssetObject = {
|
||||
url?: string | null;
|
||||
@@ -29,7 +26,7 @@ type EnsureAssetUrl = (
|
||||
options?: { force?: boolean;[key: string]: unknown },
|
||||
) => Promise<unknown>;
|
||||
|
||||
type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null;
|
||||
type GetAsset = (document: Document, assetType: string) => AssetLike | null;
|
||||
|
||||
type AssetViewLike = {
|
||||
url: string | null;
|
||||
@@ -37,14 +34,14 @@ type AssetViewLike = {
|
||||
};
|
||||
|
||||
interface UseAssetNavigatorOptions {
|
||||
document?: DocumentLike | null;
|
||||
document?: Document | null;
|
||||
assetType: string;
|
||||
ensureAssetUrl?: EnsureAssetUrl | null;
|
||||
getAsset?: GetAsset;
|
||||
}
|
||||
|
||||
interface AssetNavigatorReturn {
|
||||
document: DocumentLike | null;
|
||||
document: Document | null;
|
||||
documentId: Identifier | null;
|
||||
asset: AssetLike | null;
|
||||
assetType: string;
|
||||
|
||||
@@ -5,14 +5,7 @@ import { DownloadIcon } from '../ui/icons';
|
||||
import PdfViewer from './PdfViewer';
|
||||
import { AUDIO_EXTENSIONS, VIDEO_EXTENSIONS } from '../constants/preview';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: string;
|
||||
title?: string;
|
||||
mime_type?: string;
|
||||
filename?: string;
|
||||
original_name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
interface DocumentLink {
|
||||
url?: string;
|
||||
@@ -36,7 +29,7 @@ interface ContentTabConfig {
|
||||
type LayoutMode = 'split' | 'stacked' | (string & {});
|
||||
|
||||
interface DocumentViewerLayoutProps {
|
||||
document?: DocumentLike | null;
|
||||
document?: Document | null;
|
||||
documentLink?: DocumentLink | null;
|
||||
summaryProps?: Record<string, unknown>;
|
||||
metadataPayload?: unknown;
|
||||
|
||||
@@ -29,53 +29,26 @@ import DocumentViewerLayout from './DocumentViewerLayout';
|
||||
import useViewerLayoutMode from './useViewerLayoutMode';
|
||||
import { usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||
import type { DocumentId, FolderId } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId;
|
||||
title?: string;
|
||||
mime_type?: string | null;
|
||||
issued_at?: string | null;
|
||||
folder_id?: FolderId | null;
|
||||
correspondents?: Array<{ id?: string; name?: string }>;
|
||||
current_version?: {
|
||||
version_number?: number;
|
||||
download?: { url?: string | null; expires_at?: number } | null;
|
||||
mime_type?: string | null;
|
||||
filename?: string | null;
|
||||
} | null;
|
||||
documentLink?: {
|
||||
url: string;
|
||||
alt?: string;
|
||||
mimeType?: string | null;
|
||||
} | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface AssetLike {
|
||||
id?: string;
|
||||
url?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
import type { Document } from '../types/documents';
|
||||
import type { AssetLike } from '../types/assets';
|
||||
|
||||
type SidebarMode = 'overlay' | 'inline';
|
||||
|
||||
interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
|
||||
document: DocumentLike | null;
|
||||
document: Document | null;
|
||||
ensureAssetUrl?: (docId: DocumentId, asset: AssetLike, options?: { force?: boolean }) => Promise<unknown>;
|
||||
getDocumentAsset?: (doc: DocumentLike | null, type: string) => AssetLike | null;
|
||||
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>;
|
||||
getDocumentAsset?: (doc: Document | null, type: string) => AssetLike | null;
|
||||
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<Document | null>;
|
||||
notifyApiError?: (error: unknown, fallbackMessage?: string) => void;
|
||||
sidebarToggle?: ReactNode;
|
||||
onClosePanel?: () => void;
|
||||
resolveFolderPath?: (doc: DocumentLike | null) => Array<{ id?: string; name?: string }>;
|
||||
resolveFolderPath?: (doc: Document | null) => Array<{ id?: string; name?: string }>;
|
||||
variant?: 'viewer' | 'sidebar';
|
||||
onCollapsePanel?: () => void;
|
||||
onMaximizePanel?: (args: { documentIds: Array<string> }) => void;
|
||||
sidebarMode?: SidebarMode;
|
||||
}
|
||||
|
||||
|
||||
export const createDocumentViewerHeaderActions = ({
|
||||
document,
|
||||
actionState,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { Identifier } from './identifiers';
|
||||
|
||||
export interface AssetObject {
|
||||
ordinal?: number;
|
||||
url?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
expires_at?: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface AssetLike {
|
||||
id?: Identifier;
|
||||
asset_type?: string;
|
||||
cardinality?: number | null;
|
||||
download?: { url: string; expires_at: number } | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
assets?: Record<string, AssetLike> | AssetLike[] | null;
|
||||
objects?: AssetObject[] | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { Identifier } from './identifiers';
|
||||
import type { AssetLike } from './assets';
|
||||
|
||||
export interface DocumentTag {
|
||||
id?: Identifier;
|
||||
label?: string | null;
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export interface DocumentCorrespondent {
|
||||
id?: Identifier;
|
||||
name?: string | null;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface DocumentVersion {
|
||||
assets?: Record<string, AssetLike> | AssetLike[] | null;
|
||||
metadata?: Record<string, unknown> & { page_count?: number } | null;
|
||||
size_bytes?: number | string | null;
|
||||
checksum?: string | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Document {
|
||||
id?: Identifier;
|
||||
title?: string | null;
|
||||
original_name?: string | null;
|
||||
filename?: string | null;
|
||||
mime_type?: string | null;
|
||||
|
||||
issued_at?: string | number | null;
|
||||
created_at?: string | null;
|
||||
uploaded_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
|
||||
folder_id?: Identifier | null;
|
||||
folder_name?: string;
|
||||
folder_path?: string;
|
||||
|
||||
tags?: DocumentTag[] | null;
|
||||
correspondents?: DocumentCorrespondent[] | null;
|
||||
|
||||
current_version?: DocumentVersion | null;
|
||||
|
||||
// Allow for other properties as we unify loosely typed interfaces
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
id?: Identifier | 'root';
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type FolderEntry = {
|
||||
type: 'folder';
|
||||
id: Identifier | 'root';
|
||||
key: string;
|
||||
folder: Folder;
|
||||
};
|
||||
|
||||
export type DocumentEntry = {
|
||||
type: 'document';
|
||||
id: Identifier;
|
||||
key: string;
|
||||
document: Document;
|
||||
};
|
||||
|
||||
export type DocumentsListEntry = FolderEntry | DocumentEntry;
|
||||
|
||||
// Event Handlers
|
||||
import type { MouseEvent } from 'react';
|
||||
|
||||
export type FolderEventHandler = (folder: Folder, event: MouseEvent<HTMLElement>) => void;
|
||||
export type DocumentEventHandler = (document: Document, event: MouseEvent<HTMLElement>) => void;
|
||||
@@ -1,18 +1,16 @@
|
||||
import { resolveDocumentAssetUrl, resolveAssetUrl } from '../asset_manager';
|
||||
import type {
|
||||
DocumentLike as AssetManagerDocumentLike,
|
||||
DocumentVersionLike,
|
||||
AssetLike as AssetManagerAssetLike,
|
||||
GetAsset as AssetManagerGetAsset,
|
||||
} from '../asset_manager';
|
||||
import type {
|
||||
Document,
|
||||
DocumentVersion,
|
||||
} from '../types/documents';
|
||||
import type { AssetLike } from '../types/assets';
|
||||
|
||||
export interface DocumentLike extends AssetManagerDocumentLike {
|
||||
current_version?: DocumentVersionLike | null;
|
||||
}
|
||||
export type { Document, DocumentVersion, AssetLike };
|
||||
|
||||
export type AssetLike = AssetManagerAssetLike;
|
||||
|
||||
export type EnsurePreviewData = (id: string) => Promise<DocumentLike | null>;
|
||||
export type EnsurePreviewData = (id: string) => Promise<Document | null>;
|
||||
export type EnsureAssetUrl = (
|
||||
id: string,
|
||||
asset: AssetLike,
|
||||
@@ -21,13 +19,13 @@ export type EnsureAssetUrl = (
|
||||
export type GetDocumentAsset = AssetManagerGetAsset;
|
||||
|
||||
interface ResolveOcrTextUrlOptions {
|
||||
document: DocumentLike | null;
|
||||
document: Document | null;
|
||||
ensurePreviewData?: EnsurePreviewData;
|
||||
getDocumentAsset?: GetDocumentAsset;
|
||||
ensureAssetUrl?: EnsureAssetUrl;
|
||||
}
|
||||
|
||||
const pickAsset = (doc?: DocumentLike | null, getDocumentAsset?: GetDocumentAsset): AssetLike | null => {
|
||||
const pickAsset = (doc?: Document | null, getDocumentAsset?: GetDocumentAsset): AssetLike | null => {
|
||||
if (!doc || !getDocumentAsset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user