refactor: extract various constants
This commit is contained in:
@@ -10,13 +10,16 @@ import React, {
|
||||
} from 'react';
|
||||
import type { CSSProperties, PointerEvent as ReactPointerEvent, RefObject } from 'react';
|
||||
import { useSidebarContext } from '../sidebar/SidebarContext';
|
||||
|
||||
type PanelKey = 'sidebar' | 'detail';
|
||||
|
||||
interface PanelLimits {
|
||||
maxRatio: number;
|
||||
minPx: number;
|
||||
}
|
||||
import {
|
||||
DEFAULT_DETAIL_WIDTH,
|
||||
DEFAULT_SIDEBAR_WIDTH,
|
||||
MINIMAL_FREE_RATIO,
|
||||
MINIMUM_MAIN_CONTENT_WIDTH,
|
||||
PANEL_LIMITS,
|
||||
PANEL_STORAGE_KEYS,
|
||||
SIDEBAR_SOLO_THRESHOLD,
|
||||
type PanelKey,
|
||||
} from '../constants/layout';
|
||||
|
||||
interface SetPanelWidthOptions {
|
||||
commit?: boolean;
|
||||
@@ -50,29 +53,6 @@ type PanelResizeBindings = {
|
||||
|
||||
const PanelManagerContext = createContext<PanelManagerContextValue | null>(null);
|
||||
|
||||
const PANEL_LIMITS: Record<PanelKey, PanelLimits> = {
|
||||
sidebar: {
|
||||
maxRatio: 1 / 3,
|
||||
minPx: 280,
|
||||
},
|
||||
detail: {
|
||||
maxRatio: 2 / 3,
|
||||
minPx: 320,
|
||||
},
|
||||
};
|
||||
|
||||
const STORAGE_KEYS: Record<PanelKey, string> = {
|
||||
sidebar: 'papercrate_sidebar_width',
|
||||
detail: 'papercrate_detail_width',
|
||||
};
|
||||
|
||||
const DEFAULT_SIDEBAR_WIDTH = 320;
|
||||
const DEFAULT_DETAIL_WIDTH = 420;
|
||||
|
||||
const MINIMAL_FREE_RATIO = 1 / 3;
|
||||
const SIDEBAR_SOLO_THRESHOLD = 1 / 2;
|
||||
const MINIMUM_MAIN_CONTENT_WIDTH = 160;
|
||||
|
||||
const clampPanelWidth = (panel: PanelKey, value: number): number => {
|
||||
const numeric = Number(value);
|
||||
const limits = PANEL_LIMITS[panel];
|
||||
@@ -86,7 +66,7 @@ const clampPanelWidth = (panel: PanelKey, value: number): number => {
|
||||
};
|
||||
|
||||
const readStoredWidth = (panel: PanelKey, fallback: number): number => {
|
||||
const raw = window.localStorage.getItem(STORAGE_KEYS[panel]);
|
||||
const raw = window.localStorage.getItem(PANEL_STORAGE_KEYS[panel]);
|
||||
if (!raw) {
|
||||
return fallback;
|
||||
}
|
||||
@@ -95,7 +75,7 @@ const readStoredWidth = (panel: PanelKey, fallback: number): number => {
|
||||
};
|
||||
|
||||
const persistWidth = (panel: PanelKey, value: number): void => {
|
||||
window.localStorage.setItem(STORAGE_KEYS[panel], String(Math.round(value)));
|
||||
window.localStorage.setItem(PANEL_STORAGE_KEYS[panel], String(Math.round(value)));
|
||||
};
|
||||
|
||||
const applyPanelWidthToRoot = (panel: PanelKey, width: number, active: boolean): void => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useContext, useEffect, useMemo, useReducer } from 'react';
|
||||
import { clearAuthToken, setAuthToken, setAuthRefreshHandlers } from '../lib/apiClient';
|
||||
import { ApiProvider } from './ApiContext';
|
||||
import { listTenants } from '../lib/apiClient';
|
||||
import { STORED_TOKEN_KEY } from '../constants/app';
|
||||
|
||||
type Tenant = Record<string, unknown> | null;
|
||||
|
||||
@@ -47,7 +48,7 @@ type AppAction =
|
||||
|
||||
const storage = window.sessionStorage;
|
||||
|
||||
const STORED_TOKEN = storage?.getItem('papercrate_token') ?? '';
|
||||
const storedToken = storage?.getItem(STORED_TOKEN_KEY) ?? '';
|
||||
let STORED_TENANT: Tenant = null;
|
||||
|
||||
if (storage) {
|
||||
@@ -61,13 +62,13 @@ if (storage) {
|
||||
}
|
||||
}
|
||||
|
||||
if (STORED_TOKEN) {
|
||||
setAuthToken(STORED_TOKEN);
|
||||
if (storedToken) {
|
||||
setAuthToken(storedToken);
|
||||
}
|
||||
|
||||
const initialAppState: AppState = {
|
||||
status: STORED_TOKEN ? 'authenticated' : 'logged-out',
|
||||
token: STORED_TOKEN,
|
||||
status: storedToken ? 'authenticated' : 'logged-out',
|
||||
token: storedToken,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
tenantSelection: null,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { DocumentId, FolderId } from '../types/identifiers';
|
||||
import { ENTRY_KEY_SEPARATOR } from '../constants/app';
|
||||
|
||||
// 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: DocumentId): string =>
|
||||
`document${ENTRY_KEY_SEPARATOR}${documentId}`;
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
DEFAULT_SORT_FIELD,
|
||||
SORT_FIELD_VALUES,
|
||||
} from './workspaceUtils';
|
||||
|
||||
const VIEW_MODE_STORAGE_KEY = 'papercrate_view_mode';
|
||||
const SORT_FIELD_STORAGE_KEY = 'papercrate_sort_field';
|
||||
const SORT_DIRECTION_STORAGE_KEY = 'papercrate_sort_direction';
|
||||
const INCLUDE_DESCENDANTS_STORAGE_KEY = 'papercrate_include_descendants';
|
||||
import {
|
||||
INCLUDE_DESCENDANTS_STORAGE_KEY,
|
||||
SORT_DIRECTION_STORAGE_KEY,
|
||||
SORT_FIELD_STORAGE_KEY,
|
||||
VIEW_MODE_STORAGE_KEY,
|
||||
} from '../constants/workspace';
|
||||
|
||||
const readSessionStorage = (key: string): string | null => {
|
||||
try {
|
||||
|
||||
@@ -4,9 +4,7 @@ import TagsPanel from '../tags/TagsPanel';
|
||||
import CorrespondentsPanel, { CorrespondentsPanelProps } from '../correspondents/CorrespondentsPanel';
|
||||
import PanelHeader from '../ui/PanelHeader';
|
||||
import { CloseIcon } from '../ui/icons';
|
||||
|
||||
const TAGS_MODAL = 'tags';
|
||||
const CORRESPONDENTS_MODAL = 'correspondents';
|
||||
import { CORRESPONDENTS_MODAL, TAGS_MODAL } from '../constants/app';
|
||||
|
||||
interface TagRecord {
|
||||
id?: string;
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
export const DEFAULT_FOLDER_NAME = 'Documents';
|
||||
export const DEFAULT_SORT_FIELD = 'title';
|
||||
export const DEFAULT_SORT_DIRECTION = 'asc';
|
||||
export const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at'];
|
||||
export const TAG_FILTER_UNTAGGED = '__UNTAGGED__';
|
||||
import {
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DEFAULT_SORT_DIRECTION,
|
||||
DEFAULT_SORT_FIELD,
|
||||
SORT_FIELD_VALUES,
|
||||
TAG_FILTER_UNTAGGED,
|
||||
} from '../constants/workspace';
|
||||
|
||||
export {
|
||||
DEFAULT_FOLDER_NAME,
|
||||
DEFAULT_SORT_DIRECTION,
|
||||
DEFAULT_SORT_FIELD,
|
||||
SORT_FIELD_VALUES,
|
||||
TAG_FILTER_UNTAGGED,
|
||||
};
|
||||
|
||||
export const hasFiles = (event) =>
|
||||
Array.from(event.dataTransfer?.types || []).includes('Files');
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export const ENTRY_KEY_SEPARATOR = ':';
|
||||
export const TAGS_MODAL = 'tags';
|
||||
export const CORRESPONDENTS_MODAL = 'correspondents';
|
||||
export const STORED_TOKEN_KEY = 'papercrate_token';
|
||||
@@ -0,0 +1 @@
|
||||
export const HEX_COLOR_PATTERN = /^#?([0-9a-fA-F]{6})$/;
|
||||
@@ -0,0 +1,48 @@
|
||||
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 TAG_REMOVE_DISTANCE = 160;
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,18 @@
|
||||
export const DEFAULT_THUMBNAIL_SIZE = 48;
|
||||
|
||||
export const TAG_MIME_TYPES = ['application/x-papercrate-tag', 'text/papercrate-tag'] as const;
|
||||
export const TAG_TEXT_MIME_TYPE = 'text/plain';
|
||||
|
||||
export const DEFAULT_GRID_ICON_SIZE = 144;
|
||||
|
||||
export const SORT_OPTIONS = [
|
||||
{ value: 'title', label: 'Title' },
|
||||
{ value: 'issued_at', label: 'Issued date' },
|
||||
{ value: 'created_at', label: 'Added' },
|
||||
{ value: 'updated_at', label: 'Updated date' },
|
||||
] as const;
|
||||
|
||||
export const SORT_LABEL_LOOKUP = SORT_OPTIONS.reduce<Record<string, string>>((acc, option) => {
|
||||
acc[option.value] = option.label;
|
||||
return acc;
|
||||
}, {});
|
||||
@@ -0,0 +1,23 @@
|
||||
export const DEFAULT_SIDEBAR_WIDTH = 320;
|
||||
export const DEFAULT_DETAIL_WIDTH = 420;
|
||||
export const MINIMAL_FREE_RATIO = 1 / 3;
|
||||
export const SIDEBAR_SOLO_THRESHOLD = 1 / 2;
|
||||
export const MINIMUM_MAIN_CONTENT_WIDTH = 160;
|
||||
|
||||
export type PanelKey = 'sidebar' | 'detail';
|
||||
|
||||
export const PANEL_LIMITS: Record<PanelKey, { maxRatio: number; minPx: number }> = {
|
||||
sidebar: {
|
||||
maxRatio: 1 / 3,
|
||||
minPx: 280,
|
||||
},
|
||||
detail: {
|
||||
maxRatio: 2 / 3,
|
||||
minPx: 320,
|
||||
},
|
||||
};
|
||||
|
||||
export const PANEL_STORAGE_KEYS: Record<PanelKey, string> = {
|
||||
sidebar: 'papercrate_sidebar_width',
|
||||
detail: 'papercrate_detail_width',
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
export const RERENDER_DELTA = 48;
|
||||
export const MAX_PIXEL_RATIO = 2;
|
||||
export const FAST_SCROLL_VELOCITY_THRESHOLD = 0.8;
|
||||
export const FAST_SCROLL_DWELL_THRESHOLD_MS = 120;
|
||||
export const SCROLL_VELOCITY_MIN_DELTA = 0.05;
|
||||
|
||||
export const AUDIO_EXTENSIONS = new Set([
|
||||
'aac',
|
||||
'aiff',
|
||||
'flac',
|
||||
'm4a',
|
||||
'mp3',
|
||||
'ogg',
|
||||
'oga',
|
||||
'opus',
|
||||
'wav',
|
||||
'weba',
|
||||
]);
|
||||
|
||||
export const VIDEO_EXTENSIONS = new Set([
|
||||
'avi',
|
||||
'mkv',
|
||||
'mov',
|
||||
'mp4',
|
||||
'm4v',
|
||||
'webm',
|
||||
'wmv',
|
||||
]);
|
||||
|
||||
export const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.4;
|
||||
export const PORTRAIT_RATIO_STYLE_ID = 'document-viewer-portrait-ratio-style';
|
||||
export const MIN_STACKED_BREAKPOINT = 480;
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { SettingsSectionConfig } from '../settings/SettingsModal';
|
||||
import ApiTokensSection from '../settings/sections/ApiTokensSection';
|
||||
import CapabilitySetsSection from '../settings/sections/CapabilitySetsSection';
|
||||
import PasskeysSection from '../settings/sections/PasskeysSection';
|
||||
|
||||
export const PASSKEYS_SECTION: SettingsSectionConfig = {
|
||||
id: 'passkeys',
|
||||
label: 'Passkeys',
|
||||
component: PasskeysSection,
|
||||
};
|
||||
|
||||
export const API_TOKENS_SECTION: SettingsSectionConfig = {
|
||||
id: 'apiTokens',
|
||||
label: 'API tokens',
|
||||
component: ApiTokensSection,
|
||||
};
|
||||
|
||||
export const CAPABILITY_SETS_SECTION: SettingsSectionConfig = {
|
||||
id: 'capabilitySets',
|
||||
label: 'Capability sets',
|
||||
component: CapabilitySetsSection,
|
||||
};
|
||||
|
||||
export const DEFAULT_SETTINGS_SECTIONS = [
|
||||
PASSKEYS_SECTION,
|
||||
API_TOKENS_SECTION,
|
||||
CAPABILITY_SETS_SECTION,
|
||||
];
|
||||
@@ -0,0 +1,13 @@
|
||||
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 = {
|
||||
system: 'System default',
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
};
|
||||
export const DARK_MODE_MEDIA_QUERY = '(prefers-color-scheme: dark)';
|
||||
@@ -0,0 +1,7 @@
|
||||
export const NBSP = String.fromCharCode(160);
|
||||
export const DEFAULT_VIEWPORT_MARGIN = 8;
|
||||
|
||||
export const ELLIPSIS = { id: '__breadcrumbs_ellipsis__', label: '…', onClick: null, raw: null } as const;
|
||||
export const WIDTH_TOLERANCE = 1;
|
||||
export const WIDTH_BUFFER_RATIO = 0.95;
|
||||
export const WIDTH_CHANGE_TOLERANCE = 0.25;
|
||||
@@ -0,0 +1,12 @@
|
||||
export const DEFAULT_FOLDER_NAME = 'Documents';
|
||||
export const DEFAULT_SORT_FIELD = 'title';
|
||||
export const DEFAULT_SORT_DIRECTION = 'asc';
|
||||
export const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at'];
|
||||
export const TAG_FILTER_UNTAGGED = '__UNTAGGED__';
|
||||
|
||||
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;
|
||||
@@ -25,6 +25,7 @@ import useDeskPointer from './pointer/useDeskPointer';
|
||||
import useDeskTagInteractions from './tags/useDeskTagInteractions';
|
||||
import DesktopDocumentCard from './DesktopDocumentCard';
|
||||
import usePreviewMetadata from './hooks/usePreviewMetadata';
|
||||
import { DEBUG_DRAG, DEBUG_FOCUS } from '../constants/desktop';
|
||||
import '../styles/workspace/workspace-layout.css';
|
||||
import '../styles/workspace/workspace-items.css';
|
||||
import '../styles/workspace/workspace-cards.css';
|
||||
@@ -186,8 +187,6 @@ interface DesktopWorkspaceViewProps {
|
||||
markLayoutDirty: () => void;
|
||||
}
|
||||
|
||||
const DEBUG_DRAG = false;
|
||||
const DEBUG_FOCUS = false;
|
||||
const defaultGetDocumentAsset: GetAsset = () => null;
|
||||
|
||||
const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = ({
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
import { DB_NAME, DB_VERSION, LAYOUT_STORE } from '../constants/desktop';
|
||||
type TenantId = import('../types/identifiers').TenantId;
|
||||
|
||||
const DB_NAME = 'papercrate_desk';
|
||||
const DB_VERSION = 1;
|
||||
const LAYOUT_STORE = 'layouts';
|
||||
|
||||
const currentDbPromise: { value: Promise<IDBDatabase | null> | null } = { value: null };
|
||||
|
||||
const openDatabase = (): Promise<IDBDatabase> => {
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
import { safeInvoke } from '../events';
|
||||
import type { DocumentId } from '../../types/identifiers';
|
||||
import {
|
||||
CLICK_ACTIONS,
|
||||
DRAG_ACTIONS,
|
||||
LONG_PRESS_DURATION_MS,
|
||||
POINTER_DRAG_THRESHOLD_SQUARED,
|
||||
STACK_HIT_EPSILON,
|
||||
} from '../../constants/desktop';
|
||||
|
||||
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 { CLICK_ACTIONS, DRAG_ACTIONS, STACK_HIT_EPSILON, POINTER_DRAG_THRESHOLD_SQUARED, LONG_PRESS_DURATION_MS };
|
||||
|
||||
export type ClickAction = (typeof CLICK_ACTIONS)[keyof typeof CLICK_ACTIONS];
|
||||
export type DragAction = (typeof DRAG_ACTIONS)[keyof typeof DRAG_ACTIONS];
|
||||
|
||||
export const STACK_HIT_EPSILON = 4;
|
||||
export const POINTER_DRAG_THRESHOLD_SQUARED = 16;
|
||||
export const LONG_PRESS_DURATION_MS = 450;
|
||||
|
||||
export const withinThreshold = (dx: number, dy: number, thresholdSquared: number): boolean => (dx * dx + dy * dy) <= thresholdSquared;
|
||||
|
||||
interface PointerIntentArgs {
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
parseTagTransferPayload,
|
||||
writeTagTransferData,
|
||||
} from '../../documents/tagTransfer';
|
||||
|
||||
const TAG_REMOVE_DISTANCE = 160;
|
||||
import { TAG_REMOVE_DISTANCE } from '../../constants/desktop';
|
||||
|
||||
const createDragPreview = (node, clientX, clientY) => {
|
||||
if (!(node instanceof HTMLElement)) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
applyDomTransform,
|
||||
type WorkspaceEngine,
|
||||
} from './workspaceEngine';
|
||||
import { DRAG_HYSTERESIS_SQUARED, EDGE_COLLISION_THRESHOLD } from '../constants/desktop';
|
||||
import type { DocumentId, Identifier } from '../types/identifiers';
|
||||
|
||||
interface DocumentLike {
|
||||
@@ -168,10 +169,6 @@ interface DragTapMetadata {
|
||||
docTitle: string;
|
||||
}
|
||||
|
||||
const DRAG_HYSTERESIS_PX = 4;
|
||||
const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
|
||||
const EDGE_COLLISION_THRESHOLD = 0.5;
|
||||
|
||||
const getDocumentPageCount = (doc?: DocumentLike | null): number | null => {
|
||||
const raw = doc?.current_version?.metadata?.page_count ?? doc?.metadata?.page_count;
|
||||
if (raw == null) {
|
||||
|
||||
@@ -1,8 +1,44 @@
|
||||
import { clamp, formatTransform } from '../utils/math';
|
||||
import { fetchLayoutRecords, upsertLayoutRecords } from './db';
|
||||
import {
|
||||
ANGULAR_DAMPING,
|
||||
CARD_BASE_WEIGHT_GRAMS,
|
||||
CARD_PAGE_WEIGHT_GRAMS,
|
||||
DEFAULT_Z_START,
|
||||
DESK_CANVAS_PADDING,
|
||||
DESK_CARD_MAX,
|
||||
DESK_CARD_MIN,
|
||||
DESK_DEFAULT_CANVAS_HEIGHT,
|
||||
DESK_DEFAULT_CANVAS_WIDTH,
|
||||
DESK_ROTATION_RANGE,
|
||||
MAX_ANGULAR_VELOCITY,
|
||||
MAX_DYNAMIC_ROTATION,
|
||||
MAX_TIMESTEP,
|
||||
MIN_TIMESTEP,
|
||||
SETTLE_ANGULAR_VELOCITY,
|
||||
TORQUE_TO_ACCELERATION,
|
||||
} from '../constants/desktop';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
type TenantId = import('../types/identifiers').TenantId;
|
||||
|
||||
export {
|
||||
ANGULAR_DAMPING,
|
||||
CARD_BASE_WEIGHT_GRAMS,
|
||||
CARD_PAGE_WEIGHT_GRAMS,
|
||||
DESK_CANVAS_PADDING,
|
||||
DESK_CARD_MAX,
|
||||
DESK_CARD_MIN,
|
||||
DESK_DEFAULT_CANVAS_HEIGHT,
|
||||
DESK_DEFAULT_CANVAS_WIDTH,
|
||||
DESK_ROTATION_RANGE,
|
||||
MAX_ANGULAR_VELOCITY,
|
||||
MAX_DYNAMIC_ROTATION,
|
||||
MAX_TIMESTEP,
|
||||
MIN_TIMESTEP,
|
||||
SETTLE_ANGULAR_VELOCITY,
|
||||
TORQUE_TO_ACCELERATION,
|
||||
};
|
||||
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -128,24 +164,6 @@ type UseSyncExternalStoreHook = <State>(
|
||||
getServerSnapshot: () => State,
|
||||
) => State;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
const getMassScale = (massGrams?: number): number => {
|
||||
if (!Number.isFinite(massGrams) || Number(massGrams) <= 0) {
|
||||
return 1;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
const NBSP = String.fromCharCode(160);
|
||||
import { NBSP } from '../constants/ui';
|
||||
|
||||
export interface CorrespondentLinkEntry {
|
||||
id?: string | null;
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
resolveDocumentAssetUrl,
|
||||
resolveAssetUrl,
|
||||
} from '../asset_manager';
|
||||
import { DEFAULT_THUMBNAIL_SIZE } from '../constants/documents';
|
||||
import type {
|
||||
DocumentLike as AssetManagerDocumentLike,
|
||||
AssetLike as AssetManagerAssetLike,
|
||||
@@ -12,8 +13,6 @@ import type {
|
||||
GetAsset as AssetManagerGetAsset,
|
||||
} from '../asset_manager';
|
||||
|
||||
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,
|
||||
|
||||
@@ -12,10 +12,9 @@ import SelectionAssignmentMenu, { SelectionAssignmentMenuItem } from './Selectio
|
||||
import SelectionSummary from './SelectionSummary';
|
||||
import { useAppState } from '../app/appState';
|
||||
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
|
||||
import { DEFAULT_FOLDER_NAME } from '../constants/workspace';
|
||||
import type { DocumentId } from '../types/identifiers';
|
||||
|
||||
const ROOT_FOLDER_LABEL = 'Documents';
|
||||
|
||||
type NullableDocumentId = DocumentId | null;
|
||||
|
||||
type SelectedIdList = NullableDocumentId[] | null;
|
||||
@@ -128,11 +127,11 @@ const buildFolderTreeOptions = (tree?: FolderTreeNode[] | null): SelectionAssign
|
||||
});
|
||||
};
|
||||
|
||||
traverse(Array.isArray(tree) ? tree : [], [ROOT_FOLDER_LABEL]);
|
||||
traverse(Array.isArray(tree) ? tree : [], [DEFAULT_FOLDER_NAME]);
|
||||
|
||||
entries.sort((a, b) => (a.label || '').localeCompare(b.label || '', undefined, { sensitivity: 'base' }));
|
||||
|
||||
return [{ id: 'root', label: ROOT_FOLDER_LABEL, state: 'none', payload: { id: 'root' } }, ...entries];
|
||||
return [{ id: 'root', label: DEFAULT_FOLDER_NAME, state: 'none', payload: { id: 'root' } }, ...entries];
|
||||
};
|
||||
|
||||
const buildTagAssignments = (
|
||||
|
||||
@@ -18,10 +18,9 @@ import DocumentsPanelHeader, {
|
||||
import { SelectionFloatingPanel } from '../SelectionFloatingActions';
|
||||
import { createDocumentsTableHeaderActions } from './DocumentsToolbar';
|
||||
import { useDocumentsFilter } from '../context/DocumentsFilterContext';
|
||||
import { DEFAULT_GRID_ICON_SIZE } from '../../constants/documents';
|
||||
import type { Identifier } from '../../types/identifiers';
|
||||
|
||||
const DEFAULT_GRID_ICON_SIZE = 144;
|
||||
|
||||
const EntryType = {
|
||||
folder: 'folder',
|
||||
document: 'document',
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { SORT_LABEL_LOOKUP, SORT_OPTIONS } from '../../constants/documents';
|
||||
import QuickAddMenu from '../../ui/QuickAddMenu';
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ value: 'title', label: 'Title' },
|
||||
{ value: 'issued_at', label: 'Issued date' },
|
||||
{ value: 'created_at', label: 'Added' },
|
||||
{ value: 'updated_at', label: 'Updated date' },
|
||||
];
|
||||
|
||||
const SORT_LABEL_LOOKUP = SORT_OPTIONS.reduce((acc, option) => {
|
||||
const next = acc;
|
||||
next[option.value] = option.label;
|
||||
return next;
|
||||
}, {});
|
||||
|
||||
interface SortFieldQuickMenuProps {
|
||||
sortField: string;
|
||||
onChange?: (value: string) => void;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
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';
|
||||
import { TAG_MIME_TYPES, TAG_TEXT_MIME_TYPE } from '../constants/documents';
|
||||
|
||||
interface TagPayload {
|
||||
id: TagId;
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { JSX } from 'react';
|
||||
import DocumentInfoPanel from '../documents/DocumentInfoPanel';
|
||||
import { DownloadIcon } from '../ui/icons';
|
||||
import PdfViewer from './PdfViewer';
|
||||
import { AUDIO_EXTENSIONS, VIDEO_EXTENSIONS } from '../constants/preview';
|
||||
|
||||
interface DocumentLike {
|
||||
id?: string;
|
||||
@@ -48,29 +49,6 @@ interface DocumentViewerLayoutProps {
|
||||
layoutMode?: LayoutMode;
|
||||
}
|
||||
|
||||
const AUDIO_EXTENSIONS = new Set([
|
||||
'aac',
|
||||
'aiff',
|
||||
'flac',
|
||||
'm4a',
|
||||
'mp3',
|
||||
'ogg',
|
||||
'oga',
|
||||
'opus',
|
||||
'wav',
|
||||
'weba',
|
||||
]);
|
||||
|
||||
const VIDEO_EXTENSIONS = new Set([
|
||||
'avi',
|
||||
'mkv',
|
||||
'mov',
|
||||
'mp4',
|
||||
'm4v',
|
||||
'webm',
|
||||
'wmv',
|
||||
]);
|
||||
|
||||
const getFileExtension = (filename?: string | null) => {
|
||||
if (!filename) {
|
||||
return '';
|
||||
|
||||
@@ -17,6 +17,13 @@ import type {
|
||||
PDFDocumentProxy,
|
||||
RenderTask,
|
||||
} from 'pdfjs-dist/types/src/display/api';
|
||||
import {
|
||||
FAST_SCROLL_DWELL_THRESHOLD_MS,
|
||||
FAST_SCROLL_VELOCITY_THRESHOLD,
|
||||
MAX_PIXEL_RATIO,
|
||||
RERENDER_DELTA,
|
||||
SCROLL_VELOCITY_MIN_DELTA,
|
||||
} from '../constants/preview';
|
||||
GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url,
|
||||
@@ -45,12 +52,6 @@ interface RenderQueueRequest {
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
const RERENDER_DELTA = 48;
|
||||
const MAX_PIXEL_RATIO = 2;
|
||||
const FAST_SCROLL_VELOCITY_THRESHOLD = 0.8; // px/ms ~ 800px/s
|
||||
const FAST_SCROLL_DWELL_THRESHOLD_MS = 120;
|
||||
const SCROLL_VELOCITY_MIN_DELTA = 0.05;
|
||||
|
||||
const getAvailableViewportSize = (viewportNode: Element, stackNode: Element) => {
|
||||
const viewportStyle = window.getComputedStyle(viewportNode);
|
||||
const viewportPaddingX = parseFloat(viewportStyle.paddingLeft || '0')
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { MutableRefObject, useLayoutEffect, useState } from 'react';
|
||||
import {
|
||||
DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO,
|
||||
MIN_STACKED_BREAKPOINT,
|
||||
PORTRAIT_RATIO_STYLE_ID,
|
||||
} from '../constants/preview';
|
||||
|
||||
export const DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO = 0.4;
|
||||
const PORTRAIT_RATIO_STYLE_ID = 'document-viewer-portrait-ratio-style';
|
||||
export { DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO };
|
||||
|
||||
const ensurePortraitRatioStyle = () => {
|
||||
const cssValue = String(DOCUMENT_VIEWER_PORTRAIT_HEIGHT_RATIO);
|
||||
@@ -19,8 +23,6 @@ const ensurePortraitRatioStyle = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const MIN_STACKED_BREAKPOINT = 480;
|
||||
|
||||
const computeStackedLayoutBreakpoint = () => Math.max(window.innerWidth / 2, MIN_STACKED_BREAKPOINT);
|
||||
|
||||
export const useViewerLayoutMode = (
|
||||
|
||||
@@ -489,10 +489,4 @@ const ApiTokensSection = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const API_TOKENS_SECTION = {
|
||||
id: 'apiTokens',
|
||||
label: 'API tokens',
|
||||
component: ApiTokensSection,
|
||||
};
|
||||
|
||||
export default ApiTokensSection;
|
||||
|
||||
@@ -5,7 +5,6 @@ import React, {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { SettingsSectionConfig } from '../SettingsModal';
|
||||
import { IconX } from '../../ui/icons';
|
||||
import CapabilityDropdown, { CapabilityDropdownOption } from '../components/CapabilityDropdown';
|
||||
import type { CapabilitySetId, CapabilityValue } from '../../types/identifiers';
|
||||
@@ -641,10 +640,4 @@ const CapabilitySetsSection: React.FC<CapabilitySetsSectionProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const CAPABILITY_SETS_SECTION: SettingsSectionConfig = {
|
||||
id: 'capabilitySets',
|
||||
label: 'Capability sets',
|
||||
component: CapabilitySetsSection,
|
||||
};
|
||||
|
||||
export default CapabilitySetsSection;
|
||||
|
||||
@@ -172,10 +172,4 @@ const PasskeysSection = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const PASSKEYS_SECTION = {
|
||||
id: 'passkeys',
|
||||
label: 'Passkeys',
|
||||
component: PasskeysSection,
|
||||
};
|
||||
|
||||
export default PasskeysSection;
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import PasskeysSection, { PASSKEYS_SECTION } from './PasskeysSection';
|
||||
import ApiTokensSection, { API_TOKENS_SECTION } from './ApiTokensSection';
|
||||
import CapabilitySetsSection, { CAPABILITY_SETS_SECTION } from './CapabilitySetsSection';
|
||||
|
||||
export const DEFAULT_SETTINGS_SECTIONS = [
|
||||
PASSKEYS_SECTION,
|
||||
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 { PasskeysSection, ApiTokensSection, CapabilitySetsSection };
|
||||
export { PASSKEYS_SECTION, API_TOKENS_SECTION, CAPABILITY_SETS_SECTION, DEFAULT_SETTINGS_SECTIONS };
|
||||
|
||||
@@ -32,6 +32,7 @@ import { useSidebarContext } from './SidebarContext';
|
||||
import { usePanelManager, usePanelResizeBindings } from '../app/PanelManagerContext';
|
||||
import type { StatusMessage } from '../hooks/documents/store/useDocumentsStore';
|
||||
import type { Identifier } from '../types/identifiers';
|
||||
import { THEME_MODE_LABELS, THEME_MODES } from '../constants/sidebar';
|
||||
|
||||
interface CommunityLink {
|
||||
label: string;
|
||||
@@ -260,13 +261,6 @@ const FolderNode: React.FC<FolderNodeProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const THEME_MODES = ['system', 'light', 'dark'];
|
||||
const THEME_MODE_LABELS = {
|
||||
system: 'System',
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
};
|
||||
|
||||
interface FloatingMenuControls {
|
||||
isOpen: boolean;
|
||||
toggle: () => void;
|
||||
|
||||
@@ -6,6 +6,16 @@ import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import {
|
||||
DARK_MODE_MEDIA_QUERY,
|
||||
DEFAULT_NEUTRAL_CHROMA,
|
||||
DEFAULT_NEUTRAL_CONTRAST,
|
||||
DEFAULT_NEUTRAL_HUE,
|
||||
DEFAULT_THEME_MODE,
|
||||
SIDEBAR_COLLAPSE_STORAGE_KEY,
|
||||
THEME_MODES,
|
||||
THEME_STORAGE_KEY,
|
||||
} from '../constants/sidebar';
|
||||
|
||||
interface SidebarContextValue {
|
||||
collapsed: boolean;
|
||||
@@ -25,20 +35,10 @@ interface SidebarContextValue {
|
||||
themeModes: ThemeMode[];
|
||||
}
|
||||
|
||||
type ThemeMode = 'system' | 'light' | 'dark';
|
||||
|
||||
const SIDEBAR_COLLAPSE_STORAGE_KEY = 'papercrate_sidebar_collapsed';
|
||||
type ThemeMode = (typeof THEME_MODES)[number];
|
||||
|
||||
const SidebarContext = createContext<SidebarContextValue | null>(null);
|
||||
|
||||
const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
||||
const DEFAULT_NEUTRAL_HUE = 29;
|
||||
const DEFAULT_NEUTRAL_CHROMA = 0.44;
|
||||
const DEFAULT_NEUTRAL_CONTRAST = 0.56;
|
||||
const DEFAULT_THEME_MODE = 'system';
|
||||
const THEME_MODES = ['system', 'light', 'dark'];
|
||||
const DARK_MODE_MEDIA_QUERY = '(prefers-color-scheme: dark)';
|
||||
|
||||
const loadInitialThemeSettings = () => {
|
||||
const defaults = {
|
||||
neutralHue: DEFAULT_NEUTRAL_HUE,
|
||||
|
||||
@@ -8,6 +8,12 @@ import React, {
|
||||
import { createPortal } from 'react-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import useFloatingMenu from './useFloatingMenu';
|
||||
import {
|
||||
ELLIPSIS,
|
||||
WIDTH_BUFFER_RATIO,
|
||||
WIDTH_CHANGE_TOLERANCE,
|
||||
WIDTH_TOLERANCE,
|
||||
} from '../constants/ui';
|
||||
|
||||
const normalizeEntries = (entries) =>
|
||||
(Array.isArray(entries) ? entries : [])
|
||||
@@ -22,11 +28,6 @@ const normalizeEntries = (entries) =>
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const ELLIPSIS = { id: '__breadcrumbs_ellipsis__', label: '…', onClick: null, raw: null };
|
||||
const WIDTH_TOLERANCE = 1;
|
||||
const WIDTH_BUFFER_RATIO = 0.95;
|
||||
const WIDTH_CHANGE_TOLERANCE = 0.25;
|
||||
|
||||
const BreadcrumbTrail = ({
|
||||
entries = [],
|
||||
className = '',
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import type { MutableRefObject, CSSProperties } from 'react';
|
||||
import { clamp } from '../utils/math';
|
||||
|
||||
const DEFAULT_VIEWPORT_MARGIN = 8;
|
||||
import { DEFAULT_VIEWPORT_MARGIN } from '../constants/ui';
|
||||
|
||||
type PositionStrategy = 'fixed' | 'absolute' | (string & {});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const HEX_COLOR_PATTERN = /^#?([0-9a-fA-F]{6})$/;
|
||||
import { HEX_COLOR_PATTERN } from '../constants/colors';
|
||||
|
||||
const clamp01 = (value: number): number => Math.min(1, Math.max(0, value));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user