feat: Refactor utility and component exports, update dependencies, and add Knip for unused code detection.
This commit is contained in:
@@ -41,5 +41,3 @@ export const useApi = (): ApiContextValue => {
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export const getHttpClient = (): HttpClient => httpClient;
|
||||
|
||||
@@ -421,5 +421,3 @@ export const usePanelResizeBindings = (
|
||||
isPanelResizing: resizingPanel === panel,
|
||||
};
|
||||
};
|
||||
|
||||
export default PanelManagerContext;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import type useWorkspaceSelection from './useWorkspaceSelection';
|
||||
import type { useWorkspaceSelection } from './useWorkspaceSelection';
|
||||
|
||||
export type WorkspaceSelectionValue = ReturnType<typeof useWorkspaceSelection>;
|
||||
|
||||
@@ -23,5 +23,3 @@ export const useWorkspaceSelectionContext = () => {
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default WorkspaceSelectionContext;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
export interface DetailDocument {
|
||||
interface DetailDocument {
|
||||
id?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -102,5 +102,3 @@ export const useDetailPanel = ({
|
||||
setDetailPanelOpen,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDetailPanel;
|
||||
|
||||
@@ -285,5 +285,3 @@ export const useDocumentSelection = ({
|
||||
configureSelectionEnvironment,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDocumentSelection;
|
||||
|
||||
@@ -231,5 +231,3 @@ export const useManagementModals = ({
|
||||
activeModal,
|
||||
};
|
||||
};
|
||||
|
||||
export default useManagementModals;
|
||||
|
||||
@@ -115,5 +115,3 @@ export const useWorkspaceSelection = ({
|
||||
inspectFolder,
|
||||
};
|
||||
};
|
||||
|
||||
export default useWorkspaceSelection;
|
||||
|
||||
@@ -7,17 +7,17 @@ import { usePanelManager } from './PanelManagerContext';
|
||||
import { FolderManagerProvider } from '../folders/FolderManagerContext';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
export type EnsureAssetUrl = (
|
||||
type EnsureAssetUrl = (
|
||||
docId: Identifier,
|
||||
asset: unknown,
|
||||
options?: Record<string, unknown>,
|
||||
) => Promise<unknown> | void;
|
||||
export type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
||||
export type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
||||
export type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
||||
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
||||
type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
|
||||
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
|
||||
|
||||
export type DocumentsTableProps = ComponentProps<typeof DocumentsPanel> | null;
|
||||
export type DetailPanelProps = (ComponentProps<typeof DocumentViewerPanel> & {
|
||||
type DocumentsTableProps = ComponentProps<typeof DocumentsPanel> | null;
|
||||
type DetailPanelProps = (ComponentProps<typeof DocumentViewerPanel> & {
|
||||
onClose?: () => void;
|
||||
onOpenPreview?: (args: { documentIds: Array<string> }) => void;
|
||||
folderNodes?: Map<Identifier | 'root', unknown>;
|
||||
@@ -27,7 +27,7 @@ export type DetailPanelProps = (ComponentProps<typeof DocumentViewerPanel> & {
|
||||
) => Promise<void>;
|
||||
}) | null;
|
||||
|
||||
export type WorkspaceSurface = { content: ReactNode; detail?: ReactNode | null; detailMode?: 'overlay' | 'inline' | null } | null;
|
||||
type WorkspaceSurface = { content: ReactNode; detail?: ReactNode | null; detailMode?: 'overlay' | 'inline' | null } | null;
|
||||
|
||||
export interface UseWorkspaceSurfaceArgs {
|
||||
sidebarHidden?: boolean;
|
||||
@@ -44,7 +44,7 @@ export interface UseWorkspaceSurfaceArgs {
|
||||
closeDocumentPreview?: () => void;
|
||||
}
|
||||
|
||||
export interface UseWorkspaceSurfaceResult {
|
||||
interface UseWorkspaceSurfaceResult {
|
||||
surface: WorkspaceSurface;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
export type AppShellContextValue = Record<string, unknown>;
|
||||
type AppShellContextValue = Record<string, unknown>;
|
||||
|
||||
export const AppShellContext = React.createContext<AppShellContextValue | null>(null);
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { DocumentVersion, Document } from './types/documents';
|
||||
|
||||
type Nullable<T> = T | null;
|
||||
|
||||
export type { Asset, DocumentVersion as DocumentVersionLike, Document };
|
||||
export type { Asset };
|
||||
|
||||
export const resolveAssetExpiresAt = (asset?: { download?: { expires_at: number } | null } | null): number | null =>
|
||||
const resolveAssetExpiresAt = (asset?: { download?: { expires_at: number } | null } | null): number | null =>
|
||||
asset?.download?.expires_at ?? null;
|
||||
|
||||
export const resolveAssetUrl = (asset?: { download?: { url: string } | null } | null): string | null =>
|
||||
@@ -20,7 +20,7 @@ export type EnsureAssetUrl = (
|
||||
|
||||
export type GetAsset = (document: Document, assetType: string) => Nullable<Asset>;
|
||||
|
||||
export const getAssetFromGroup = (
|
||||
const getAssetFromGroup = (
|
||||
assets?: Asset[] | Record<string, Asset> | null,
|
||||
assetType: string = '',
|
||||
): Nullable<Asset> => {
|
||||
|
||||
@@ -1,46 +1,3 @@
|
||||
export const DB_NAME = 'papercrate_desk';
|
||||
export const DB_VERSION = 1;
|
||||
export const LAYOUT_STORE = 'layouts';
|
||||
|
||||
export const CLICK_ACTIONS = {
|
||||
selectSingle: 'selectSingle',
|
||||
openDetail: 'openDetail',
|
||||
addCard: 'addCard',
|
||||
addStack: 'addStack',
|
||||
none: 'none',
|
||||
} as const;
|
||||
|
||||
export const DRAG_ACTIONS = {
|
||||
dragSelectSingle: 'dragSelectSingle',
|
||||
dragSelection: 'dragSelection',
|
||||
dragSelectStack: 'dragSelectStack',
|
||||
none: 'none',
|
||||
} as const;
|
||||
|
||||
export const STACK_HIT_EPSILON = 4;
|
||||
export const POINTER_DRAG_THRESHOLD_SQUARED = 16;
|
||||
export const LONG_PRESS_DURATION_MS = 450;
|
||||
|
||||
export const DRAG_HYSTERESIS_PX = 4;
|
||||
export const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
|
||||
export const EDGE_COLLISION_THRESHOLD = 0.5;
|
||||
|
||||
export const DEBUG_DRAG = false;
|
||||
export const DEBUG_FOCUS = false;
|
||||
|
||||
export const DESK_CANVAS_PADDING = 24;
|
||||
export const DESK_ROTATION_RANGE = 7;
|
||||
export const DESK_DEFAULT_CANVAS_WIDTH = 1024;
|
||||
export const DESK_DEFAULT_CANVAS_HEIGHT = 680;
|
||||
export const DESK_CARD_MIN = 240;
|
||||
export const DESK_CARD_MAX = 340;
|
||||
export const CARD_PAGE_WEIGHT_GRAMS = 5;
|
||||
export const CARD_BASE_WEIGHT_GRAMS = 30;
|
||||
export const DEFAULT_Z_START = 10;
|
||||
export const MIN_TIMESTEP = 1 / 120;
|
||||
export const MAX_TIMESTEP = 1 / 20;
|
||||
export const MAX_DYNAMIC_ROTATION = 7;
|
||||
export const MAX_ANGULAR_VELOCITY = 210;
|
||||
export const ANGULAR_DAMPING = 10;
|
||||
export const TORQUE_TO_ACCELERATION = 0.007;
|
||||
export const SETTLE_ANGULAR_VELOCITY = 0.45;
|
||||
|
||||
@@ -3,19 +3,19 @@ import ApiTokensSection from '../settings/sections/ApiTokensSection';
|
||||
import CapabilitySetsSection from '../settings/sections/CapabilitySetsSection';
|
||||
import PasskeysSection from '../settings/sections/PasskeysSection';
|
||||
|
||||
export const PASSKEYS_SECTION: SettingsSectionConfig = {
|
||||
const PASSKEYS_SECTION: SettingsSectionConfig = {
|
||||
id: 'passkeys',
|
||||
label: 'Passkeys',
|
||||
component: PasskeysSection,
|
||||
};
|
||||
|
||||
export const API_TOKENS_SECTION: SettingsSectionConfig = {
|
||||
const API_TOKENS_SECTION: SettingsSectionConfig = {
|
||||
id: 'apiTokens',
|
||||
label: 'API tokens',
|
||||
component: ApiTokensSection,
|
||||
};
|
||||
|
||||
export const CAPABILITY_SETS_SECTION: SettingsSectionConfig = {
|
||||
const CAPABILITY_SETS_SECTION: SettingsSectionConfig = {
|
||||
id: 'capabilitySets',
|
||||
label: 'Capability sets',
|
||||
component: CapabilitySetsSection,
|
||||
|
||||
@@ -2,7 +2,6 @@ export const SIDEBAR_COLLAPSE_STORAGE_KEY = 'papercrate_sidebar_collapsed';
|
||||
export const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
||||
export const DEFAULT_NEUTRAL_HUE = 29;
|
||||
export const DEFAULT_NEUTRAL_CHROMA = 0.44;
|
||||
export const DEFAULT_NEUTRAL_CONTRAST = 0.56;
|
||||
export const DEFAULT_THEME_MODE = 'system';
|
||||
export const THEME_MODES = ['system', 'light', 'dark'];
|
||||
export const THEME_MODE_LABELS = {
|
||||
|
||||
@@ -8,5 +8,3 @@ export const VIEW_MODE_STORAGE_KEY = 'papercrate_view_mode';
|
||||
export const SORT_FIELD_STORAGE_KEY = 'papercrate_sort_field';
|
||||
export const SORT_DIRECTION_STORAGE_KEY = 'papercrate_sort_direction';
|
||||
export const INCLUDE_DESCENDANTS_STORAGE_KEY = 'papercrate_include_descendants';
|
||||
|
||||
export const ROOT_FOLDER_LABEL = DEFAULT_FOLDER_NAME;
|
||||
|
||||
@@ -2,9 +2,9 @@ import React, { createContext, useContext, useCallback } from 'react';
|
||||
import type { Document } from '../types/documents';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
export type DocumentOpenTarget = 'preview' | 'sidepanel' | 'viewer';
|
||||
type DocumentOpenTarget = 'preview' | 'sidepanel' | 'viewer';
|
||||
|
||||
export interface DocumentOpenContextValue {
|
||||
interface DocumentOpenContextValue {
|
||||
openDocument: (doc: Document, target?: DocumentOpenTarget) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { createContext, useContext, useState, useCallback, useEffect, use
|
||||
|
||||
export type ToastVariant = 'info' | 'success' | 'error';
|
||||
|
||||
export interface ToastMessage {
|
||||
interface ToastMessage {
|
||||
id: string;
|
||||
message: string;
|
||||
variant: ToastVariant;
|
||||
|
||||
@@ -6,7 +6,7 @@ interface CorrespondentUsage {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface CorrespondentEntry {
|
||||
interface CorrespondentEntry {
|
||||
id?: string;
|
||||
name?: string;
|
||||
usage?: CorrespondentUsage;
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
import { LayoutStore, LayoutCard } from './LayoutSystem';
|
||||
import DesktopDocumentCard from './DesktopDocumentCard';
|
||||
import usePreviewMetadata from './hooks/usePreviewMetadata';
|
||||
import useDeskTagInteractions from './tags/useDeskTagInteractions';
|
||||
import { useDeskTagInteractions } from './tags/useDeskTagInteractions';
|
||||
import '../styles/workspace/workspace-layout.css';
|
||||
import '../styles/workspace/workspace-items.css';
|
||||
import '../styles/workspace/workspace-cards.css';
|
||||
@@ -32,7 +32,7 @@ const computeFallbackCardSize = (_doc: Document, defaultSize: number = 200): Doc
|
||||
return { width: size, height: size, source: 'fallback' };
|
||||
};
|
||||
|
||||
export interface DesktopWorkspaceProps {
|
||||
interface DesktopWorkspaceProps {
|
||||
entries: DocumentsListEntry[];
|
||||
ensureAssetUrl?: (...args: any[]) => Promise<unknown>;
|
||||
getDocumentAsset?: (...args: any[]) => unknown;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { constrainDimensions, getInitialPosition, CONTAINER_PADDING } from './ut
|
||||
import { fetchLayoutRecords, upsertLayoutRecords } from './db';
|
||||
import { CardPhysics } from './CardPhysics';
|
||||
|
||||
export interface LayoutCardState {
|
||||
interface LayoutCardState {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
@@ -47,26 +47,6 @@ const requestToPromise = <T>(request: IDBRequest<T>, defaultValue: T): Promise<T
|
||||
};
|
||||
});
|
||||
|
||||
const iterateCursor = (request: IDBRequest<IDBCursorWithValue | null>, iteratee: (cursor: IDBCursorWithValue) => void) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
request.onsuccess = (event) => {
|
||||
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
||||
if (!cursor) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
iteratee(cursor);
|
||||
cursor.continue();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
request.onerror = () => {
|
||||
reject(request.error || new Error('IndexedDB cursor failed'));
|
||||
};
|
||||
});
|
||||
|
||||
const transactionComplete = (transaction: IDBTransaction) =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
transaction.oncomplete = () => {
|
||||
@@ -182,36 +162,3 @@ export const upsertLayoutRecords = async ({
|
||||
console.warn('[desk] Failed to upsert layout records', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteTenantLayouts = async (tenantId?: TenantId) => {
|
||||
if (!tenantId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await withStore('readwrite', (store) => {
|
||||
const index = store.index('tenantIdx');
|
||||
const request = index.openCursor(tenantId);
|
||||
return iterateCursor(request, (cursor) => {
|
||||
cursor.delete();
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('[desk] Failed to clean tenant layouts', error);
|
||||
}
|
||||
};
|
||||
|
||||
export const closeDeskDatabase = (): void => {
|
||||
if (!currentDbPromise.value) {
|
||||
return;
|
||||
}
|
||||
currentDbPromise.value = currentDbPromise.value.then((db) => {
|
||||
if (db) {
|
||||
try {
|
||||
db.close();
|
||||
} catch (error) {
|
||||
console.warn('[desk] Failed to close IndexedDB', error);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -239,5 +239,3 @@ export const useDeskTagInteractions = ({
|
||||
handleCanvasDrop,
|
||||
};
|
||||
};
|
||||
|
||||
export default useDeskTagInteractions;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NBSP } from '../constants/ui';
|
||||
|
||||
export interface CorrespondentLinkEntry {
|
||||
interface CorrespondentLinkEntry {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
key?: string;
|
||||
|
||||
@@ -147,7 +147,7 @@ const normalizeQuickAddOption = (option?: QuickAddOption | string | null): Quick
|
||||
};
|
||||
};
|
||||
|
||||
export const TagSection: React.FC<TagSectionProps> = ({
|
||||
const TagSection: React.FC<TagSectionProps> = ({
|
||||
tags = [],
|
||||
onRemove,
|
||||
onAdd,
|
||||
@@ -292,7 +292,7 @@ export const TagSection: React.FC<TagSectionProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const CorrespondentSection: React.FC<CorrespondentSectionProps> = ({
|
||||
const CorrespondentSection: React.FC<CorrespondentSectionProps> = ({
|
||||
entries = [],
|
||||
onRemove,
|
||||
onAdd,
|
||||
|
||||
@@ -187,4 +187,3 @@ const DocumentThumbnailImage = ({
|
||||
};
|
||||
|
||||
export default DocumentThumbnailImage;
|
||||
export { DEFAULT_THUMBNAIL_SIZE };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { shallowEqual } from 'react-redux';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
export type ManagedDocument = { id?: DocumentId | null } & Record<string, unknown>;
|
||||
type ManagedDocument = { id?: DocumentId | null } & Record<string, unknown>;
|
||||
|
||||
type FetchDocument = (id: DocumentId) => Promise<unknown>;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { CSSProperties, useCallback, useEffect, useMemo, useRef, useState
|
||||
import useFloatingMenu from '../ui/useFloatingMenu';
|
||||
import { CheckIcon, CircleDashedCheckIcon, PlusIcon } from '../ui/icons';
|
||||
|
||||
export type AssignmentState = 'all' | 'partial' | 'none';
|
||||
type AssignmentState = 'all' | 'partial' | 'none';
|
||||
|
||||
export interface SelectionAssignmentMenuItem {
|
||||
id?: string;
|
||||
@@ -24,7 +24,7 @@ export interface NormalizedSelectionAssignmentItem {
|
||||
payload: unknown;
|
||||
}
|
||||
|
||||
export interface SelectionAssignmentMenuProps {
|
||||
interface SelectionAssignmentMenuProps {
|
||||
label: React.ReactNode;
|
||||
items?: SelectionAssignmentMenuItem[];
|
||||
placeholder?: string;
|
||||
|
||||
@@ -53,7 +53,7 @@ interface BulkCorrespondentRemoveArgs {
|
||||
documentIds: DocumentId[];
|
||||
}
|
||||
|
||||
export interface SelectionFloatingActionsProps {
|
||||
interface SelectionFloatingActionsProps {
|
||||
selectionCount?: number;
|
||||
selectedDocumentIds?: SelectedIdList;
|
||||
selectedFolderIds?: SelectedIdList;
|
||||
@@ -487,7 +487,7 @@ const SelectionFloatingActions: React.FC<SelectionFloatingActionsProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export type SelectionFloatingPanelProps = Omit<SelectionFloatingActionsProps, 'selectedDocumentIds' | 'selectedFolderIds' | 'selectionCount'>;
|
||||
type SelectionFloatingPanelProps = Omit<SelectionFloatingActionsProps, 'selectedDocumentIds' | 'selectedFolderIds' | 'selectionCount'>;
|
||||
|
||||
export const SelectionFloatingPanel: React.FC<SelectionFloatingPanelProps> = ({ onClearSelection, ...rest }) => {
|
||||
const { selectedDocumentIds, selectedFolderIds, clearSelection } = useWorkspaceSelectionContext();
|
||||
@@ -512,6 +512,3 @@ export const SelectionFloatingPanel: React.FC<SelectionFloatingPanelProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SelectionFloatingActions;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import type { FolderTreeNode } from '../lib/apiTypes';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
export interface SelectionFolderMenuProps {
|
||||
interface SelectionFolderMenuProps {
|
||||
label: React.ReactNode;
|
||||
folderTree?: FolderTreeNode[];
|
||||
onSelectFolder?: (folderId: DocumentId | null) => Promise<void> | void;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { type DragEvent } from 'react';
|
||||
|
||||
export interface EntryShellHandlers {
|
||||
interface EntryShellHandlers {
|
||||
onClick: (event: React.MouseEvent) => void;
|
||||
onDoubleClick: (event: React.MouseEvent) => void;
|
||||
onDragStart: (event: DragEvent<HTMLElement>) => void;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface IconSizeSliderProps {
|
||||
scale: number;
|
||||
onChange: (scale: number) => void;
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
const IconSizeSlider: React.FC<IconSizeSliderProps> = ({
|
||||
scale,
|
||||
onChange,
|
||||
min = 0.5,
|
||||
max = 2,
|
||||
}) => {
|
||||
return (
|
||||
<div className="icon-size-slider">
|
||||
<input
|
||||
type="range"
|
||||
className="icon-size-slider__input"
|
||||
min={min}
|
||||
max={max}
|
||||
step="any"
|
||||
value={scale}
|
||||
onChange={(e) => onChange(parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconSizeSlider;
|
||||
@@ -35,5 +35,3 @@ export const useDocumentsFilter = (): DocumentsFilterValue => {
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export default DocumentsFilterContext;
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
export interface CorrespondentReference {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
export interface ResolvedCorrespondent {
|
||||
interface ResolvedCorrespondent {
|
||||
id?: string | null;
|
||||
name: string;
|
||||
key: string;
|
||||
@@ -44,5 +38,3 @@ export const resolveCorrespondents = (doc?: Document | null): ResolvedCorrespond
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
export default resolveCorrespondents;
|
||||
|
||||
@@ -6,8 +6,6 @@ import type {
|
||||
} from '../utils/ocr';
|
||||
import type { Document } from '../types/documents';
|
||||
|
||||
export type { Document };
|
||||
|
||||
const asyncFalse = async () => false;
|
||||
|
||||
const resolveDocumentDownloadHref = (document?: Document | null): string | null => {
|
||||
@@ -82,8 +80,3 @@ export const createDocumentActionState = ({
|
||||
openOcr,
|
||||
};
|
||||
};
|
||||
|
||||
export const documentActionsTestExports = {
|
||||
resolveDocumentDownloadHref,
|
||||
hasDocumentTextContentAsset,
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ interface DescribeSummaryOptions {
|
||||
formatDateTime?: typeof defaultFormatDateTime;
|
||||
}
|
||||
|
||||
export type DocumentSummaryRowType = 'text' | 'editable-title' | 'editable-issued' | 'tags' | 'correspondents' | 'folder';
|
||||
type DocumentSummaryRowType = 'text' | 'editable-title' | 'editable-issued' | 'tags' | 'correspondents' | 'folder';
|
||||
|
||||
export interface DocumentSummaryRow {
|
||||
key: string;
|
||||
@@ -17,7 +17,7 @@ export interface DocumentSummaryRow {
|
||||
kind?: DocumentSummaryRowType;
|
||||
}
|
||||
|
||||
export type DocumentSummary = DocumentSummaryRow[];
|
||||
type DocumentSummary = DocumentSummaryRow[];
|
||||
|
||||
const coercePageCount = (metadata?: { page_count?: number | string | null } | null): number | null => {
|
||||
const raw = metadata?.page_count;
|
||||
|
||||
@@ -2,20 +2,20 @@ import { useMemo } from 'react';
|
||||
import type { WorkspaceSelectionValue } from '../../app/WorkspaceSelectionContext';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
export interface Breadcrumb {
|
||||
interface Breadcrumb {
|
||||
id?: Identifier;
|
||||
name?: string;
|
||||
label?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface FolderClickHandlers {
|
||||
interface FolderClickHandlers {
|
||||
onDrop?: (...args: unknown[]) => void;
|
||||
onDragOver?: (...args: unknown[]) => void;
|
||||
onDragLeave?: (...args: unknown[]) => void;
|
||||
}
|
||||
|
||||
export interface UseDocumentsPanelPropsArgs {
|
||||
interface UseDocumentsPanelPropsArgs {
|
||||
currentFolderName?: string | null;
|
||||
breadcrumbs?: Breadcrumb[];
|
||||
refreshCurrentFolder?: () => void | Promise<void>;
|
||||
@@ -65,8 +65,6 @@ export interface UseDocumentsPanelPropsArgs {
|
||||
selectionValue: WorkspaceSelectionValue;
|
||||
}
|
||||
|
||||
export type DocumentsPanelProps = ReturnType<typeof useDocumentsPanelProps>;
|
||||
|
||||
const useDocumentsPanelProps = (props: UseDocumentsPanelPropsArgs) => {
|
||||
const {
|
||||
currentFolderName,
|
||||
|
||||
@@ -51,43 +51,43 @@ export const createDocumentsTableHeaderActions = ({
|
||||
|
||||
const includeDescendantsToggle = isFilterActive && onToggleIncludeDescendants
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button documents-toolbar__toggle"
|
||||
onClick={onToggleIncludeDescendants}
|
||||
aria-pressed={!includeDescendants}
|
||||
aria-label={includeDescendants ? 'Include subfolders' : 'Limit to current folder'}
|
||||
title={includeDescendants
|
||||
? 'Including subfolders. Click to limit the search to the current folder.'
|
||||
: 'Limiting to the current folder. Click to include subfolders again.'}
|
||||
>
|
||||
{includeDescendants ? <FoldersIcon /> : <FoldersOffIcon />}
|
||||
</button>
|
||||
)
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button documents-toolbar__toggle"
|
||||
onClick={onToggleIncludeDescendants}
|
||||
aria-pressed={!includeDescendants}
|
||||
aria-label={includeDescendants ? 'Include subfolders' : 'Limit to current folder'}
|
||||
title={includeDescendants
|
||||
? 'Including subfolders. Click to limit the search to the current folder.'
|
||||
: 'Limiting to the current folder. Click to include subfolders again.'}
|
||||
>
|
||||
{includeDescendants ? <FoldersIcon /> : <FoldersOffIcon />}
|
||||
</button>
|
||||
)
|
||||
: null;
|
||||
|
||||
const sortControls = onSortFieldChange
|
||||
? (
|
||||
<div className="documents-actions__sort-group">
|
||||
<SortFieldQuickMenu sortField={sortField} onChange={onSortFieldChange} />
|
||||
{onSortDirectionToggle ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button documents-toolbar__toggle documents-sort__direction"
|
||||
onClick={onSortDirectionToggle}
|
||||
aria-pressed={sortDirectionIsDesc}
|
||||
aria-label={sortDirectionIsDesc ? 'Sort descending' : 'Sort ascending'}
|
||||
title={sortDirectionTitle}
|
||||
>
|
||||
{sortDirectionIsDesc ? (
|
||||
<SortDescendingLettersIcon size={18} />
|
||||
) : (
|
||||
<SortAscendingLettersIcon size={18} />
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
<div className="documents-actions__sort-group">
|
||||
<SortFieldQuickMenu sortField={sortField} onChange={onSortFieldChange} />
|
||||
{onSortDirectionToggle ? (
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button documents-toolbar__toggle documents-sort__direction"
|
||||
onClick={onSortDirectionToggle}
|
||||
aria-pressed={sortDirectionIsDesc}
|
||||
aria-label={sortDirectionIsDesc ? 'Sort descending' : 'Sort ascending'}
|
||||
title={sortDirectionTitle}
|
||||
>
|
||||
{sortDirectionIsDesc ? (
|
||||
<SortDescendingLettersIcon size={18} />
|
||||
) : (
|
||||
<SortAscendingLettersIcon size={18} />
|
||||
)}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
@@ -152,5 +152,3 @@ export const createDocumentsTableHeaderActions = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default createDocumentsTableHeaderActions;
|
||||
|
||||
@@ -21,7 +21,7 @@ const serializePayload = (payload: TagPayload): string | null => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createTagTransferPayload = (
|
||||
const createTagTransferPayload = (
|
||||
tag?: TagLike | null,
|
||||
sourceDocId: DocumentId | null = null,
|
||||
): TagPayload | null => {
|
||||
@@ -65,7 +65,7 @@ export const writeTagTransferData = (
|
||||
}
|
||||
};
|
||||
|
||||
export const readTagTransferData = (dataTransfer?: DataTransfer | null): string | null => {
|
||||
const readTagTransferData = (dataTransfer?: DataTransfer | null): string | null => {
|
||||
if (!dataTransfer) {
|
||||
return null;
|
||||
}
|
||||
@@ -133,4 +133,3 @@ export const isTagTransferEvent = (event?: DragEventLike | null): boolean => {
|
||||
return TAG_MIME_TYPES.some((type) => typeList.includes(type));
|
||||
};
|
||||
|
||||
export { TAG_MIME_TYPES };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { createDocumentEntryKey, createFolderEntryKey } from '../app/entryKey';
|
||||
|
||||
export type PointerEventLike = MouseEvent | PointerEvent;
|
||||
type PointerEventLike = MouseEvent | PointerEvent;
|
||||
|
||||
export const isPointerModifierEvent = (event?: PointerEventLike | null): boolean =>
|
||||
Boolean(event && (event.shiftKey || event.metaKey || event.ctrlKey || event.altKey));
|
||||
@@ -17,9 +17,9 @@ export const isPrimaryPointerEvent = (event?: PointerEventLike | null): boolean
|
||||
return type === 'click' || type === 'pointerdown' || type === 'pointerup';
|
||||
};
|
||||
|
||||
export type EntryType = 'document' | 'folder';
|
||||
type EntryType = 'document' | 'folder';
|
||||
|
||||
export interface WorkspaceEntry {
|
||||
interface WorkspaceEntry {
|
||||
id: string;
|
||||
key?: string;
|
||||
type: EntryType;
|
||||
@@ -31,7 +31,7 @@ interface UseEntryPointerOptions {
|
||||
onDocumentActivate?: (id: string, metadata?: EntryPointerMetadata) => void;
|
||||
}
|
||||
|
||||
export interface EntryPointerMetadata {
|
||||
interface EntryPointerMetadata {
|
||||
modifierClick: boolean;
|
||||
primaryClick: boolean;
|
||||
rowKey: string;
|
||||
@@ -72,5 +72,3 @@ export const useEntryPointer = ({
|
||||
},
|
||||
[onSelectEntry, onDocumentActivate],
|
||||
);
|
||||
|
||||
export default useEntryPointer;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DEFAULT_FOLDER_NAME } from '../app/workspaceUtils';
|
||||
|
||||
type FolderId = string | null;
|
||||
|
||||
export interface FolderManager {
|
||||
interface FolderManager {
|
||||
getNameSync: (folderId: FolderId) => string | null;
|
||||
resolveName: (folderId: FolderId) => Promise<string>;
|
||||
}
|
||||
@@ -55,5 +55,3 @@ export const FolderManagerProvider: React.FC<FolderManagerProviderProps> = ({
|
||||
};
|
||||
|
||||
export const useFolderManager = (): FolderManager => useContext(FolderManagerContext);
|
||||
|
||||
export default FolderManagerContext;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useManagementModals } from '../../app/useManagementModals';
|
||||
import { useAppDispatch, useAppState } from '../../app/appState';
|
||||
import { fetchAsset } from '../../lib/apiClient';
|
||||
import { useApi } from '../../app/ApiContext';
|
||||
import useWorkspaceSelection from '../../app/useWorkspaceSelection';
|
||||
import { useWorkspaceSelection } from '../../app/useWorkspaceSelection';
|
||||
import { useEntryPointer as useEntryPointerCore } from '../../documents/useEntryPointer';
|
||||
import { isTagTransferEvent } from '../../documents/tagTransfer';
|
||||
import useDocumentsSelection from '../../documents/hooks/useDocumentsSelection';
|
||||
|
||||
@@ -342,15 +342,4 @@ api.interceptors.response.use(
|
||||
},
|
||||
);
|
||||
|
||||
export type {
|
||||
DownloadLink,
|
||||
DocumentResponse,
|
||||
AssetResponse,
|
||||
FolderTreeNode,
|
||||
CapabilitySetResponse,
|
||||
CapabilityResponse,
|
||||
ApiTokenRecord,
|
||||
PasskeySummary,
|
||||
Identifier,
|
||||
TenantSnippet,
|
||||
} from './apiTypes';
|
||||
export type { ApiTokenRecord } from './apiTypes';
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface TagResponse {
|
||||
color?: string | null;
|
||||
}
|
||||
|
||||
export interface CorrespondentResponse {
|
||||
interface CorrespondentResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
metadata: Record<string, unknown>;
|
||||
@@ -29,7 +29,7 @@ export interface AssetResponse {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface DocumentVersionResponse {
|
||||
interface DocumentVersionResponse {
|
||||
id: string;
|
||||
version_number: number;
|
||||
size_bytes: number;
|
||||
@@ -57,7 +57,7 @@ export interface DocumentResponse {
|
||||
current_version?: DocumentVersionResponse | null;
|
||||
}
|
||||
|
||||
export interface FolderInfo {
|
||||
interface FolderInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
parent_id?: string | null;
|
||||
|
||||
@@ -23,7 +23,7 @@ import { resolveDocumentAssetUrl } from '../asset_manager';
|
||||
import PanelHeader from '../ui/PanelHeader';
|
||||
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
|
||||
import DocumentViewerLayout from './DocumentViewerLayout';
|
||||
import useViewerLayoutMode from './useViewerLayoutMode';
|
||||
import { useViewerLayoutMode } from './useViewerLayoutMode';
|
||||
import { usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||
import type { DocumentId, FolderId } from '../types/identifiers';
|
||||
import type { Document } from '../types/documents';
|
||||
@@ -46,7 +46,7 @@ interface DocumentViewerPanelProps extends DocumentSummarySectionProps {
|
||||
sidebarMode?: SidebarMode;
|
||||
}
|
||||
|
||||
export const createDocumentViewerHeaderActions = ({
|
||||
const createDocumentViewerHeaderActions = ({
|
||||
document,
|
||||
actionState,
|
||||
onZoom,
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { DownloadIcon } from '../ui/icons';
|
||||
import { AUDIO_EXTENSIONS, VIDEO_EXTENSIONS } from '../constants/preview';
|
||||
|
||||
export interface MediaViewerProps {
|
||||
interface MediaViewerProps {
|
||||
src: string;
|
||||
mimeType?: string;
|
||||
filename?: string;
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
PORTRAIT_RATIO_STYLE_ID,
|
||||
} from '../constants/preview';
|
||||
|
||||
export { DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO };
|
||||
|
||||
const ensurePortraitRatioStyle = () => {
|
||||
const cssValue = String(DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO);
|
||||
const cssText = `:root { --document-viewer-portrait-height-ratio: ${cssValue}; }`;
|
||||
@@ -78,5 +76,3 @@ export const useViewerLayoutMode = (
|
||||
|
||||
return isStacked;
|
||||
};
|
||||
|
||||
export default useViewerLayoutMode;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useAppShell } from '../appShellContext';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentViewerRouteContext {
|
||||
previewWorkspaceDocument?: { id?: Identifier } | null;
|
||||
ensurePreviewData?: (documentId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
|
||||
notifyApiError?: (error: unknown, message?: string) => void;
|
||||
}
|
||||
|
||||
const DocumentViewerRoute: React.FC = () => {
|
||||
const {
|
||||
previewWorkspaceDocument,
|
||||
ensurePreviewData,
|
||||
notifyApiError,
|
||||
} = useAppShell() as DocumentViewerRouteContext;
|
||||
const { documentId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!documentId) {
|
||||
navigate('/documents', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
const hydrate = async () => {
|
||||
try {
|
||||
await ensurePreviewData?.(documentId);
|
||||
} catch (error) {
|
||||
if (cancelled) {
|
||||
return;
|
||||
}
|
||||
notifyApiError?.(error, 'Failed to open document preview.');
|
||||
navigate('/documents', { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
hydrate();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [documentId, ensurePreviewData, notifyApiError, navigate]);
|
||||
|
||||
if (!documentId) {
|
||||
return <Navigate to="/documents" replace />;
|
||||
}
|
||||
|
||||
const previewId = previewWorkspaceDocument?.id;
|
||||
if (!previewWorkspaceDocument || String(previewId) !== String(documentId)) {
|
||||
return <div className="document-viewer__message">Loading preview…</div>;
|
||||
}
|
||||
|
||||
return <Navigate to="/documents" replace />;
|
||||
};
|
||||
|
||||
export default DocumentViewerRoute;
|
||||
@@ -1,12 +1,5 @@
|
||||
import PasskeysSection from './PasskeysSection';
|
||||
import ApiTokensSection from './ApiTokensSection';
|
||||
import CapabilitySetsSection from './CapabilitySetsSection';
|
||||
import {
|
||||
API_TOKENS_SECTION,
|
||||
CAPABILITY_SETS_SECTION,
|
||||
DEFAULT_SETTINGS_SECTIONS,
|
||||
PASSKEYS_SECTION,
|
||||
} from '../../constants/settings';
|
||||
|
||||
export { PasskeysSection, ApiTokensSection, CapabilitySetsSection };
|
||||
export { PASSKEYS_SECTION, API_TOKENS_SECTION, CAPABILITY_SETS_SECTION, DEFAULT_SETTINGS_SECTIONS };
|
||||
export { DEFAULT_SETTINGS_SECTIONS };
|
||||
|
||||
@@ -59,13 +59,13 @@ interface PasskeyRegisterPayload {
|
||||
nickname?: string;
|
||||
}
|
||||
|
||||
export type RegisterPasskeyFailureReason = 'unsupported' | 'busy' | 'cancelled' | 'error';
|
||||
type RegisterPasskeyFailureReason = 'unsupported' | 'busy' | 'cancelled' | 'error';
|
||||
export type RegisterPasskeyResult =
|
||||
| { ok: true }
|
||||
| { ok: false; reason: RegisterPasskeyFailureReason; message?: string };
|
||||
|
||||
export type RevokePasskeyFailureReason = 'missing-id' | 'error';
|
||||
export type RevokePasskeyResult =
|
||||
type RevokePasskeyFailureReason = 'missing-id' | 'error';
|
||||
type RevokePasskeyResult =
|
||||
| { ok: true }
|
||||
| { ok: false; reason: RevokePasskeyFailureReason; message?: string };
|
||||
|
||||
|
||||
@@ -295,12 +295,3 @@ export const useSidebarContext = () => {
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const useSidebarControls = () => {
|
||||
const { setCollapsed } = useSidebarContext();
|
||||
const openSidebar = useCallback(() => setCollapsed(false), [setCollapsed]);
|
||||
const closeSidebar = useCallback(() => setCollapsed(true), [setCollapsed]);
|
||||
return { openSidebar, closeSidebar };
|
||||
};
|
||||
|
||||
export default SidebarContext;
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface FolderTreeNode {
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
||||
export interface FolderNodeProps {
|
||||
interface FolderNodeProps {
|
||||
node: FolderTreeNode;
|
||||
depth: number;
|
||||
isSelected: boolean;
|
||||
|
||||
@@ -57,14 +57,14 @@ export interface Folder {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type FolderEntry = {
|
||||
type FolderEntry = {
|
||||
type: 'folder';
|
||||
id: Identifier | 'root';
|
||||
key: string;
|
||||
folder: Folder;
|
||||
};
|
||||
|
||||
export type DocumentEntry = {
|
||||
type DocumentEntry = {
|
||||
type: 'document';
|
||||
id: Identifier;
|
||||
key: string;
|
||||
@@ -77,4 +77,3 @@ export type DocumentsListEntry = FolderEntry | DocumentEntry;
|
||||
import type { MouseEvent } from 'react';
|
||||
|
||||
export type FolderEventHandler = (folder: Folder, event: MouseEvent<HTMLElement>) => void;
|
||||
export type DocumentEventHandler = (document: Document, event: MouseEvent<HTMLElement>) => void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { ElementType, ReactNode } from 'react';
|
||||
import composeClassName from './classNames';
|
||||
import { composeClassName } from './classNames';
|
||||
|
||||
type TitleProps = Record<string, unknown>;
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ interface FloatingMenuState {
|
||||
|
||||
type CSSVarStyle = CSSProperties & Record<string, string>;
|
||||
|
||||
export interface QuickAddMenuProps {
|
||||
interface QuickAddMenuProps {
|
||||
onSelectOption?: (value: QuickAddOption, normalized: NormalizedOption) => Promise<void> | void;
|
||||
onCreate?: (value: string) => Promise<void> | void;
|
||||
options?: QuickAddOption[];
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
export const composeClassName = (base: string, extra?: string | null): string =>
|
||||
extra ? `${base} ${extra}` : base;
|
||||
|
||||
export default composeClassName;
|
||||
|
||||
@@ -12,15 +12,11 @@ import {
|
||||
IconLayoutList,
|
||||
IconLayoutGrid,
|
||||
IconArrowLeft,
|
||||
IconArrowRight,
|
||||
IconArrowUp,
|
||||
IconAnalyze,
|
||||
IconUpload,
|
||||
IconWindowMaximize,
|
||||
IconTextScan2,
|
||||
IconFolderPlus,
|
||||
IconFolder,
|
||||
IconFolderUp,
|
||||
IconFolders,
|
||||
IconFoldersOff,
|
||||
IconRefresh,
|
||||
@@ -37,10 +33,8 @@ import {
|
||||
IconDeviceLaptop,
|
||||
IconLayoutSidebarLeftCollapse,
|
||||
IconLayoutSidebarLeftExpand,
|
||||
IconLayoutSidebarRightCollapse,
|
||||
IconLayoutBottombarCollapse,
|
||||
IconLayoutBottombarExpand,
|
||||
IconClearAll,
|
||||
IconInfoCircle,
|
||||
IconCircleDashedCheck,
|
||||
IconFile,
|
||||
@@ -56,7 +50,7 @@ import {
|
||||
import FolderSvg from '../assets/folder.svg';
|
||||
const logoWebp = new URL('../assets/logo.webp', import.meta.url).toString();
|
||||
const logoSmallWebp = new URL('../assets/logo_small.webp', import.meta.url).toString();
|
||||
import composeClassName from './classNames';
|
||||
import { composeClassName } from './classNames';
|
||||
|
||||
type TablerIconComponent = (props: TablerIconProps) => JSX.Element;
|
||||
|
||||
@@ -223,16 +217,6 @@ export const ArrowLeftIcon: TablerIconComponent = ({ className, size = '1em', st
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const ArrowRightIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconArrowRight
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const SidebarCollapseIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconLayoutSidebarLeftCollapse
|
||||
className={composeClassName('icon', className)}
|
||||
@@ -268,16 +252,6 @@ export const FileInfoIcon: TablerIconComponent = ({ className, size = '1em', str
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const DetailPanelCollapseIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconLayoutSidebarRightCollapse
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const BottombarCollapseIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconLayoutBottombarCollapse
|
||||
className={composeClassName('icon', className)}
|
||||
@@ -295,16 +269,6 @@ export const BottombarExpandIcon: TablerIconComponent = ({ className, size = '1e
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const ClearAllIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconClearAll
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const FolderPlusIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconFolderPlus
|
||||
className={composeClassName('icon', className)}
|
||||
@@ -349,16 +313,6 @@ export const RestoreIcon: TablerIconComponent = ({ className, size = '1em', stro
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const ArrowUpIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconArrowUp
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const MinusVerticalIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconMinusVertical
|
||||
className={composeClassName('icon', className)}
|
||||
@@ -567,16 +521,6 @@ export const ChevronDownIcon: TablerIconComponent = ({ className, size = '1em',
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const FolderUpIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconFolderUp
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const FolderMoveIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -601,38 +545,3 @@ export const FolderMoveIcon: TablerIconComponent = ({ className, size = '1em', s
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default {
|
||||
ChevronIcon,
|
||||
TrashIcon,
|
||||
EditIcon,
|
||||
FolderIcon,
|
||||
TagIcon,
|
||||
CorrespondentIcon,
|
||||
DownloadIcon,
|
||||
ViewListIcon,
|
||||
ViewGridIcon,
|
||||
SidebarCollapseIcon,
|
||||
SidebarExpandIcon,
|
||||
DetailPanelCollapseIcon,
|
||||
AnalyzeIcon,
|
||||
LoaderIcon,
|
||||
WindowMaximizeIcon,
|
||||
FolderPlusIcon,
|
||||
RefreshIcon,
|
||||
ArrowUpIcon,
|
||||
MinusVerticalIcon,
|
||||
LogoutIcon,
|
||||
ChevronDownIcon,
|
||||
FolderUpIcon,
|
||||
FolderMoveIcon,
|
||||
};
|
||||
|
||||
export const TextScanIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||
<IconTextScan2
|
||||
className={composeClassName('icon', className)}
|
||||
size={size}
|
||||
stroke={stroke}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import type { PointerEvent as ReactPointerEvent } from 'react';
|
||||
|
||||
export interface PointerTapContext<T = unknown> {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
pointerType: string;
|
||||
event: PointerEventWithPersist;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
type PointerEventWithPersist = (PointerEvent | ReactPointerEvent) & { persist?: () => void };
|
||||
|
||||
type PointerTapFilter = (event?: PointerEventWithPersist | null) => boolean;
|
||||
|
||||
export interface UsePointerTapOptions<T = unknown> {
|
||||
onSingle?: (context: PointerTapContext<T>) => void;
|
||||
onDouble?: (context: PointerTapContext<T>) => void;
|
||||
delay?: number;
|
||||
filter?: PointerTapFilter;
|
||||
}
|
||||
|
||||
const defaultFilter: PointerTapFilter = (event) => {
|
||||
if (!event) {
|
||||
return false;
|
||||
}
|
||||
const { type, button, pointerType, isPrimary } = event;
|
||||
const isPointerUp = type === 'pointerup';
|
||||
const buttonValid =
|
||||
button == null || button === 0 || (isPointerUp && (button === -1 || button === 0));
|
||||
if (!buttonValid) {
|
||||
return false;
|
||||
}
|
||||
if (pointerType === 'touch' && isPrimary === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const usePointerTap = <T,>({
|
||||
onSingle,
|
||||
onDouble,
|
||||
delay = 240,
|
||||
filter = defaultFilter,
|
||||
}: UsePointerTapOptions<T> = {}) => {
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return useCallback(
|
||||
(event?: PointerEventWithPersist | null, metadata?: T) => {
|
||||
if (!filter(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.persist?.();
|
||||
|
||||
const context: PointerTapContext<T> = {
|
||||
clientX: event.clientX ?? 0,
|
||||
clientY: event.clientY ?? 0,
|
||||
pointerType: event.pointerType ?? 'mouse',
|
||||
event,
|
||||
data: metadata,
|
||||
};
|
||||
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
onDouble?.(context);
|
||||
return;
|
||||
}
|
||||
|
||||
timerRef.current = setTimeout(() => {
|
||||
timerRef.current = null;
|
||||
onSingle?.(context);
|
||||
}, delay);
|
||||
},
|
||||
[delay, filter, onDouble, onSingle],
|
||||
);
|
||||
};
|
||||
|
||||
export default usePointerTap;
|
||||
@@ -93,7 +93,7 @@ const srgbChannelToLinear = (value: number) => {
|
||||
return ((normalized + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
|
||||
export const hexToRgb = (input?: string): (RgbColor & { hex: string }) | null => {
|
||||
const hexToRgb = (input?: string): (RgbColor & { hex: string }) | null => {
|
||||
if (!input) return null;
|
||||
const match = HEX_COLOR_PATTERN.exec(input.trim());
|
||||
if (!match) return null;
|
||||
@@ -163,7 +163,7 @@ const buildTagStyle = (backgroundHex: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const relativeLuminance = ({ r, g, b }: RgbColor): number => {
|
||||
const relativeLuminance = ({ r, g, b }: RgbColor): number => {
|
||||
const toLinear = (channel) => {
|
||||
const normalized = channel / 255;
|
||||
return normalized <= 0.03928
|
||||
@@ -198,7 +198,7 @@ const contrastForPair = (backgroundHex: string, textHex: string): number => {
|
||||
return contrastRatio(relativeLuminance(background), relativeLuminance(text));
|
||||
};
|
||||
|
||||
export const getReadableTextColor = (
|
||||
const getReadableTextColor = (
|
||||
hex: string,
|
||||
{ light = '#1f1f1f', dark = '#ffffff', fallback = '#1f1f1f' }: { light?: string; dark?: string; fallback?: string } = {},
|
||||
): string => {
|
||||
|
||||
@@ -51,13 +51,3 @@ export const toIssuedTimestamp = (dateString: string | null, fallback: string |
|
||||
candidate.setUTCFullYear(year, month - 1, day);
|
||||
return Number.isNaN(candidate.getTime()) ? null : candidate.toISOString();
|
||||
};
|
||||
|
||||
export const parseDateValue = (value: string | Date | null): Date | null => ensureDate(value);
|
||||
|
||||
export default {
|
||||
formatDate,
|
||||
formatDateTime,
|
||||
toDateInputValue,
|
||||
toIssuedTimestamp,
|
||||
parseDateValue,
|
||||
};
|
||||
|
||||
@@ -7,19 +7,3 @@ export const clamp = (value: number, min: number, max: number): number => {
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const formatTransform = (
|
||||
x: number,
|
||||
y: number,
|
||||
rotation = 0,
|
||||
scale = 1,
|
||||
): string => `translate3d(${x}px, ${y}px, 0) rotate(${rotation}deg) scale(${scale})`;
|
||||
|
||||
export const toNumber = (v: unknown, fallback = 0): number =>
|
||||
Number.isFinite(Number(v)) ? Number(v) : fallback;
|
||||
|
||||
export default {
|
||||
clamp,
|
||||
formatTransform,
|
||||
toNumber,
|
||||
};
|
||||
|
||||
@@ -4,12 +4,9 @@ import type {
|
||||
} from '../asset_manager';
|
||||
import type {
|
||||
Document,
|
||||
DocumentVersion,
|
||||
} from '../types/documents';
|
||||
import type { Asset } from '../types/assets';
|
||||
|
||||
export type { Document, DocumentVersion, Asset };
|
||||
|
||||
export type EnsurePreviewData = (id: string) => Promise<Document | null>;
|
||||
export type EnsureAssetUrl = (
|
||||
id: string,
|
||||
@@ -32,7 +29,7 @@ const pickAsset = (doc?: Document | null, getDocumentAsset?: GetDocumentAsset):
|
||||
return getDocumentAsset(doc, 'text-content') || null;
|
||||
};
|
||||
|
||||
export async function resolveTextContentUrl({
|
||||
async function resolveTextContentUrl({
|
||||
document,
|
||||
ensurePreviewData,
|
||||
getDocumentAsset,
|
||||
|
||||
@@ -43,7 +43,7 @@ const decodeBase64 = (value: string): string => window.atob(value);
|
||||
|
||||
const encodeBase64 = (binary: string): string => window.btoa(binary);
|
||||
|
||||
export const base64urlToUint8Array = (value?: string | null): Uint8Array => {
|
||||
const base64urlToUint8Array = (value?: string | null): Uint8Array => {
|
||||
const base64 = base64urlToBase64(value || '');
|
||||
const binary = decodeBase64(base64);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
@@ -77,7 +77,7 @@ const toUint8Array = (input?: ArrayBuffer | ArrayBufferView | ArrayLike<number>
|
||||
return Uint8Array.from(input as ArrayLike<number>) as Uint8Array;
|
||||
};
|
||||
|
||||
export const arrayBufferToBase64url = (
|
||||
const arrayBufferToBase64url = (
|
||||
buffer?: ArrayBuffer | ArrayBufferView | ArrayLike<number> | null,
|
||||
): string => {
|
||||
const bytes = toUint8Array(buffer);
|
||||
|
||||
Reference in New Issue
Block a user