diff options
Diffstat (limited to 'packages/excalidraw/constants.ts')
| -rw-r--r-- | packages/excalidraw/constants.ts | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/packages/excalidraw/constants.ts b/packages/excalidraw/constants.ts new file mode 100644 index 0000000..62b9a14 --- /dev/null +++ b/packages/excalidraw/constants.ts @@ -0,0 +1,467 @@ +import type { AppProps, AppState } from "./types"; +import type { ExcalidrawElement, FontFamilyValues } from "./element/types"; +import { COLOR_PALETTE } from "./colors"; + +export const isDarwin = /Mac|iPod|iPhone|iPad/.test(navigator.platform); +export const isWindows = /^Win/.test(navigator.platform); +export const isAndroid = /\b(android)\b/i.test(navigator.userAgent); +export const isFirefox = + "netscape" in window && + navigator.userAgent.indexOf("rv:") > 1 && + navigator.userAgent.indexOf("Gecko") > 1; +export const isChrome = navigator.userAgent.indexOf("Chrome") !== -1; +export const isSafari = + !isChrome && navigator.userAgent.indexOf("Safari") !== -1; +export const isIOS = + /iPad|iPhone/.test(navigator.platform) || + // iPadOS 13+ + (navigator.userAgent.includes("Mac") && "ontouchend" in document); +// keeping function so it can be mocked in test +export const isBrave = () => + (navigator as any).brave?.isBrave?.name === "isBrave"; + +export const supportsResizeObserver = + typeof window !== "undefined" && "ResizeObserver" in window; + +export const APP_NAME = "kj-diagramming"; + +// distance when creating text before it's considered `autoResize: false` +// we're using higher threshold so that clicks that end up being drags +// don't unintentionally create text elements that are wrapped to a few chars +// (happens a lot with fast clicks with the text tool) +export const TEXT_AUTOWRAP_THRESHOLD = 36; // px +export const DRAGGING_THRESHOLD = 10; // px +export const LINE_CONFIRM_THRESHOLD = 8; // px +export const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5; +export const ELEMENT_TRANSLATE_AMOUNT = 1; +export const TEXT_TO_CENTER_SNAP_THRESHOLD = 30; +export const SHIFT_LOCKING_ANGLE = Math.PI / 12; +export const DEFAULT_LASER_COLOR = "red"; +export const CURSOR_TYPE = { + TEXT: "text", + CROSSHAIR: "crosshair", + GRABBING: "grabbing", + GRAB: "grab", + POINTER: "pointer", + MOVE: "move", + AUTO: "", +}; +export const POINTER_BUTTON = { + MAIN: 0, + WHEEL: 1, + SECONDARY: 2, + TOUCH: -1, + ERASER: 5, +} as const; + +export const POINTER_EVENTS = { + enabled: "all", + disabled: "none", + // asserted as any so it can be freely assigned to React Element + // "pointerEnvets" CSS prop + inheritFromUI: "var(--ui-pointerEvents)" as any, +} as const; + +export enum EVENT { + COPY = "copy", + PASTE = "paste", + CUT = "cut", + KEYDOWN = "keydown", + KEYUP = "keyup", + MOUSE_MOVE = "mousemove", + RESIZE = "resize", + UNLOAD = "unload", + FOCUS = "focus", + BLUR = "blur", + DRAG_OVER = "dragover", + DROP = "drop", + GESTURE_END = "gestureend", + BEFORE_UNLOAD = "beforeunload", + GESTURE_START = "gesturestart", + GESTURE_CHANGE = "gesturechange", + POINTER_MOVE = "pointermove", + POINTER_DOWN = "pointerdown", + POINTER_UP = "pointerup", + STATE_CHANGE = "statechange", + WHEEL = "wheel", + TOUCH_START = "touchstart", + TOUCH_END = "touchend", + HASHCHANGE = "hashchange", + VISIBILITY_CHANGE = "visibilitychange", + SCROLL = "scroll", + // custom events + EXCALIDRAW_LINK = "excalidraw-link", + MENU_ITEM_SELECT = "menu.itemSelect", + MESSAGE = "message", + FULLSCREENCHANGE = "fullscreenchange", +} + +export const YOUTUBE_STATES = { + UNSTARTED: -1, + ENDED: 0, + PLAYING: 1, + PAUSED: 2, + BUFFERING: 3, + CUED: 5, +} as const; + +export const ENV = { + TEST: "test", + DEVELOPMENT: "development", +}; + +export const CLASSES = { + SHAPE_ACTIONS_MENU: "App-menu__left", + ZOOM_ACTIONS: "zoom-actions", + SEARCH_MENU_INPUT_WRAPPER: "layer-ui__search-inputWrapper", +}; + +export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai"; +export const WINDOWS_EMOJI_FALLBACK_FONT = "Segoe UI Emoji"; + +/** + * // TODO: shouldn't be really `const`, likely neither have integers as values, due to value for the custom fonts, which should likely be some hash. + * + * Let's think this through and consider: + * - https://developer.mozilla.org/en-US/docs/Web/CSS/generic-family + * - https://drafts.csswg.org/css-fonts-4/#font-family-prop + * - https://learn.microsoft.com/en-us/typography/opentype/spec/ibmfc + */ +export const FONT_FAMILY = { + Virgil: 1, + Helvetica: 2, + Cascadia: 3, + // leave 4 unused as it was historically used for Assistant (which we don't use anymore) or custom font (Obsidian) + Excalifont: 5, + Nunito: 6, + "Lilita One": 7, + "Comic Shanns": 8, + "Liberation Sans": 9, +}; + +export const FONT_FAMILY_FALLBACKS = { + [CJK_HAND_DRAWN_FALLBACK_FONT]: 100, + [WINDOWS_EMOJI_FALLBACK_FONT]: 1000, +}; + +export const getFontFamilyFallbacks = ( + fontFamily: number, +): Array<keyof typeof FONT_FAMILY_FALLBACKS> => { + switch (fontFamily) { + case FONT_FAMILY.Excalifont: + return [CJK_HAND_DRAWN_FALLBACK_FONT, WINDOWS_EMOJI_FALLBACK_FONT]; + default: + return [WINDOWS_EMOJI_FALLBACK_FONT]; + } +}; + +export const THEME = { + LIGHT: "light", + DARK: "dark", +} as const; + +export const FRAME_STYLE = { + strokeColor: "#bbb" as ExcalidrawElement["strokeColor"], + strokeWidth: 2 as ExcalidrawElement["strokeWidth"], + strokeStyle: "solid" as ExcalidrawElement["strokeStyle"], + fillStyle: "solid" as ExcalidrawElement["fillStyle"], + roughness: 0 as ExcalidrawElement["roughness"], + roundness: null as ExcalidrawElement["roundness"], + backgroundColor: "transparent" as ExcalidrawElement["backgroundColor"], + radius: 8, + nameOffsetY: 3, + nameColorLightTheme: "#999999", + nameColorDarkTheme: "#7a7a7a", + nameFontSize: 14, + nameLineHeight: 1.25, +}; + +export const MIN_FONT_SIZE = 1; +export const DEFAULT_FONT_SIZE = 20; +export const DEFAULT_FONT_FAMILY: FontFamilyValues = FONT_FAMILY.Excalifont; +export const DEFAULT_TEXT_ALIGN = "left"; +export const DEFAULT_VERTICAL_ALIGN = "top"; +export const DEFAULT_VERSION = "{version}"; +export const DEFAULT_TRANSFORM_HANDLE_SPACING = 2; + +export const SIDE_RESIZING_THRESHOLD = 2 * DEFAULT_TRANSFORM_HANDLE_SPACING; +// a small epsilon to make side resizing always take precedence +// (avoids an increase in renders and changes to tests) +export const EPSILON = 0.00001; +export const DEFAULT_COLLISION_THRESHOLD = + 2 * SIDE_RESIZING_THRESHOLD - EPSILON; + +export const COLOR_WHITE = "#ffffff"; +export const COLOR_CHARCOAL_BLACK = "#1e1e1e"; +// keep this in sync with CSS +export const COLOR_VOICE_CALL = "#a2f1a6"; + +export const CANVAS_ONLY_ACTIONS = ["selectAll"]; + +export const DEFAULT_GRID_SIZE = 20; +export const DEFAULT_GRID_STEP = 5; + +export const IMAGE_MIME_TYPES = { + svg: "image/svg+xml", + png: "image/png", + jpg: "image/jpeg", + gif: "image/gif", + webp: "image/webp", + bmp: "image/bmp", + ico: "image/x-icon", + avif: "image/avif", + jfif: "image/jfif", +} as const; + +export const MIME_TYPES = { + text: "text/plain", + html: "text/html", + json: "application/json", + // excalidraw data + excalidraw: "application/vnd.excalidraw+json", + excalidrawlib: "application/vnd.excalidrawlib+json", + // image-encoded excalidraw data + "excalidraw.svg": "image/svg+xml", + "excalidraw.png": "image/png", + // binary + binary: "application/octet-stream", + // image + ...IMAGE_MIME_TYPES, +} as const; + +export const ALLOWED_PASTE_MIME_TYPES = [ + MIME_TYPES.text, + MIME_TYPES.html, + ...Object.values(IMAGE_MIME_TYPES), +] as const; + +export const EXPORT_IMAGE_TYPES = { + png: "png", + svg: "svg", + clipboard: "clipboard", +} as const; + +export const EXPORT_DATA_TYPES = { + excalidraw: "excalidraw", + excalidrawClipboard: "excalidraw/clipboard", + excalidrawLibrary: "excalidrawlib", + excalidrawClipboardWithAPI: "excalidraw-api/clipboard", +} as const; + +export const EXPORT_SOURCE = + window.EXCALIDRAW_EXPORT_SOURCE || window.location.origin; + +// time in milliseconds +export const IMAGE_RENDER_TIMEOUT = 500; +export const TAP_TWICE_TIMEOUT = 300; +export const TOUCH_CTX_MENU_TIMEOUT = 500; +export const TITLE_TIMEOUT = 10000; +export const VERSION_TIMEOUT = 30000; +export const SCROLL_TIMEOUT = 100; +export const ZOOM_STEP = 0.1; +export const MIN_ZOOM = 0.1; +export const MAX_ZOOM = 30; +export const HYPERLINK_TOOLTIP_DELAY = 300; + +// Report a user inactive after IDLE_THRESHOLD milliseconds +export const IDLE_THRESHOLD = 60_000; +// Report a user active each ACTIVE_THRESHOLD milliseconds +export const ACTIVE_THRESHOLD = 3_000; + +// duplicates --theme-filter, should be removed soon +export const THEME_FILTER = "invert(93%) hue-rotate(180deg)"; + +export const URL_QUERY_KEYS = { + addLibrary: "addLibrary", +} as const; + +export const URL_HASH_KEYS = { + addLibrary: "addLibrary", +} as const; + +export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = { + canvasActions: { + changeViewBackgroundColor: true, + clearCanvas: true, + export: { saveFileToDisk: true }, + loadScene: true, + saveToActiveFile: true, + toggleTheme: null, + saveAsImage: true, + }, + tools: { + image: true, + }, +}; + +// breakpoints +// ----------------------------------------------------------------------------- +// md screen +export const MQ_MAX_WIDTH_PORTRAIT = 730; +export const MQ_MAX_WIDTH_LANDSCAPE = 1000; +export const MQ_MAX_HEIGHT_LANDSCAPE = 500; +// sidebar +export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229; +// ----------------------------------------------------------------------------- + +export const MAX_DECIMALS_FOR_SVG_EXPORT = 2; + +export const EXPORT_SCALES = [1, 2, 3]; +export const DEFAULT_EXPORT_PADDING = 10; // px + +export const DEFAULT_MAX_IMAGE_WIDTH_OR_HEIGHT = 1440; + +export const MAX_ALLOWED_FILE_BYTES = 4 * 1024 * 1024; + +export const SVG_NS = "http://www.w3.org/2000/svg"; + +export const ENCRYPTION_KEY_BITS = 128; + +export const VERSIONS = { + excalidraw: 2, + excalidrawLibrary: 2, +} as const; + +export const BOUND_TEXT_PADDING = 5; +export const ARROW_LABEL_WIDTH_FRACTION = 0.7; +export const ARROW_LABEL_FONT_SIZE_TO_MIN_WIDTH_RATIO = 11; + +export const VERTICAL_ALIGN = { + TOP: "top", + MIDDLE: "middle", + BOTTOM: "bottom", +}; + +export const TEXT_ALIGN = { + LEFT: "left", + CENTER: "center", + RIGHT: "right", +}; + +export const ELEMENT_READY_TO_ERASE_OPACITY = 20; + +// Radius represented as 25% of element's largest side (width/height). +// Used for LEGACY and PROPORTIONAL_RADIUS algorithms, or when the element is +// below the cutoff size. +export const DEFAULT_PROPORTIONAL_RADIUS = 0.25; +// Fixed radius for the ADAPTIVE_RADIUS algorithm. In pixels. +export const DEFAULT_ADAPTIVE_RADIUS = 32; +// roundness type (algorithm) +export const ROUNDNESS = { + // Used for legacy rounding (rectangles), which currently works the same + // as PROPORTIONAL_RADIUS, but we need to differentiate for UI purposes and + // forwards-compat. + LEGACY: 1, + + // Used for linear elements & diamonds + PROPORTIONAL_RADIUS: 2, + + // Current default algorithm for rectangles, using fixed pixel radius. + // It's working similarly to a regular border-radius, but attemps to make + // radius visually similar across differnt element sizes, especially + // very large and very small elements. + // + // NOTE right now we don't allow configuration and use a constant radius + // (see DEFAULT_ADAPTIVE_RADIUS constant) + ADAPTIVE_RADIUS: 3, +} as const; + +export const ROUGHNESS = { + architect: 0, + artist: 1, + cartoonist: 2, +} as const; + +export const STROKE_WIDTH = { + thin: 1, + bold: 2, + extraBold: 4, +} as const; + +export const DEFAULT_ELEMENT_PROPS: { + strokeColor: ExcalidrawElement["strokeColor"]; + backgroundColor: ExcalidrawElement["backgroundColor"]; + fillStyle: ExcalidrawElement["fillStyle"]; + strokeWidth: ExcalidrawElement["strokeWidth"]; + strokeStyle: ExcalidrawElement["strokeStyle"]; + roughness: ExcalidrawElement["roughness"]; + opacity: ExcalidrawElement["opacity"]; + locked: ExcalidrawElement["locked"]; +} = { + strokeColor: COLOR_PALETTE.black, + backgroundColor: COLOR_PALETTE.transparent, + fillStyle: "solid", + strokeWidth: 2, + strokeStyle: "solid", + roughness: ROUGHNESS.artist, + opacity: 100, + locked: false, +}; + +export const LIBRARY_SIDEBAR_TAB = "library"; +export const CANVAS_SEARCH_TAB = "search"; + +export const DEFAULT_SIDEBAR = { + name: "default", + defaultTab: LIBRARY_SIDEBAR_TAB, +} as const; + +export const LIBRARY_DISABLED_TYPES = new Set([ + "iframe", + "embeddable", + "image", +] as const); + +// use these constants to easily identify reference sites +export const TOOL_TYPE = { + selection: "selection", + rectangle: "rectangle", + diamond: "diamond", + ellipse: "ellipse", + arrow: "arrow", + line: "line", + freedraw: "freedraw", + text: "text", + image: "image", + eraser: "eraser", + hand: "hand", + frame: "frame", + magicframe: "magicframe", + embeddable: "embeddable", + laser: "laser", +} as const; + +export const EDITOR_LS_KEYS = { + OAI_API_KEY: "excalidraw-oai-api-key", + // legacy naming (non)scheme + MERMAID_TO_EXCALIDRAW: "mermaid-to-excalidraw", + PUBLISH_LIBRARY: "publish-library-data", +} as const; + +/** + * not translated as this is used only in public, stateless API as default value + * where filename is optional and we can't retrieve name from app state + */ +export const DEFAULT_FILENAME = "Untitled"; + +export const STATS_PANELS = { generalStats: 1, elementProperties: 2 } as const; + +export const MIN_WIDTH_OR_HEIGHT = 1; + +export const ARROW_TYPE: { [T in AppState["currentItemArrowType"]]: T } = { + sharp: "sharp", + round: "round", + elbow: "elbow", +}; + +export const DEFAULT_REDUCED_GLOBAL_ALPHA = 0.3; +export const ELEMENT_LINK_KEY = "element"; + +/** used in tests */ +export const ORIG_ID = Symbol.for("__test__originalId__"); + +export enum UserIdleState { + ACTIVE = "active", + AWAY = "away", + IDLE = "idle", +} |
