refactor: introduce dedicated identifier types for improved clarity and type safety
This commit is contained in:
@@ -11,8 +11,7 @@ import { DocumentsHeaderBreadcrumb } from '../documents/panel/DocumentsPanelHead
|
||||
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
|
||||
import { PanelManagerProvider, usePanelManager } from './PanelManagerContext';
|
||||
import Sidebar from '../sidebar/Sidebar';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type EnsureAssetUrl = (
|
||||
docId: Identifier,
|
||||
|
||||
@@ -28,7 +28,7 @@ interface StatusMessage {
|
||||
}
|
||||
|
||||
interface TenantOption {
|
||||
id?: string | number | null;
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ const LoginRoute: React.FC = () => {
|
||||
const payload: {
|
||||
magic_token: string;
|
||||
username?: string;
|
||||
preferred_tenant_id?: string | number;
|
||||
preferred_tenant_id?: string;
|
||||
} = {
|
||||
magic_token: magicToken,
|
||||
};
|
||||
|
||||
@@ -15,12 +15,12 @@ import PanelHeader from '../ui/PanelHeader';
|
||||
type UploadStatus = 'pending' | 'uploading' | 'success' | 'duplicate' | 'error' | (string & {});
|
||||
|
||||
interface UploadQueueItem {
|
||||
id: string | number;
|
||||
id: string;
|
||||
name: string;
|
||||
status: UploadStatus;
|
||||
error?: string | null;
|
||||
document?: { id?: string | number; title?: string };
|
||||
conflictDocumentId?: string | number;
|
||||
document?: { id?: string; title?: string };
|
||||
conflictDocumentId?: string;
|
||||
}
|
||||
|
||||
interface UploadQueueOverlayProps {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import type { DocumentId, FolderId } from '../types/identifiers';
|
||||
|
||||
// Entry key utilities for workspace selection
|
||||
// Entry keys are strings in the format "document:id" or "folder:id"
|
||||
|
||||
const ENTRY_KEY_SEPARATOR = ':';
|
||||
|
||||
// Create entry key strings
|
||||
export const createDocumentEntryKey = (documentId: string | number): string =>
|
||||
export const createDocumentEntryKey = (documentId: DocumentId): string =>
|
||||
`document${ENTRY_KEY_SEPARATOR}${documentId}`;
|
||||
|
||||
export const createFolderEntryKey = (folderId: string | number): string =>
|
||||
export const createFolderEntryKey = (folderId: FolderId): string =>
|
||||
`folder${ENTRY_KEY_SEPARATOR}${folderId}`;
|
||||
|
||||
// Type guards for entry key strings
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export interface DetailDocument {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface UseDetailPanelOptions {
|
||||
documentLookup: Map<string | number, DetailDocument>;
|
||||
documentLookup: Map<string, DetailDocument>;
|
||||
orderedSelectedDocuments: DetailDocument[];
|
||||
}
|
||||
|
||||
interface OpenDetailPanelArgs {
|
||||
documentId?: string | number;
|
||||
documentId?: string;
|
||||
document?: DetailDocument | null;
|
||||
documentIds?: Array<string | number>;
|
||||
documentIds?: Array<string>;
|
||||
documents?: DetailDocument[];
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useDetailPanel = ({
|
||||
orderedSelectedDocuments,
|
||||
}: UseDetailPanelOptions) => {
|
||||
const [detailPanelOpen, setDetailPanelOpen] = useState(false);
|
||||
const [detailPanelDocId, setDetailPanelDocId] = useState<string | number | null>(null);
|
||||
const [detailPanelDocId, setDetailPanelDocId] = useState<string | null>(null);
|
||||
const [detailPanelDocument, setDetailPanelDocument] = useState<DetailDocument | null>(null);
|
||||
const latestOrderedDocsRef = useRef<DetailDocument[]>([]);
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import type {
|
||||
SetStateAction,
|
||||
} from 'react';
|
||||
import { fetchDocument } from '../lib/apiClient';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
type DocumentId = string | number;
|
||||
type FolderId = DocumentId | 'root';
|
||||
|
||||
type DocumentLike = {
|
||||
|
||||
@@ -6,8 +6,7 @@ import {
|
||||
isFolderEntry,
|
||||
getEntryId,
|
||||
} from './entryKey';
|
||||
|
||||
type DocumentId = string | number;
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
interface SelectionEventLike {
|
||||
shiftKey?: boolean;
|
||||
@@ -21,7 +20,7 @@ interface UseDocumentSelectionOptions {
|
||||
}
|
||||
|
||||
interface ApplySelectionOptions {
|
||||
anchor?: string;
|
||||
anchor: string | null;
|
||||
interactedKeys?: string[];
|
||||
}
|
||||
|
||||
@@ -35,8 +34,8 @@ export const useDocumentSelection = ({
|
||||
const selectionOrderRef = useRef<string[]>(initialEntries);
|
||||
const selectionAnchorRef = useRef<string | null>(null);
|
||||
const selectionInitializedRef = useRef(false);
|
||||
const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | undefined>(undefined);
|
||||
const [focusedRowKey, setFocusedRowKey] = useState<string | undefined>(undefined);
|
||||
const [focusedDocumentId, setFocusedDocumentId] = useState<DocumentId | null>(null);
|
||||
const [focusedRowKey, setFocusedRowKey] = useState<string | null>(null);
|
||||
|
||||
const visibleRowKeySetRef = useRef<Set<string>>(new Set());
|
||||
const navigableRowKeysRef = useRef<string[]>([]);
|
||||
@@ -90,7 +89,7 @@ export const useDocumentSelection = ({
|
||||
const applySelection = useCallback(
|
||||
(
|
||||
rowKeys: Array<string | null>,
|
||||
{ anchor, interactedKeys = [] }: ApplySelectionOptions = {},
|
||||
{ anchor = null, interactedKeys = [] }: ApplySelectionOptions = { anchor: null, interactedKeys: [] },
|
||||
) => {
|
||||
const visibleRowKeySet = visibleRowKeySetRef.current;
|
||||
const unique: string[] = [];
|
||||
@@ -117,7 +116,7 @@ export const useDocumentSelection = ({
|
||||
}
|
||||
});
|
||||
|
||||
let resolvedAnchor = anchor;
|
||||
let resolvedAnchor: string | null = anchor ?? null;
|
||||
if (resolvedAnchor && !unique.includes(resolvedAnchor)) {
|
||||
resolvedAnchor = null;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { TAG_FILTER_UNTAGGED } from './workspaceUtils';
|
||||
import { listDocuments } from '../lib/apiClient';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = { id?: Identifier } & Record<string, unknown>;
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ const TAGS_MODAL = 'tags';
|
||||
const CORRESPONDENTS_MODAL = 'correspondents';
|
||||
|
||||
interface TagRecord {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
label?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface CorrespondentRecord {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ interface SelectionEntry {
|
||||
}
|
||||
|
||||
interface WorkspaceSelectionOptions {
|
||||
onDocumentActivate?: (id: string | number) => void;
|
||||
onInspectFolder?: (id: string | number) => void;
|
||||
onDocumentActivate?: (id: string) => void;
|
||||
onInspectFolder?: (id: string) => void;
|
||||
}
|
||||
|
||||
const identity = <T,>(value: T) => value;
|
||||
@@ -71,7 +71,7 @@ export const useWorkspaceSelection = ({
|
||||
);
|
||||
|
||||
const inspectDocument = useCallback(
|
||||
(documentId?: string | number) => {
|
||||
(documentId?: string) => {
|
||||
if (!documentId) return;
|
||||
onDocumentActivate(documentId);
|
||||
},
|
||||
@@ -79,7 +79,7 @@ export const useWorkspaceSelection = ({
|
||||
);
|
||||
|
||||
const inspectFolder = useCallback(
|
||||
(folderId?: string | number) => {
|
||||
(folderId?: string) => {
|
||||
if (!folderId) return;
|
||||
onInspectFolder(folderId);
|
||||
},
|
||||
|
||||
@@ -5,8 +5,7 @@ import DocumentsPanel from '../documents/panel/DocumentsPanel';
|
||||
import DocumentViewerPanel from '../preview/DocumentViewerPanel';
|
||||
import { usePanelManager } from './PanelManagerContext';
|
||||
import { FolderManagerProvider } from '../folders/FolderManagerContext';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type EnsureAssetUrl = (docId: Identifier, asset: unknown, options?: Record<string, unknown>) => Promise<unknown> | void;
|
||||
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type Identifier = string | number;
|
||||
import type { Identifier } from './types/identifiers';
|
||||
|
||||
type Nullable<T> = T | null;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ interface CorrespondentUsage {
|
||||
}
|
||||
|
||||
export interface CorrespondentEntry {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
usage?: CorrespondentUsage;
|
||||
[key: string]: unknown;
|
||||
@@ -17,8 +17,8 @@ export interface CorrespondentsPanelProps {
|
||||
correspondents?: CorrespondentEntry[];
|
||||
onRefresh?: () => void | Promise<void>;
|
||||
onCreate: (payload: { name: string }) => Promise<CorrespondentEntry | void>;
|
||||
onUpdate: (id: string | number, payload: { name: string }) => Promise<void>;
|
||||
onDelete: (id: string | number) => Promise<void>;
|
||||
onUpdate: (id: string, payload: { name: string }) => Promise<void>;
|
||||
onDelete: (id: string) => Promise<void>;
|
||||
onNotify?: (message: string, variant?: string) => void;
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ function CorrespondentsPanel({
|
||||
onDelete,
|
||||
onNotify,
|
||||
}: CorrespondentsPanelProps) {
|
||||
const [editingId, setEditingId] = useState<string | number | null>(null);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [draftName, setDraftName] = useState('');
|
||||
const [createName, setCreateName] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [deletingId, setDeletingId] = useState<string | number | null>(null);
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
|
||||
const startEdit = useCallback((correspondent: CorrespondentEntry) => {
|
||||
setEditingId(correspondent.id);
|
||||
|
||||
@@ -3,17 +3,18 @@ import DesktopPreviewCard from './DesktopPreviewCard';
|
||||
import { resolveCorrespondents } from '../documents/correspondents';
|
||||
import { getTagColorStyle } from '../utils/colors';
|
||||
import { preventAll } from './events';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
title?: string;
|
||||
tags?: Array<{ id?: string | number; label?: string; color?: string | null }>;
|
||||
tags?: Array<{ id?: string; label?: string; color?: string | null }>;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
interface PendingRemovalTag {
|
||||
docId?: string | number;
|
||||
tagId?: string | number;
|
||||
docId?: string;
|
||||
tagId?: string;
|
||||
}
|
||||
|
||||
interface DesktopDocumentCardProps {
|
||||
@@ -30,10 +31,10 @@ interface DesktopDocumentCardProps {
|
||||
getDocumentAsset?: (...args: any[]) => unknown;
|
||||
handleNavigatorSnapshot?: (...args: any[]) => void;
|
||||
cardPointerHandlers?: React.HTMLAttributes<HTMLDivElement>;
|
||||
onDocumentActivate?: (id: string | number) => void;
|
||||
onTagDragEnter?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
|
||||
onTagDragOver?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
|
||||
onTagDragLeave?: (event: React.DragEvent<HTMLDivElement>, docId: string | number) => void;
|
||||
onDocumentActivate?: (id: string) => void;
|
||||
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;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import { useAssetNavigator } from '../hooks/useAssetNavigator';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier;
|
||||
@@ -21,7 +20,7 @@ interface AssetLike {
|
||||
type EnsureAssetUrl = (
|
||||
documentId: Identifier,
|
||||
asset: AssetLike,
|
||||
options?: { force?: boolean; [key: string]: unknown },
|
||||
options?: { force?: boolean;[key: string]: unknown },
|
||||
) => Promise<unknown>;
|
||||
|
||||
type GetDocumentAsset = (document: DocumentLike | null, assetType: string) => AssetLike | null;
|
||||
|
||||
@@ -29,8 +29,7 @@ import '../styles/workspace/workspace-layout.css';
|
||||
import '../styles/workspace/workspace-items.css';
|
||||
import '../styles/workspace/workspace-cards.css';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { DocumentId, Identifier } from '../types/identifiers';
|
||||
|
||||
type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null;
|
||||
type DocumentLinkLike = { url?: string | null; mimeType?: string | null };
|
||||
@@ -79,7 +78,7 @@ interface DocumentSizeInfo {
|
||||
}
|
||||
|
||||
interface PreviewMetadataEntry {
|
||||
docId: string;
|
||||
docId: DocumentId;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
@@ -225,7 +224,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
const syntheticEvent = event || ({
|
||||
metaKey: true,
|
||||
ctrlKey: true,
|
||||
preventDefault: () => {},
|
||||
preventDefault: () => { },
|
||||
} as unknown as PointerEvent);
|
||||
docIds.forEach((id) => {
|
||||
const key = getDocRowKey(id);
|
||||
@@ -354,7 +353,7 @@ const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
engine.recalcVisibleDocIds();
|
||||
}, [engine]);
|
||||
|
||||
const setDraggingId = useCallback((value: string | number | null) => {
|
||||
const setDraggingId = useCallback((value: string | null) => {
|
||||
engine.setDraggingId(value);
|
||||
}, [engine]);
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
type TenantId = import('../types/identifiers').TenantId;
|
||||
|
||||
const DB_NAME = 'papercrate_desk';
|
||||
const DB_VERSION = 1;
|
||||
const LAYOUT_STORE = 'layouts';
|
||||
@@ -107,9 +110,9 @@ const withStore = async <T>(mode: TransactionMode, handler: (store: IDBObjectSto
|
||||
};
|
||||
|
||||
interface LayoutRecord {
|
||||
tenantId: string | number;
|
||||
viewId: string | number;
|
||||
documentId: string | number;
|
||||
tenantId: TenantId;
|
||||
viewId: string;
|
||||
documentId: DocumentId;
|
||||
centerX?: number;
|
||||
centerY?: number;
|
||||
rotation?: number;
|
||||
@@ -117,7 +120,13 @@ interface LayoutRecord {
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
export const fetchLayoutRecords = async ({ tenantId, viewId }: { tenantId?: string | number; viewId?: string | number }): Promise<LayoutRecord[]> => {
|
||||
export const fetchLayoutRecords = async ({
|
||||
tenantId,
|
||||
viewId,
|
||||
}: {
|
||||
tenantId?: TenantId;
|
||||
viewId?: string;
|
||||
}): Promise<LayoutRecord[]> => {
|
||||
if (!tenantId || !viewId) {
|
||||
return [];
|
||||
}
|
||||
@@ -133,7 +142,22 @@ export const fetchLayoutRecords = async ({ tenantId, viewId }: { tenantId?: stri
|
||||
}
|
||||
};
|
||||
|
||||
export const upsertLayoutRecords = async ({ tenantId, viewId, entries }: { tenantId?: string | number; viewId?: string | number; entries?: Array<{ documentId?: string | number; centerX?: number; centerY?: number; rotation?: number; zIndex?: number; updatedAt?: number }> }) => {
|
||||
export const upsertLayoutRecords = async ({
|
||||
tenantId,
|
||||
viewId,
|
||||
entries,
|
||||
}: {
|
||||
tenantId?: TenantId;
|
||||
viewId?: string;
|
||||
entries?: Array<{
|
||||
documentId?: DocumentId;
|
||||
centerX?: number;
|
||||
centerY?: number;
|
||||
rotation?: number;
|
||||
zIndex?: number;
|
||||
updatedAt?: number;
|
||||
}>;
|
||||
}) => {
|
||||
if (!tenantId || !viewId || !Array.isArray(entries) || !entries.length) {
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +186,7 @@ export const upsertLayoutRecords = async ({ tenantId, viewId, entries }: { tenan
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteTenantLayouts = async (tenantId?: string | number) => {
|
||||
export const deleteTenantLayouts = async (tenantId?: TenantId) => {
|
||||
if (!tenantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
interface DocumentLike {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
current_version?: unknown;
|
||||
tags?: unknown;
|
||||
}
|
||||
|
||||
interface AssetLike {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface PreviewMetadataEntry {
|
||||
docId: string;
|
||||
docId: DocumentId;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
type GetDocumentAsset = (doc: DocumentLike, type: string) => AssetLike | null;
|
||||
type EnsureAssetUrl = (docId: string | number, asset: AssetLike, options?: { force?: boolean }) => Promise<AssetLike | null>;
|
||||
type EnsureAssetUrl = (docId: DocumentId, asset: AssetLike, options?: { force?: boolean }) => Promise<AssetLike | null>;
|
||||
|
||||
const usePreviewMetadata = (
|
||||
documents: DocumentLike[] | null,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { safeInvoke } from '../events';
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
|
||||
export const CLICK_ACTIONS = {
|
||||
selectSingle: 'selectSingle',
|
||||
@@ -25,9 +26,9 @@ export const LONG_PRESS_DURATION_MS = 450;
|
||||
export const withinThreshold = (dx: number, dy: number, thresholdSquared: number): boolean => (dx * dx + dy * dy) <= thresholdSquared;
|
||||
|
||||
interface PointerIntentArgs {
|
||||
doc: { id: string | number };
|
||||
doc: { id: string };
|
||||
entryDescriptor: unknown;
|
||||
selectedDocumentIds: Array<string | number>;
|
||||
selectedDocumentIds: Array<string>;
|
||||
metaKey: boolean;
|
||||
pointerButton?: number;
|
||||
pointerType?: string;
|
||||
@@ -35,7 +36,7 @@ interface PointerIntentArgs {
|
||||
}
|
||||
|
||||
export interface PointerIntent {
|
||||
docId: string | number;
|
||||
docId: DocumentId;
|
||||
entryDescriptor: unknown;
|
||||
pointerType?: string;
|
||||
pointerButton?: number;
|
||||
|
||||
@@ -19,8 +19,7 @@ import {
|
||||
applyDomTransform,
|
||||
type WorkspaceEngine,
|
||||
} from './workspaceEngine';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { DocumentId, Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier | null;
|
||||
@@ -121,7 +120,7 @@ interface UseDocumentDragOptions {
|
||||
type EngineInertiaState = Parameters<WorkspaceEngine['startInertiaAnimation']>[1];
|
||||
|
||||
interface DragStateInternal extends EngineDragState {
|
||||
docId: string;
|
||||
docId: DocumentId;
|
||||
docKey: string;
|
||||
pointerId: number;
|
||||
originCenterX: number;
|
||||
@@ -216,7 +215,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
||||
onDocumentStackSelect,
|
||||
selectedDocumentIds = [],
|
||||
markLayoutDirty,
|
||||
} = options;
|
||||
} = options;
|
||||
|
||||
const fallbackContainerRef = useRef<HTMLElement | null>(null);
|
||||
const containerRef = providedContainerRef ?? fallbackContainerRef;
|
||||
@@ -237,7 +236,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
||||
|
||||
const tapHandler = usePointerTap<DragTapMetadata>({
|
||||
delay: 220,
|
||||
onSingle: () => {},
|
||||
onSingle: () => { },
|
||||
onDouble: ({ data, event }) => {
|
||||
if (!data?.docId) {
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { clamp, formatTransform } from '../utils/math';
|
||||
import { fetchLayoutRecords, upsertLayoutRecords } from './db';
|
||||
|
||||
type DocumentId = string;
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
type TenantId = import('../types/identifiers').TenantId;
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
@@ -63,7 +63,7 @@ interface BaseMetrics {
|
||||
}
|
||||
|
||||
interface DragGroupItem {
|
||||
docId?: string | number | null;
|
||||
docId?: string | null;
|
||||
width: number;
|
||||
height: number;
|
||||
currentCenterX?: number;
|
||||
@@ -80,7 +80,7 @@ interface DragState {
|
||||
}
|
||||
|
||||
interface InertiaSimulationState {
|
||||
docId: string;
|
||||
docId: DocumentId;
|
||||
restRotation: number;
|
||||
rotation: number;
|
||||
dynamicRotation: number;
|
||||
@@ -106,7 +106,7 @@ interface WorkspaceSnapshot {
|
||||
|
||||
type WorkspaceSubscriber = () => void;
|
||||
|
||||
type DeskDocument = { id?: string | number | null } & Record<string, unknown>;
|
||||
type DeskDocument = { id?: string | null } & Record<string, unknown>;
|
||||
|
||||
type EnsureDocumentSize = (doc: DeskDocument) => DocumentSize | null;
|
||||
|
||||
@@ -226,7 +226,7 @@ export const clampCardDimensions = (width: number, height: number): CardDimensio
|
||||
};
|
||||
};
|
||||
|
||||
export const computeFallbackCardSize = (docId: string | number): CardDimensions | null => {
|
||||
export const computeFallbackCardSize = (docId: DocumentId): CardDimensions | null => {
|
||||
const baseSeed = seededRandom(`${docId}:fallback-size`);
|
||||
const aspectSeed = seededRandom(`${docId}:fallback-aspect`);
|
||||
|
||||
@@ -259,7 +259,7 @@ function randomRangeFromSeed(seedKey: string, min: number, max: number): number
|
||||
return min + seed * span;
|
||||
}
|
||||
|
||||
function buildKey(docId: string | number, suffix: string): string {
|
||||
function buildKey(docId: DocumentId, suffix: string): string {
|
||||
return `${docId}::${suffix}`;
|
||||
}
|
||||
|
||||
@@ -525,63 +525,34 @@ const generateInitialLayout = (
|
||||
|
||||
export class WorkspaceEngine {
|
||||
allowLayoutPersistence: boolean;
|
||||
|
||||
tenantId: string | null;
|
||||
|
||||
tenantId: TenantId | null;
|
||||
viewId: string | null;
|
||||
|
||||
layout: Map<DocumentId, LayoutEntry>;
|
||||
|
||||
layoutSnapshot: Map<DocumentId, LayoutEntry>;
|
||||
|
||||
persistedLayout: Map<DocumentId, LayoutEntry>;
|
||||
|
||||
layoutDirty: boolean;
|
||||
|
||||
zCounter: number;
|
||||
|
||||
canvasSize: { width: number; height: number };
|
||||
|
||||
visibleDocIds: Set<DocumentId>;
|
||||
|
||||
draggingId: string | null;
|
||||
|
||||
tagDropTargetId: string | null;
|
||||
|
||||
pendingTagDocId: string | null;
|
||||
|
||||
pendingRemovalTag: unknown;
|
||||
|
||||
dragInProgress: boolean;
|
||||
|
||||
activeDragDocIds: Set<DocumentId>;
|
||||
|
||||
pendingSnapshotSync: boolean;
|
||||
|
||||
pendingPersistSync: boolean;
|
||||
|
||||
persistDebounceId: number | null;
|
||||
|
||||
items: DeskDocument[];
|
||||
|
||||
documentLookup: Map<string, DeskDocument>;
|
||||
|
||||
ensureDocumentSize: EnsureDocumentSize;
|
||||
|
||||
resolveBaseMetrics: ResolveBaseMetrics;
|
||||
|
||||
snapshotCache: WorkspaceSnapshot;
|
||||
|
||||
subscribers: Set<WorkspaceSubscriber>;
|
||||
|
||||
loadingPersisted: boolean;
|
||||
|
||||
pendingPersistence: unknown;
|
||||
|
||||
itemRefs: ItemRefs;
|
||||
|
||||
inertiaAnimations: Map<string, InertiaSimulationState>;
|
||||
|
||||
initialLoadDone: boolean;
|
||||
|
||||
constructor({
|
||||
@@ -716,7 +687,7 @@ export class WorkspaceEngine {
|
||||
this.emit();
|
||||
}
|
||||
|
||||
setDraggingId(docId: string | number | null): void {
|
||||
setDraggingId(docId: DocumentId | null): void {
|
||||
const normalized = docId != null ? String(docId) : null;
|
||||
if (this.draggingId === normalized) {
|
||||
return;
|
||||
@@ -725,7 +696,7 @@ export class WorkspaceEngine {
|
||||
this.emit();
|
||||
}
|
||||
|
||||
beginDrag(docIds: Array<string | number | null> = []): void {
|
||||
beginDrag(docIds: Array<string | null> = []): void {
|
||||
this.dragInProgress = true;
|
||||
if (Array.isArray(docIds)) {
|
||||
this.activeDragDocIds = new Set(docIds.map((id) => (id != null ? String(id) : null)).filter(Boolean));
|
||||
@@ -749,7 +720,7 @@ export class WorkspaceEngine {
|
||||
}
|
||||
}
|
||||
|
||||
setTagDropTargetId(docId: string | number | null): void {
|
||||
setTagDropTargetId(docId: DocumentId | null): void {
|
||||
const normalized = docId != null ? String(docId) : null;
|
||||
if (this.tagDropTargetId === normalized) {
|
||||
return;
|
||||
@@ -758,7 +729,7 @@ export class WorkspaceEngine {
|
||||
this.emit();
|
||||
}
|
||||
|
||||
setPendingTagDocId(docId: string | number | null): void {
|
||||
setPendingTagDocId(docId: DocumentId | null): void {
|
||||
const normalized = docId != null ? String(docId) : null;
|
||||
if (this.pendingTagDocId === normalized) {
|
||||
return;
|
||||
@@ -779,7 +750,7 @@ export class WorkspaceEngine {
|
||||
this.layoutDirty = true;
|
||||
}
|
||||
|
||||
getLayout(docId: string | number | null): LayoutEntry | null {
|
||||
getLayout(docId: DocumentId | null): LayoutEntry | null {
|
||||
if (docId == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -788,7 +759,7 @@ export class WorkspaceEngine {
|
||||
}
|
||||
|
||||
updateLayoutEntry(
|
||||
docId: string | number | null,
|
||||
docId: DocumentId | null,
|
||||
updater: (previous: LayoutEntry | null) => LayoutEntry | null,
|
||||
): void {
|
||||
if (docId == null) {
|
||||
@@ -807,7 +778,7 @@ export class WorkspaceEngine {
|
||||
this.persistLayoutSnapshot();
|
||||
}
|
||||
|
||||
bringToFront(docId: string | number | null): void {
|
||||
bringToFront(docId: DocumentId | null): void {
|
||||
if (docId == null) {
|
||||
return;
|
||||
}
|
||||
@@ -825,7 +796,7 @@ export class WorkspaceEngine {
|
||||
}
|
||||
|
||||
applyTransform(
|
||||
docId: string | number | null,
|
||||
docId: DocumentId | null,
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
width: number,
|
||||
@@ -896,7 +867,7 @@ export class WorkspaceEngine {
|
||||
this.persistLayoutSnapshot();
|
||||
}
|
||||
|
||||
cancelInertiaAnimation(docId: string | number | null): void {
|
||||
cancelInertiaAnimation(docId: DocumentId | null): void {
|
||||
const key = docId != null ? String(docId) : null;
|
||||
if (!key) {
|
||||
return;
|
||||
@@ -995,7 +966,7 @@ export class WorkspaceEngine {
|
||||
return isSettled;
|
||||
}
|
||||
|
||||
startInertiaAnimation(docId: string | number | null, baseState: InertiaSimulationState): void {
|
||||
startInertiaAnimation(docId: DocumentId | null, baseState: InertiaSimulationState): void {
|
||||
const raf = window.requestAnimationFrame;
|
||||
if (!raf) {
|
||||
return;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { clamp } from '../utils/math';
|
||||
import PdfViewer from '../preview/PdfViewer';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
title?: string;
|
||||
mime_type?: string | null;
|
||||
[key: string]: unknown;
|
||||
@@ -43,7 +43,7 @@ const determineDisplayKind = (entry?: DocumentLink | null): DisplayKind => {
|
||||
return 'image';
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
const noop = () => { };
|
||||
|
||||
const PreviewZoomOverlay: React.FC<PreviewZoomOverlayProps> = ({
|
||||
open = false,
|
||||
|
||||
@@ -6,8 +6,7 @@ import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
|
||||
import { getEntryId, isDocumentEntry } from '../app/entryKey';
|
||||
import type { DocumentInfoPanelProps } from '../documents/DocumentInfoPanel';
|
||||
import type { EnsureAssetUrl, GetDocumentAsset } from '../utils/ocr';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier;
|
||||
|
||||
@@ -3,15 +3,15 @@ import React from 'react';
|
||||
const NBSP = String.fromCharCode(160);
|
||||
|
||||
export interface CorrespondentLinkEntry {
|
||||
id?: string | number | null;
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
interface CorrespondentLinksProps {
|
||||
correspondents?: CorrespondentLinkEntry[];
|
||||
activeCorrespondentIdSet?: Set<string | number>;
|
||||
onCorrespondentClick?: (id: string | number) => void;
|
||||
activeCorrespondentIdSet?: Set<string>;
|
||||
onCorrespondentClick?: (id: string) => void;
|
||||
}
|
||||
|
||||
const CorrespondentLinks: React.FC<CorrespondentLinksProps> = ({
|
||||
@@ -23,7 +23,7 @@ const CorrespondentLinks: React.FC<CorrespondentLinksProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeSet = activeCorrespondentIdSet || new Set<string | number>();
|
||||
const activeSet = activeCorrespondentIdSet || new Set<string>();
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>, correspondent: CorrespondentLinkEntry) => {
|
||||
if (!onCorrespondentClick || correspondent.id == null) {
|
||||
return;
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface DocumentInfoPanelProps {
|
||||
activeTab?: string;
|
||||
onTabChange?: (tabId: string) => void;
|
||||
defaultTabId?: string;
|
||||
resetKey?: string | number | null;
|
||||
resetKey?: string | null;
|
||||
classNamePrefix?: string;
|
||||
hideTabNavWhenSingle?: boolean;
|
||||
summaryPlacement?: 'inline' | 'tabs';
|
||||
|
||||
@@ -14,11 +14,10 @@ import {
|
||||
import { describeDocumentSummary, type DocumentSummaryRow } from './documentSummary';
|
||||
|
||||
import { useFolderManager } from '../folders/FolderManagerContext';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { FolderId, Identifier, TagId } from '../types/identifiers';
|
||||
|
||||
interface TagEntry {
|
||||
id?: Identifier;
|
||||
id?: TagId;
|
||||
label?: string;
|
||||
color?: string | null;
|
||||
}
|
||||
@@ -33,7 +32,7 @@ interface DocumentLike {
|
||||
id?: Identifier;
|
||||
title?: string;
|
||||
issued_at?: string | null;
|
||||
folder_id?: string | null;
|
||||
folder_id?: FolderId | null;
|
||||
current_version?: { version_number?: number } | null;
|
||||
tags?: TagEntry[];
|
||||
correspondents?: CorrespondentEntry[];
|
||||
@@ -64,17 +63,17 @@ interface CorrespondentSectionProps {
|
||||
|
||||
export interface DocumentSummarySectionProps {
|
||||
document?: DocumentLike | null;
|
||||
tagLookupById?: Map<Identifier, TagEntry>;
|
||||
tagLookupById?: Map<TagId, TagEntry>;
|
||||
tagOptions?: SelectionAssignmentMenuItem[];
|
||||
onTagAdd?: (doc: DocumentLike, value: string, context?: { option?: unknown }) => void;
|
||||
onTagRemove?: (docId: Identifier | undefined, tagId: Identifier | undefined) => void;
|
||||
onTagRemove?: (docId: Identifier | undefined, tagId: TagId | undefined) => void;
|
||||
correspondents?: CorrespondentEntry[];
|
||||
correspondentOptions?: SelectionAssignmentMenuItem[];
|
||||
onCorrespondentAdd?: (payload: { document: DocumentLike; 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;
|
||||
onFolderNavigate?: (folderId: string | null) => void;
|
||||
onFolderNavigate?: (folderId: FolderId | null) => void;
|
||||
layout?: 'default' | 'compact';
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const DEFAULT_THUMBNAIL_SIZE = 48;
|
||||
// Detect when an element becomes visible within a scroll container so we can delay loading.
|
||||
const useLazyVisibility = (
|
||||
rootRef: MutableRefObject<Element | null> | null,
|
||||
resetKey?: string | number | null,
|
||||
resetKey?: string | null,
|
||||
) => {
|
||||
const targetRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
@@ -8,8 +8,7 @@ import { resolveCorrespondents } from './correspondents';
|
||||
import { writeTagTransferData } from './tagTransfer';
|
||||
import useInlineRename from './useInlineRename';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
|
||||
export type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
export interface FolderLike {
|
||||
id?: Identifier | 'root';
|
||||
|
||||
@@ -9,8 +9,7 @@ import { resolveCorrespondents } from './correspondents';
|
||||
import { writeTagTransferData } from './tagTransfer';
|
||||
import useInlineRename from './useInlineRename';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
|
||||
export type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
export interface FolderLike {
|
||||
id?: Identifier | 'root';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { shallowEqual } from 'react-redux';
|
||||
|
||||
type DocumentId = string | number;
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
export type ManagedDocument = { id?: DocumentId | null } & Record<string, unknown>;
|
||||
|
||||
|
||||
@@ -5,18 +5,18 @@ import { CheckIcon, CircleDashedCheckIcon, PlusIcon } from '../ui/icons';
|
||||
export type AssignmentState = 'all' | 'partial' | 'none';
|
||||
|
||||
export interface SelectionAssignmentMenuItem {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
label?: string;
|
||||
state?: AssignmentState;
|
||||
count?: number | null;
|
||||
total?: number | null;
|
||||
color?: string | null;
|
||||
value?: string | number;
|
||||
value?: string;
|
||||
payload?: unknown;
|
||||
}
|
||||
|
||||
export interface NormalizedSelectionAssignmentItem {
|
||||
id: string | number;
|
||||
id: string;
|
||||
label: string;
|
||||
state: AssignmentState;
|
||||
count: number | null;
|
||||
@@ -105,7 +105,7 @@ const SelectionAssignmentMenu: React.FC<SelectionAssignmentMenuProps> = ({
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [query, setQuery] = useState('');
|
||||
const [pending, setPending] = useState(false);
|
||||
const [sortSnapshot, setSortSnapshot] = useState<Array<string | number> | null>(null);
|
||||
const [sortSnapshot, setSortSnapshot] = useState<Array<string> | null>(null);
|
||||
|
||||
const {
|
||||
isOpen,
|
||||
@@ -175,10 +175,10 @@ const SelectionAssignmentMenu: React.FC<SelectionAssignmentMenuProps> = ({
|
||||
|
||||
const orderedItems = useMemo(() => {
|
||||
if (freezeSortOnOpen && sortSnapshot && sortByState) {
|
||||
const itemMap = new Map<string | number, NormalizedSelectionAssignmentItem>(
|
||||
const itemMap = new Map<string, NormalizedSelectionAssignmentItem>(
|
||||
sortedByStateItems.map((item) => [item.id, item]),
|
||||
);
|
||||
const seen = new Set<string | number>();
|
||||
const seen = new Set<string>();
|
||||
const fromSnapshot = sortSnapshot
|
||||
.map((id) => {
|
||||
const entry = itemMap.get(id);
|
||||
|
||||
@@ -12,10 +12,10 @@ import SelectionAssignmentMenu, { SelectionAssignmentMenuItem } from './Selectio
|
||||
import SelectionSummary from './SelectionSummary';
|
||||
import { useAppState } from '../app/appState';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
const ROOT_FOLDER_LABEL = 'Documents';
|
||||
|
||||
type DocumentId = string | number;
|
||||
type NullableDocumentId = DocumentId | null;
|
||||
|
||||
type SelectedIdList = NullableDocumentId[] | null;
|
||||
@@ -145,7 +145,7 @@ const buildTagAssignments = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const map = new Map<string | number, {
|
||||
const map = new Map<string, {
|
||||
id?: DocumentId;
|
||||
label: string;
|
||||
color: string | null;
|
||||
@@ -209,7 +209,7 @@ const buildCorrespondentAssignments = (
|
||||
return [];
|
||||
}
|
||||
|
||||
const map = new Map<string | number, {
|
||||
const map = new Map<string, {
|
||||
id?: DocumentId;
|
||||
label: string;
|
||||
count: number;
|
||||
@@ -491,7 +491,7 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
||||
const value = isRecord(candidate)
|
||||
? (candidate?.id ?? candidate?.value ?? null)
|
||||
: candidate;
|
||||
if (!value && value !== 0) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
await onMoveDocumentsToFolder(documentIdList, value as DocumentId);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
export interface DocumentsFilterValue {
|
||||
query: string;
|
||||
searchResultIds: Array<string | number> | null;
|
||||
searchResultIds: Array<string> | null;
|
||||
searchLoading: boolean;
|
||||
includeDescendants: boolean;
|
||||
activeTagIds: Identifier[];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface CorrespondentReference {
|
||||
id?: string | number | null;
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
key?: string;
|
||||
}
|
||||
@@ -9,9 +9,9 @@ export interface DocumentLike {
|
||||
}
|
||||
|
||||
export interface ResolvedCorrespondent {
|
||||
id?: string | number | null;
|
||||
id?: string | null;
|
||||
name: string;
|
||||
key: string | number;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export const resolveCorrespondents = (doc?: DocumentLike | null): ResolvedCorrespondent[] => {
|
||||
@@ -19,7 +19,7 @@ export const resolveCorrespondents = (doc?: DocumentLike | null): ResolvedCorres
|
||||
return [];
|
||||
}
|
||||
|
||||
const seen = new Set<string | number>();
|
||||
const seen = new Set<string>();
|
||||
const results: ResolvedCorrespondent[] = [];
|
||||
|
||||
doc.correspondents.forEach((entry = {}, index) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { assignCorrespondentsBulk } from '../../lib/apiClient';
|
||||
|
||||
export type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
type BulkAssignmentResponse = {
|
||||
assigned?: number;
|
||||
@@ -116,7 +115,7 @@ const useBulkDocumentActions = ({
|
||||
setStatusMessage,
|
||||
updateDocumentCaches,
|
||||
],
|
||||
);
|
||||
);
|
||||
|
||||
const handleBulkCorrespondentRemove = useCallback(
|
||||
async ({ assignments = [], documentIds }: { assignments?: CorrespondentAssignment[]; documentIds?: Identifier[] }) => {
|
||||
@@ -174,7 +173,7 @@ const useBulkDocumentActions = ({
|
||||
}
|
||||
},
|
||||
[resolveTargetDocumentIds, setStatusMessage, updateDocumentCaches],
|
||||
);
|
||||
);
|
||||
|
||||
const handleDeleteSelection = useCallback(async () => {
|
||||
const docIds = Array.isArray(selectedDocumentIds) ? selectedDocumentIds : [];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { WorkspaceSelectionValue } from '../../app/WorkspaceSelectionContext';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface DocumentLinkLike {
|
||||
url?: string | null;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { createDocumentEntryKey, createFolderEntryKey, isFolderEntry } from '../../app/entryKey';
|
||||
import type { DocumentId, FolderId } from '../../types/identifiers';
|
||||
|
||||
interface FolderEntry {
|
||||
id: string | number;
|
||||
id: FolderId;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface DocumentEntry {
|
||||
id: string | number;
|
||||
id: DocumentId;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface NavigableRow {
|
||||
key: string;
|
||||
type: 'folder' | 'document';
|
||||
id: string | number;
|
||||
id: FolderId | DocumentId;
|
||||
}
|
||||
|
||||
interface UseDocumentsSelectionOptions {
|
||||
@@ -25,19 +26,19 @@ interface UseDocumentsSelectionOptions {
|
||||
visibleRowKeySet: Set<string>;
|
||||
selectedEntries: string[];
|
||||
selectionAnchorRef: { current: string | null };
|
||||
promoteSelectionOrderRaw: (id: string | number) => void;
|
||||
setFocusedDocumentId: (id: string | number | null) => void;
|
||||
setActivePreviewId: (id: string | number | null) => void;
|
||||
promoteSelectionOrderRaw: (id: DocumentId) => void;
|
||||
setFocusedDocumentId: (id: DocumentId | null) => void;
|
||||
setActivePreviewId: (id: DocumentId | null) => void;
|
||||
clearSelection: () => void;
|
||||
focusedDocumentId: string | number | null;
|
||||
focusedDocumentId: DocumentId | null;
|
||||
setFocusedRowKey: (value: string | null | ((current: string | null) => string | null)) => void;
|
||||
focusedRowKey: string | null;
|
||||
}
|
||||
|
||||
const useDocumentsSelection = ({
|
||||
showingSearchResults,
|
||||
currentSubfolders,
|
||||
visibleDocuments,
|
||||
currentSubfolders = [],
|
||||
visibleDocuments = [],
|
||||
configureSelectionEnvironment,
|
||||
visibleRowKeySet,
|
||||
selectedEntries,
|
||||
@@ -82,7 +83,7 @@ const useDocumentsSelection = ({
|
||||
}, [configureSelectionEnvironment, visibleRowKeySet, navigableRowKeys]);
|
||||
|
||||
const promoteSelectionOrder = useCallback(
|
||||
(docId: string | number | null) => {
|
||||
(docId: DocumentId | null) => {
|
||||
if (!docId) return;
|
||||
promoteSelectionOrderRaw(docId);
|
||||
const rowKey = createDocumentEntryKey(docId);
|
||||
@@ -99,7 +100,7 @@ const useDocumentsSelection = ({
|
||||
clearSelection();
|
||||
}, [clearSelection]);
|
||||
|
||||
const prevFocusedDocIdRef = useRef<string | number | null>(focusedDocumentId);
|
||||
const prevFocusedDocIdRef = useRef<DocumentId | null>(focusedDocumentId);
|
||||
useEffect(() => {
|
||||
const previous = prevFocusedDocIdRef.current;
|
||||
if (previous === focusedDocumentId) {
|
||||
@@ -109,7 +110,7 @@ const useDocumentsSelection = ({
|
||||
if (focusedDocumentId) {
|
||||
setFocusedRowKey(createDocumentEntryKey(focusedDocumentId));
|
||||
} else {
|
||||
setFocusedRowKey((current) => (isFolderEntry(current) ? current : null));
|
||||
setFocusedRowKey((current) => (current && isFolderEntry(current) ? current : null));
|
||||
}
|
||||
}, [focusedDocumentId, setFocusedRowKey]);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import DocumentsPanelHeader, {
|
||||
import { SelectionFloatingPanel } from '../SelectionFloatingActions';
|
||||
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
||||
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
const DEFAULT_GRID_ICON_SIZE = 144;
|
||||
|
||||
@@ -73,7 +74,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
documentLinks,
|
||||
ensureDownloadUrl,
|
||||
deskWorkspaceProps = null,
|
||||
onRefresh = () => {},
|
||||
onRefresh = () => { },
|
||||
sortField,
|
||||
sortDirection,
|
||||
onSortFieldChange,
|
||||
@@ -256,8 +257,7 @@ const DocumentsPanelInner: React.FC<DocumentsPanelInnerProps> = ({
|
||||
const isGridView = viewMode === 'grid';
|
||||
const isDeskView = viewMode === 'desk';
|
||||
|
||||
type Identifier = string | number;
|
||||
type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null };
|
||||
type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null };
|
||||
|
||||
const [previewDocId, setPreviewDocId] = useState<Identifier | null>(null);
|
||||
|
||||
@@ -499,7 +499,7 @@ type ZoomSource = { url: string; alt?: string | null; mimeType?: string | null }
|
||||
setFocusedRowKey(targetRow.key);
|
||||
handleEntrySelection(targetRow.key, {
|
||||
shiftKey,
|
||||
preventDefault: () => {},
|
||||
preventDefault: () => { },
|
||||
});
|
||||
},
|
||||
[
|
||||
|
||||
@@ -2,8 +2,7 @@ import React from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import PanelHeader from '../../ui/PanelHeader';
|
||||
import BreadcrumbTrail from '../../ui/BreadcrumbTrail';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
export interface DocumentsHeaderBreadcrumb {
|
||||
id?: Identifier;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { DocumentId, TagId } from '../types/identifiers';
|
||||
|
||||
const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag'];
|
||||
const TAG_TEXT_MIME_TYPE = 'text/plain';
|
||||
|
||||
interface TagPayload {
|
||||
id: string | number;
|
||||
id: TagId;
|
||||
label: string;
|
||||
sourceDocId: string | number | null;
|
||||
sourceDocId: DocumentId | null;
|
||||
}
|
||||
|
||||
interface TagLike {
|
||||
id?: string | number;
|
||||
id?: TagId;
|
||||
label?: string | null;
|
||||
}
|
||||
|
||||
@@ -21,7 +23,10 @@ const serializePayload = (payload: TagPayload): string | null => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createTagTransferPayload = (tag?: TagLike | null, sourceDocId: string | number | null = null): TagPayload | null => {
|
||||
export const createTagTransferPayload = (
|
||||
tag?: TagLike | null,
|
||||
sourceDocId: DocumentId | null = null,
|
||||
): TagPayload | null => {
|
||||
if (!tag || tag.id == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -33,7 +38,11 @@ export const createTagTransferPayload = (tag?: TagLike | null, sourceDocId: stri
|
||||
};
|
||||
};
|
||||
|
||||
export const writeTagTransferData = (dataTransfer: DataTransfer | null, tag: TagLike, sourceDocId: string | number | null = null): void => {
|
||||
export const writeTagTransferData = (
|
||||
dataTransfer: DataTransfer | null,
|
||||
tag: TagLike,
|
||||
sourceDocId: DocumentId | null = null,
|
||||
): void => {
|
||||
if (!dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const isPrimaryPointerEvent = (event?: PointerEventLike | null): boolean
|
||||
export type EntryType = 'document' | 'folder';
|
||||
|
||||
export interface WorkspaceEntry {
|
||||
id: string | number;
|
||||
id: string;
|
||||
key?: string;
|
||||
type: EntryType;
|
||||
[key: string]: unknown;
|
||||
@@ -28,7 +28,7 @@ export interface WorkspaceEntry {
|
||||
|
||||
interface UseEntryPointerOptions {
|
||||
onSelectEntry?: (entry: WorkspaceEntry, event?: PointerEventLike | null, metadata?: EntryPointerMetadata) => void;
|
||||
onDocumentActivate?: (id: string | number, metadata?: EntryPointerMetadata) => void;
|
||||
onDocumentActivate?: (id: string, metadata?: EntryPointerMetadata) => void;
|
||||
}
|
||||
|
||||
export interface EntryPointerMetadata {
|
||||
@@ -36,7 +36,7 @@ export interface EntryPointerMetadata {
|
||||
primaryClick: boolean;
|
||||
rowKey: string;
|
||||
type: EntryType;
|
||||
id: string | number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const useEntryPointer = ({
|
||||
|
||||
@@ -13,22 +13,22 @@ type FocusableInput = (HTMLInputElement | HTMLTextAreaElement) & {
|
||||
|
||||
type InlineRenameOptions<TEntity> = {
|
||||
getCurrentValue?: (entity: TEntity) => string | null;
|
||||
getEntityId?: (entity: TEntity) => string | number | null;
|
||||
getEntityId?: (entity: TEntity) => string | null;
|
||||
};
|
||||
|
||||
type InlineRenameHandler = (
|
||||
id: string | number,
|
||||
id: string,
|
||||
value: string,
|
||||
) => boolean | void | Promise<boolean | void>;
|
||||
|
||||
type InlineRenameReturn<TEntity> = {
|
||||
editingId: string | number | null;
|
||||
editingId: string | null;
|
||||
draftValue: string;
|
||||
setDraftValue: Dispatch<SetStateAction<string>>;
|
||||
beginEditing: (entity?: TEntity | null, event?: SyntheticEvent | Event) => void;
|
||||
cancelEditing: (event?: SyntheticEvent | Event) => void;
|
||||
submitEditing: (entity?: TEntity | null) => Promise<boolean>;
|
||||
savingId: string | number | null;
|
||||
savingId: string | null;
|
||||
attachInputRef: (node: FocusableInput | null) => void;
|
||||
};
|
||||
|
||||
@@ -51,18 +51,18 @@ const focusInput = (node: FocusableInput | null) => {
|
||||
const identity = (value: unknown) => value as string;
|
||||
|
||||
const defaultGetEntityId = <T,>(entity?: T | null) =>
|
||||
(entity as { id?: string | number } | null)?.id ?? null;
|
||||
(entity as { id?: string } | null)?.id ?? null;
|
||||
|
||||
const useInlineRename = <TEntity,>(
|
||||
onRename?: InlineRenameHandler,
|
||||
{
|
||||
getCurrentValue = identity as (entity: TEntity) => string | null,
|
||||
getEntityId = defaultGetEntityId as (entity: TEntity) => string | number | null,
|
||||
getEntityId = defaultGetEntityId as (entity: TEntity) => string | null,
|
||||
}: InlineRenameOptions<TEntity> = {},
|
||||
): InlineRenameReturn<TEntity> => {
|
||||
const [editingId, setEditingId] = useState<string | number | null>(null);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
const [draftValue, setDraftValue] = useState('');
|
||||
const [savingId, setSavingId] = useState<string | number | null>(null);
|
||||
const [savingId, setSavingId] = useState<string | null>(null);
|
||||
const inputRef = useRef<FocusableInput | null>(null);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ const FolderManagerContext = createContext<FolderManager>(defaultManager);
|
||||
|
||||
interface FolderManagerProviderProps {
|
||||
folderNodes?: Map<string | 'root', { name?: string | null }>;
|
||||
ensureFolderData?: (folderId: string | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
|
||||
ensureFolderData?: (folderId: FolderId | 'root', options?: { force?: boolean; includeDocuments?: boolean }) => Promise<void>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ type ApiClient = {
|
||||
};
|
||||
|
||||
interface CorrespondentEntry {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ interface UseCorrespondentsOptions {
|
||||
apiClient: ApiClient;
|
||||
notifyApiError: (error: unknown, fallback: string) => void;
|
||||
setStatusMessage: (message: string, variant?: string) => void;
|
||||
tenantIdRef: MutableRefObject<string | number | null>;
|
||||
tenantIdRef: MutableRefObject<string | null>;
|
||||
mapDocumentCaches?: (mapper: (doc: any) => any) => void;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const useCorrespondents = ({
|
||||
}, [apiClient, notifyApiError, tenantIdRef]);
|
||||
|
||||
const handleCorrespondentUpdate = useCallback(
|
||||
async (correspondentId: string | number, changes: { name?: string }) => {
|
||||
async (correspondentId: string, changes: { name?: string }) => {
|
||||
if (correspondentId == null) {
|
||||
throw new Error('Missing correspondent identifier.');
|
||||
}
|
||||
@@ -100,7 +100,7 @@ const useCorrespondents = ({
|
||||
);
|
||||
|
||||
const handleCorrespondentDelete = useCallback(
|
||||
async (correspondentId: string | number) => {
|
||||
async (correspondentId: string) => {
|
||||
if (correspondentId == null) {
|
||||
throw new Error('Missing correspondent identifier.');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
|
||||
type ApiClient = {
|
||||
@@ -7,13 +8,11 @@ type ApiClient = {
|
||||
};
|
||||
|
||||
interface CorrespondentOption {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
type Identifier = string | number;
|
||||
|
||||
interface UseDocumentCorrespondentActionsArgs {
|
||||
apiClient: ApiClient;
|
||||
correspondents: CorrespondentOption[];
|
||||
@@ -135,7 +134,7 @@ const useDocumentCorrespondentActions = ({
|
||||
};
|
||||
|
||||
const handleCorrespondentAdd = useCallback(
|
||||
async ({ document, name, input = null, option = null }: { document?: { id?: string | number }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
|
||||
async ({ document, name, input = null, option = null }: { document?: { id?: string }; name?: string; input?: HTMLInputElement | null; option?: CorrespondentOption | string | null }) => {
|
||||
if (!document?.id) {
|
||||
throw new Error('Missing document for correspondent assignment.');
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import type { DragEvent } from 'react';
|
||||
import { createDocumentEntryKey, createFolderEntryKey } from '../../app/entryKey';
|
||||
import type { FolderId, Identifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderIdentifier = string | 'root';
|
||||
type FolderIdentifier = FolderId | 'root';
|
||||
type FolderInput = FolderIdentifier | number;
|
||||
|
||||
interface DocumentLike {
|
||||
@@ -14,7 +14,7 @@ interface DocumentLike {
|
||||
|
||||
type ApplySelectionFn = (
|
||||
keys: string[],
|
||||
options?: { anchor?: string | null; interactedKeys?: string[] },
|
||||
options?: { anchor: string | null; interactedKeys?: string[] },
|
||||
) => void;
|
||||
|
||||
type HandleEntrySelectionFn = (
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
trashDocument,
|
||||
updateDocument,
|
||||
} from '../../lib/apiClient';
|
||||
import type { DocumentId, FolderId as FolderIdentifier } from '../../types/identifiers';
|
||||
|
||||
type DocumentId = string | number;
|
||||
type FolderId = DocumentId | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
type NullableFolderId = FolderId | null;
|
||||
|
||||
type StatusLevel = 'success' | 'error' | 'info' | string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface TagRecord {
|
||||
id?: Identifier;
|
||||
@@ -205,8 +205,7 @@ const useDocumentTagging = ({
|
||||
if (result?.ok) {
|
||||
const { tagCount, docsCount } = result;
|
||||
setStatusMessage(
|
||||
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${
|
||||
docsCount === 1 ? '' : 's'
|
||||
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${docsCount === 1 ? '' : 's'
|
||||
}.`,
|
||||
'success',
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
||||
import useFileDrop from './useFileDrop';
|
||||
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../app/workspaceUtils';
|
||||
import { fetchDocument } from '../../lib/apiClient';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderId = Identifier | 'root' | null;
|
||||
|
||||
type FileEntry = {
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import DocumentsManager from '../../documents/DocumentsManager';
|
||||
|
||||
type DocumentId = string | number;
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId;
|
||||
|
||||
@@ -52,6 +52,7 @@ import useWorkspaceTaxonomies from './useWorkspaceTaxonomies';
|
||||
import useWorkspaceBreadcrumbs from './useWorkspaceBreadcrumbs';
|
||||
import useWorkspaceDeskProps from './useWorkspaceDeskProps';
|
||||
import useWorkspaceSelectionSync from './useWorkspaceSelectionSync';
|
||||
import type { DocumentId, FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
||||
|
||||
const EntryType = Object.freeze({
|
||||
document: 'document',
|
||||
@@ -60,9 +61,7 @@ const EntryType = Object.freeze({
|
||||
|
||||
const noop = () => { };
|
||||
|
||||
type Identifier = string | number;
|
||||
type DocumentId = Identifier;
|
||||
type FolderId = Identifier | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: DocumentId | null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MutableRefObject, useEffect } from 'react';
|
||||
|
||||
type FolderId = string | number | 'root' | null;
|
||||
type FolderId = string | 'root' | null;
|
||||
|
||||
interface DropOverlayState {
|
||||
active: boolean;
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
createDocumentEntryKey,
|
||||
createFolderEntryKey,
|
||||
} from '../../app/entryKey';
|
||||
import type { FolderId as FolderIdentifier, Identifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderId = Identifier | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: Identifier | null;
|
||||
@@ -126,8 +126,8 @@ const useFolderTree = ({
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
let nextDocKeys = [];
|
||||
let mergedSelection = [];
|
||||
let nextDocKeys: string[] = [];
|
||||
let mergedSelection: string[] = [];
|
||||
|
||||
setSelectedEntries((previous) => {
|
||||
const previousFolderKeys = previous
|
||||
@@ -140,10 +140,12 @@ const useFolderTree = ({
|
||||
});
|
||||
|
||||
const nextFocus = (() => {
|
||||
if (focusedDocumentId) {
|
||||
const currentFocusedKey = createDocumentEntryKey(focusedDocumentId);
|
||||
if (currentFocusedKey && availableDocKeySet.has(currentFocusedKey)) {
|
||||
return focusedDocumentId;
|
||||
}
|
||||
}
|
||||
if (nextDocKeys.length) {
|
||||
const lastDocKey = nextDocKeys[nextDocKeys.length - 1];
|
||||
return getEntryId(lastDocKey) || null;
|
||||
@@ -172,7 +174,7 @@ const useFolderTree = ({
|
||||
if (!targetId || targetId === 'root') {
|
||||
setFolderNodes((prev: Map<FolderId, FolderTreeNode>) => {
|
||||
const root = prev.get('root');
|
||||
if (root?.expanded) return prev;
|
||||
if (!root || root.expanded) return prev;
|
||||
const next = new Map<FolderId, FolderTreeNode>(prev);
|
||||
next.set('root', { ...root, expanded: true });
|
||||
return next;
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
moveFolder as moveFolderRequest,
|
||||
renameFolder as renameFolderRequest,
|
||||
} from '../../lib/apiClient';
|
||||
import type { FolderId } from '../../types/identifiers';
|
||||
|
||||
type FolderId = string | number;
|
||||
type FolderKey = FolderId | 'root';
|
||||
|
||||
interface FolderNode {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MutableRefObject, useCallback, useState } from 'react';
|
||||
import type { TagId, TenantId } from '../../types/identifiers';
|
||||
|
||||
type ApiClient = {
|
||||
get: (path: string) => Promise<{ data: unknown }>
|
||||
@@ -12,7 +13,7 @@ interface TagManagerInterface {
|
||||
}
|
||||
|
||||
interface TagEntry {
|
||||
id?: string | number;
|
||||
id?: TagId;
|
||||
label?: string;
|
||||
color?: string | null;
|
||||
[key: string]: unknown;
|
||||
@@ -23,8 +24,8 @@ interface UseTagsOptions {
|
||||
notifyApiError: (error: unknown, fallback: string) => void;
|
||||
setStatusMessage: (message: string, variant?: string) => void;
|
||||
tagManager: TagManagerInterface;
|
||||
tenantIdRef: MutableRefObject<string | number | null>;
|
||||
setActiveTagFilters: (updater: (prev: Array<string | number>) => Array<string | number>) => void;
|
||||
tenantIdRef: MutableRefObject<TenantId | null>;
|
||||
setActiveTagFilters: (updater: (prev: Array<TagId>) => Array<TagId>) => void;
|
||||
mapDocumentCaches?: (mapper: (doc: any) => any) => void;
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ const useTags = ({
|
||||
}, [apiClient, notifyApiError, tenantIdRef]);
|
||||
|
||||
const handleTagUpdate = useCallback(
|
||||
async (tagId: string | number, changes: { label?: string; color?: string | null }) => {
|
||||
async (tagId: TagId, changes: { label?: string; color?: string | null }) => {
|
||||
if (tagId == null) {
|
||||
throw new Error('Missing tag identifier.');
|
||||
}
|
||||
@@ -104,7 +105,7 @@ const useTags = ({
|
||||
);
|
||||
|
||||
const handleTagDelete = useCallback(
|
||||
async (tagId: string | number) => {
|
||||
async (tagId: TagId) => {
|
||||
if (tagId == null) {
|
||||
throw new Error('Missing tag identifier.');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MutableRefObject, useCallback } from 'react';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import type { FolderId, TenantId } from '../../types/identifiers';
|
||||
|
||||
interface ApiClient {
|
||||
get: (path: string) => Promise<{ data: unknown }>;
|
||||
@@ -8,24 +9,24 @@ interface ApiClient {
|
||||
}
|
||||
|
||||
interface TenantOption {
|
||||
id?: string | number;
|
||||
id?: TenantId;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface UseTenantManagerOptions {
|
||||
apiClient: ApiClient;
|
||||
appDispatch: (action: any) => void;
|
||||
currentTenantId: string | number | null;
|
||||
currentTenantId: TenantId | null;
|
||||
resetWorkspaceState: () => void;
|
||||
setStatusMessage: (message: string, variant?: string) => void;
|
||||
notifyApiError: (error: unknown, message: string) => void;
|
||||
refreshTags: () => Promise<void>;
|
||||
refreshCorrespondents: () => Promise<void>;
|
||||
loadFolder: (folderId: string, options?: { preserveSearch?: boolean }) => Promise<void>;
|
||||
loadFolder: (folderId: FolderId, options?: { preserveSearch?: boolean }) => Promise<void>;
|
||||
handleDocumentsViewModeChange: (mode: string) => void;
|
||||
navigate: NavigateFunction;
|
||||
tokenRef?: MutableRefObject<string | null>;
|
||||
tenantIdRef?: MutableRefObject<string | number | null>;
|
||||
tenantIdRef?: MutableRefObject<TenantId | null>;
|
||||
}
|
||||
|
||||
const useTenantManager = ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
|
||||
import type { FolderId as FolderIdentifier } from '../../types/identifiers';
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderId = Identifier | 'root';
|
||||
type FolderId = FolderIdentifier | 'root';
|
||||
|
||||
interface UseWorkspaceBreadcrumbsArgs {
|
||||
selectedFolder: FolderId | null;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
import { createDocumentEntryKey } from '../../app/entryKey';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface ApplySelectionFn {
|
||||
(keys: string[], options?: { anchor?: string | null; interactedKeys?: string[] }): unknown;
|
||||
(keys: string[], options?: { anchor: string | null; interactedKeys?: string[] }): unknown;
|
||||
}
|
||||
|
||||
interface UseWorkspaceDeskPropsArgs {
|
||||
@@ -17,8 +16,8 @@ interface UseWorkspaceDeskPropsArgs {
|
||||
applySelection: ApplySelectionFn;
|
||||
showingSearchResults: boolean;
|
||||
searchQuery: string;
|
||||
activeTagFilters: Array<string | number>;
|
||||
activeCorrespondentFilters: Array<string | number>;
|
||||
activeTagFilters: Array<string>;
|
||||
activeCorrespondentFilters: Array<string>;
|
||||
selectedFolder: Identifier | 'root' | null;
|
||||
promoteSelectionOrder: () => void;
|
||||
handleDocumentTagDrop: (docId: Identifier, tagId: Identifier) => Promise<void> | void;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { MutableRefObject } from 'react';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface UseWorkspaceSelectionSyncArgs {
|
||||
showingSearchResults: boolean;
|
||||
searchQuery: string;
|
||||
setSelectedEntries: (entries: Array<string | number>) => void;
|
||||
setSelectionOrder: (order: Array<string | number>) => void;
|
||||
selectionOrderRef: MutableRefObject<Array<string | number>>;
|
||||
setSelectedEntries: (entries: Array<string>) => void;
|
||||
setSelectionOrder: (order: Array<string>) => void;
|
||||
selectionOrderRef: MutableRefObject<Array<string>>;
|
||||
selectionAnchorRef: MutableRefObject<Identifier | string | null>;
|
||||
setFocusedDocumentId: (id: Identifier | null) => void;
|
||||
selectedDocumentIds: Identifier[];
|
||||
|
||||
@@ -5,8 +5,7 @@ import TagManager from '../../tag_manager';
|
||||
import useCorrespondents from './useCorrespondents';
|
||||
import useDocumentCorrespondentActions from './useDocumentCorrespondentActions';
|
||||
import useTags from './useTags';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface UseWorkspaceTaxonomiesArgs {
|
||||
apiClient: any;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { resolveAssetUrl } from '../asset_manager';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
type DocumentLike = {
|
||||
id?: Identifier;
|
||||
@@ -27,7 +26,7 @@ type AssetLike = {
|
||||
type EnsureAssetUrl = (
|
||||
documentId: Identifier,
|
||||
asset: AssetLike,
|
||||
options?: { force?: boolean; [key: string]: unknown },
|
||||
options?: { force?: boolean;[key: string]: unknown },
|
||||
) => Promise<unknown>;
|
||||
|
||||
type GetAsset = (document: DocumentLike, assetType: string) => AssetLike | null;
|
||||
@@ -92,7 +91,7 @@ export const useAssetNavigator = ({
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
ensureAssetUrl(documentId, asset, { force: true })
|
||||
.catch(() => {})
|
||||
.catch(() => { })
|
||||
.finally(() => {
|
||||
if (!cancelled) {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Types aligned with OpenAPI schemas for common endpoints.
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
export type Identifier = string | number;
|
||||
export type { Identifier };
|
||||
|
||||
export interface DownloadLink {
|
||||
url: string;
|
||||
|
||||
@@ -12,7 +12,7 @@ const StatusBanner: React.FC<StatusBannerProps> = ({ status }) => {
|
||||
};
|
||||
|
||||
interface TenantOption {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ interface LoginViewProps {
|
||||
tenantSelection?: TenantSelectionState | null;
|
||||
onSelectTenant?: (tenant: TenantOption) => void;
|
||||
onCancelSelection?: () => void;
|
||||
selectingTenantId?: string | number | null;
|
||||
selectingTenantId?: string | null;
|
||||
onPasskeyLogin?: (username: string) => void;
|
||||
onSignup?: (username: string) => void;
|
||||
passkeySupported?: boolean;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DownloadIcon } from '../ui/icons';
|
||||
import PdfViewer from './PdfViewer';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
title?: string;
|
||||
mime_type?: string;
|
||||
filename?: string;
|
||||
@@ -40,7 +40,7 @@ interface DocumentViewerLayoutProps {
|
||||
summaryProps?: Record<string, unknown>;
|
||||
metadataPayload?: unknown;
|
||||
contentTabConfig?: ContentTabConfig | null;
|
||||
resetKey?: string | number | null;
|
||||
resetKey?: string | null;
|
||||
classNamePrefix?: string;
|
||||
defaultTabId?: string;
|
||||
infoPanelProps?: Record<string, unknown>;
|
||||
|
||||
@@ -28,14 +28,15 @@ import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
||||
import DocumentViewerLayout from './DocumentViewerLayout';
|
||||
import useViewerLayoutMode from './useViewerLayoutMode';
|
||||
import { usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||
import type { DocumentId, FolderId } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: string | number;
|
||||
id?: DocumentId;
|
||||
title?: string;
|
||||
mime_type?: string | null;
|
||||
issued_at?: string | null;
|
||||
folder_id?: string | null;
|
||||
correspondents?: Array<{ id?: string | number; name?: string }>;
|
||||
folder_id?: FolderId | null;
|
||||
correspondents?: Array<{ id?: string; name?: string }>;
|
||||
current_version?: {
|
||||
version_number?: number;
|
||||
download?: { url?: string | null; expires_at?: number } | null;
|
||||
@@ -51,7 +52,7 @@ interface DocumentLike {
|
||||
}
|
||||
|
||||
interface AssetLike {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
url?: string | null;
|
||||
metadata?: Record<string, unknown> | null;
|
||||
[key: string]: unknown;
|
||||
@@ -59,16 +60,16 @@ interface AssetLike {
|
||||
|
||||
interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
|
||||
document: DocumentLike | null;
|
||||
ensureAssetUrl?: (docId: string | number, asset: AssetLike, options?: { force?: boolean }) => Promise<unknown>;
|
||||
ensureAssetUrl?: (docId: DocumentId, asset: AssetLike, options?: { force?: boolean }) => Promise<unknown>;
|
||||
getDocumentAsset?: (doc: DocumentLike | null, type: string) => AssetLike | null;
|
||||
ensurePreviewData?: (docId: string | number, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>;
|
||||
ensurePreviewData?: (docId: DocumentId, options?: { signal?: AbortSignal }) => Promise<DocumentLike | null>;
|
||||
notifyApiError?: (error: unknown, fallbackMessage?: string) => void;
|
||||
sidebarToggle?: ReactNode;
|
||||
onClosePanel?: () => void;
|
||||
resolveFolderPath?: (doc: DocumentLike | null) => Array<{ id?: string | number; name?: string }>;
|
||||
resolveFolderPath?: (doc: DocumentLike | null) => Array<{ id?: string; name?: string }>;
|
||||
variant?: 'viewer' | 'sidebar';
|
||||
onCollapsePanel?: () => void;
|
||||
onMaximizePanel?: (args: { documentIds: Array<string | number> }) => void;
|
||||
onMaximizePanel?: (args: { documentIds: Array<string> }) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +164,7 @@ const DocumentViewerPanel: React.FC<DocumentViewerPanelProps> = ({
|
||||
}, [document, getDocumentAsset]);
|
||||
|
||||
const navigateToFolder = useCallback(
|
||||
(folderId) => {
|
||||
(folderId: FolderId | null) => {
|
||||
const target = folderId == null
|
||||
? '/documents'
|
||||
: `/documents/folder/${folderId}`;
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useAppShell } from '../appShellContext';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentViewerRouteContext {
|
||||
previewWorkspaceDocument?: { id?: Identifier } | null;
|
||||
|
||||
@@ -7,9 +7,7 @@ import {
|
||||
} from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import { CheckIcon, ChevronDownIcon } from '../../ui/icons';
|
||||
|
||||
|
||||
type CapabilityValue = string | number;
|
||||
import type { CapabilityValue } from '../../types/identifiers';
|
||||
|
||||
export interface CapabilityDropdownOption {
|
||||
value?: CapabilityValue | null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import type { ChangeEvent, FormEvent } from 'react';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
interface ApiTokenEntry {
|
||||
id?: Identifier;
|
||||
|
||||
@@ -8,10 +8,7 @@ import React, {
|
||||
import type { SettingsSectionConfig } from '../SettingsModal';
|
||||
import { IconX } from '../../ui/icons';
|
||||
import CapabilityDropdown, { CapabilityDropdownOption } from '../components/CapabilityDropdown';
|
||||
|
||||
|
||||
type CapabilityValue = string | number;
|
||||
type CapabilitySetId = string | number;
|
||||
import type { CapabilitySetId, CapabilityValue } from '../../types/identifiers';
|
||||
|
||||
interface CapabilitySet {
|
||||
id: CapabilitySetId;
|
||||
|
||||
@@ -18,10 +18,10 @@ interface PasskeysSectionProps {
|
||||
passkeysSupported?: boolean | null;
|
||||
passkeysLoading?: boolean;
|
||||
registeringPasskey?: boolean;
|
||||
revokingPasskeyId?: string | number | null;
|
||||
revokingPasskeyId?: string | null;
|
||||
onRefreshPasskeys?: () => void | Promise<void>;
|
||||
onRegisterPasskey?: (args: { nickname?: string }) => Promise<RegisterPasskeyResult | undefined>;
|
||||
onRevokePasskey?: (id: string | number, reason?: string) => Promise<void>;
|
||||
onRevokePasskey?: (id: string, reason?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
const PasskeysSection = ({
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
regenerateApiToken,
|
||||
type ApiTokenRecord,
|
||||
} from '../lib/apiClient';
|
||||
import type { ApiTokenId, CapabilitySetId } from '../types/identifiers';
|
||||
|
||||
interface ApiTokensResponse {
|
||||
token_info?: ApiTokenRecord;
|
||||
@@ -15,7 +16,7 @@ interface ApiTokensResponse {
|
||||
interface CreateTokenArgs {
|
||||
label?: string;
|
||||
expires_at?: string;
|
||||
capability_set_id?: string | number;
|
||||
capability_set_id?: CapabilitySetId;
|
||||
}
|
||||
|
||||
interface UseApiTokensArgs {
|
||||
@@ -28,13 +29,13 @@ interface UseApiTokensResult {
|
||||
tokens: ApiTokenRecord[];
|
||||
loading: boolean;
|
||||
creating: boolean;
|
||||
deletingId: string | number | null;
|
||||
regeneratingId: string | number | null;
|
||||
deletingId: ApiTokenId | null;
|
||||
regeneratingId: ApiTokenId | null;
|
||||
createdSecret: string | null;
|
||||
refresh: () => Promise<void>;
|
||||
create: (args?: CreateTokenArgs) => Promise<ApiTokensResponse | false>;
|
||||
revoke: (tokenId?: string | number | null) => Promise<boolean>;
|
||||
regenerate: (tokenId?: string | number | null) => Promise<boolean>;
|
||||
revoke: (tokenId?: ApiTokenId | null) => Promise<boolean>;
|
||||
regenerate: (tokenId?: ApiTokenId | null) => Promise<boolean>;
|
||||
dismissSecret: () => void;
|
||||
}
|
||||
|
||||
@@ -42,8 +43,8 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
|
||||
const [tokens, setTokens] = useState<ApiTokenRecord[]>([]);
|
||||
const [loading] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [deletingId, setDeletingId] = useState<string | number | null>(null);
|
||||
const [regeneratingId, setRegeneratingId] = useState<string | number | null>(null);
|
||||
const [deletingId, setDeletingId] = useState<ApiTokenId | null>(null);
|
||||
const [regeneratingId, setRegeneratingId] = useState<ApiTokenId | null>(null);
|
||||
const [createdSecret, setCreatedSecret] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
@@ -65,7 +66,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
|
||||
}
|
||||
setCreating(true);
|
||||
try {
|
||||
const payload: { capability_set_id: string | number; label?: string; expires_at?: string } = { capability_set_id };
|
||||
const payload: { capability_set_id: CapabilitySetId; label?: string; expires_at?: string } = { capability_set_id };
|
||||
if (label) {
|
||||
payload.label = label;
|
||||
}
|
||||
@@ -100,7 +101,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
|
||||
);
|
||||
|
||||
const revoke = useCallback(
|
||||
async (tokenId?: string | number | null) => {
|
||||
async (tokenId?: string | null) => {
|
||||
if (!tokenId) {
|
||||
return false;
|
||||
}
|
||||
@@ -121,7 +122,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
|
||||
);
|
||||
|
||||
const regenerate = useCallback(
|
||||
async (tokenId?: string | number | null) => {
|
||||
async (tokenId?: string | null) => {
|
||||
if (!tokenId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import {
|
||||
listCapabilitySets,
|
||||
updateCapabilitySet as updateCapabilitySetRequest,
|
||||
} from '../lib/apiClient';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface CapabilitySet {
|
||||
id?: Identifier;
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
listPasskeys,
|
||||
startPasskeyRegistration,
|
||||
} from '../lib/apiClient';
|
||||
import type { PasskeyId } from '../types/identifiers';
|
||||
|
||||
type StatusMessageFn = (message: string, variant?: string) => void;
|
||||
type NotifyApiErrorFn = (error: unknown, message: string) => void;
|
||||
@@ -27,7 +28,7 @@ type ApiError = {
|
||||
};
|
||||
|
||||
export interface PasskeyRecord {
|
||||
id?: string | number;
|
||||
id?: PasskeyId;
|
||||
nickname?: string;
|
||||
created_at?: string;
|
||||
createdAt?: string;
|
||||
@@ -79,11 +80,11 @@ interface UsePasskeysResult {
|
||||
passkeysSupported: boolean | null;
|
||||
passkeysLoading: boolean;
|
||||
registeringPasskey: boolean;
|
||||
revokingPasskeyId: string | number | null;
|
||||
revokingPasskeyId: PasskeyId | null;
|
||||
refreshPasskeys: () => Promise<void>;
|
||||
registerPasskey: (options?: { nickname?: string }) => Promise<RegisterPasskeyResult>;
|
||||
revokePasskey: (
|
||||
passkeyId: string | number,
|
||||
passkeyId: PasskeyId,
|
||||
reason?: string,
|
||||
) => Promise<RevokePasskeyResult>;
|
||||
}
|
||||
@@ -93,7 +94,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
|
||||
const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
|
||||
const [passkeysLoading, setPasskeysLoading] = useState(false);
|
||||
const [registeringPasskey, setRegisteringPasskey] = useState(false);
|
||||
const [revokingPasskeyId, setRevokingPasskeyId] = useState<string | number | null>(null);
|
||||
const [revokingPasskeyId, setRevokingPasskeyId] = useState<PasskeyId | null>(null);
|
||||
|
||||
const refreshPasskeys = useCallback(async (): Promise<void> => {
|
||||
if (!token) {
|
||||
@@ -191,7 +192,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
|
||||
|
||||
const revokePasskey = useCallback(
|
||||
async (
|
||||
passkeyId: string | number,
|
||||
passkeyId: PasskeyId,
|
||||
reason?: string,
|
||||
): Promise<RevokePasskeyResult> => {
|
||||
if (passkeyId == null) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import { getTagColorStyle } from '../utils/colors';
|
||||
import { useSidebarContext } from './SidebarContext';
|
||||
import { usePanelManager, usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||
import type { StatusMessage } from '../hooks/documents/store/useDocumentsStore';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface CommunityLink {
|
||||
label: string;
|
||||
@@ -60,7 +61,6 @@ const COMMUNITY_LINKS: CommunityLink[] = [
|
||||
},
|
||||
];
|
||||
|
||||
type Identifier = string | number;
|
||||
type FolderIdentifier = Identifier | 'root';
|
||||
|
||||
interface FolderTreeNode {
|
||||
@@ -925,8 +925,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-tag-cloud${
|
||||
activeTagSet.size ? ' sidebar-tag-cloud--has-active' : ''
|
||||
className={`sidebar-tag-cloud${activeTagSet.size ? ' sidebar-tag-cloud--has-active' : ''
|
||||
}`}
|
||||
role="list"
|
||||
>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { useMemo } from 'react';
|
||||
import type { DragEvent } from 'react';
|
||||
import { TAG_FILTER_UNTAGGED } from '../app/workspaceUtils';
|
||||
import type { StatusMessage } from '../hooks/documents/store/useDocumentsStore';
|
||||
|
||||
type Identifier = string | number;
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface FolderTreeNode {
|
||||
id: Identifier;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Common string-based identifiers used across the app.
|
||||
export type Identifier = string;
|
||||
|
||||
export type DocumentId = Identifier;
|
||||
export type FolderId = Identifier;
|
||||
export type CapabilitySetId = Identifier;
|
||||
export type CapabilityValue = Identifier;
|
||||
export type TenantId = Identifier;
|
||||
export type TagId = Identifier;
|
||||
export type ApiTokenId = Identifier;
|
||||
export type PasskeyId = Identifier;
|
||||
@@ -4,10 +4,10 @@ import { PlusIcon } from './icons';
|
||||
|
||||
import useFloatingMenu from './useFloatingMenu';
|
||||
|
||||
type QuickAddOption = string | number | { id?: string | number; label?: string; name?: string;[key: string]: unknown };
|
||||
type QuickAddOption = string | { id?: string; label?: string; name?: string;[key: string]: unknown };
|
||||
|
||||
interface NormalizedOption {
|
||||
id?: string | number;
|
||||
id?: string;
|
||||
label: string;
|
||||
original: QuickAddOption;
|
||||
index: number;
|
||||
@@ -47,7 +47,7 @@ interface FloatingMenuState {
|
||||
updatePosition: () => void;
|
||||
}
|
||||
|
||||
type CSSVarStyle = CSSProperties & Record<string, string | number>;
|
||||
type CSSVarStyle = CSSProperties & Record<string, string>;
|
||||
|
||||
export interface QuickAddMenuProps {
|
||||
onSelectOption?: (value: QuickAddOption, normalized: NormalizedOption) => Promise<void> | void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const ensureDate = (value: string | number | Date | null): Date | null => {
|
||||
const ensureDate = (value: string | Date | null): Date | null => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ interface FormatOptions {
|
||||
options?: Intl.DateTimeFormatOptions;
|
||||
}
|
||||
|
||||
export const formatDate = (value: string | number | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
|
||||
export const formatDate = (value: string | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
|
||||
const date = ensureDate(value);
|
||||
if (!date) {
|
||||
return fallback;
|
||||
@@ -20,7 +20,7 @@ export const formatDate = (value: string | number | Date | null, { fallback = '
|
||||
return date.toLocaleDateString(locale, options);
|
||||
};
|
||||
|
||||
export const formatDateTime = (value: string | number | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
|
||||
export const formatDateTime = (value: string | Date | null, { fallback = '—', locale, options }: FormatOptions = {}): string => {
|
||||
const date = ensureDate(value);
|
||||
if (!date) {
|
||||
return fallback;
|
||||
@@ -28,7 +28,7 @@ export const formatDateTime = (value: string | number | Date | null, { fallback
|
||||
return date.toLocaleString(locale, options);
|
||||
};
|
||||
|
||||
export const toDateInputValue = (value: string | number | Date | null): string => {
|
||||
export const toDateInputValue = (value: string | Date | null): string => {
|
||||
const date = ensureDate(value);
|
||||
if (!date) {
|
||||
return '';
|
||||
@@ -38,7 +38,7 @@ export const toDateInputValue = (value: string | number | Date | null): string =
|
||||
return localDate.toISOString().slice(0, 10);
|
||||
};
|
||||
|
||||
export const toIssuedTimestamp = (dateString: string | null, fallback: string | number | Date | null): string | null => {
|
||||
export const toIssuedTimestamp = (dateString: string | null, fallback: string | Date | null): string | null => {
|
||||
if (!dateString) {
|
||||
return null;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export const toIssuedTimestamp = (dateString: string | null, fallback: string |
|
||||
return Number.isNaN(candidate.getTime()) ? null : candidate.toISOString();
|
||||
};
|
||||
|
||||
export const parseDateValue = (value: string | number | Date | null): Date | null => ensureDate(value);
|
||||
export const parseDateValue = (value: string | Date | null): Date | null => ensureDate(value);
|
||||
|
||||
export default {
|
||||
formatDate,
|
||||
|
||||
@@ -12,9 +12,9 @@ export interface DocumentLike extends AssetManagerDocumentLike {
|
||||
|
||||
export type AssetLike = AssetManagerAssetLike;
|
||||
|
||||
export type EnsurePreviewData = (id: string | number) => Promise<DocumentLike | null>;
|
||||
export type EnsurePreviewData = (id: string) => Promise<DocumentLike | null>;
|
||||
export type EnsureAssetUrl = (
|
||||
id: string | number,
|
||||
id: string,
|
||||
asset: AssetLike,
|
||||
options?: { force?: boolean },
|
||||
) => Promise<AssetLike | null>;
|
||||
|
||||
Reference in New Issue
Block a user