mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-12-06 04:24:41 +01:00
Merge remote-tracking branch 'origin/master' into zsviczian-stickynote
This commit is contained in:
@@ -6,32 +6,6 @@ import type { AppProps, AppState } from "@excalidraw/excalidraw/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 =
|
||||
typeof window !== "undefined" &&
|
||||
"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/i.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 isMobile =
|
||||
isIOS ||
|
||||
/android|webos|ipod|blackberry|iemobile|opera mini/i.test(
|
||||
navigator.userAgent,
|
||||
) ||
|
||||
/android|ios|ipod|blackberry|windows phone/i.test(navigator.platform);
|
||||
|
||||
export const supportsResizeObserver =
|
||||
typeof window !== "undefined" && "ResizeObserver" in window;
|
||||
|
||||
@@ -131,6 +105,7 @@ export const CLASSES = {
|
||||
SEARCH_MENU_INPUT_WRAPPER: "layer-ui__search-inputWrapper",
|
||||
CONVERT_ELEMENT_TYPE_POPUP: "ConvertElementTypePopup",
|
||||
SHAPE_ACTIONS_THEME_SCOPE: "shape-actions-theme-scope",
|
||||
FRAME_NAME: "frame-name",
|
||||
};
|
||||
|
||||
export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai";
|
||||
@@ -349,26 +324,6 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
|
||||
},
|
||||
};
|
||||
|
||||
// breakpoints
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// mobile: up to 699px
|
||||
export const MQ_MAX_MOBILE = 599;
|
||||
|
||||
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||
|
||||
// tablets
|
||||
export const MQ_MIN_TABLET = MQ_MAX_MOBILE + 1; // lower bound (excludes phones)
|
||||
export const MQ_MAX_TABLET = 1400; // upper bound (excludes laptops/desktops)
|
||||
|
||||
// desktop/laptop
|
||||
export const MQ_MIN_WIDTH_DESKTOP = 1440;
|
||||
|
||||
// sidebar
|
||||
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
export const MAX_DECIMALS_FOR_SVG_EXPORT = 2;
|
||||
|
||||
export const EXPORT_SCALES = [1, 2, 3];
|
||||
@@ -549,6 +504,8 @@ export const LINE_POLYGON_POINT_MERGE_DISTANCE = 20;
|
||||
|
||||
export const DOUBLE_TAP_POSITION_THRESHOLD = 35;
|
||||
|
||||
export const BIND_MODE_TIMEOUT = 700; // ms
|
||||
|
||||
// glass background for mobile action buttons
|
||||
export const MOBILE_ACTION_BUTTON_BG = {
|
||||
background: "var(--mobile-action-button-bg)",
|
||||
|
||||
223
packages/common/src/editorInterface.ts
Normal file
223
packages/common/src/editorInterface.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
export type StylesPanelMode = "compact" | "full" | "mobile";
|
||||
|
||||
export type EditorInterface = Readonly<{
|
||||
formFactor: "phone" | "tablet" | "desktop";
|
||||
desktopUIMode: "compact" | "full";
|
||||
userAgent: Readonly<{
|
||||
isMobileDevice: boolean;
|
||||
platform: "ios" | "android" | "other" | "unknown";
|
||||
}>;
|
||||
isTouchScreen: boolean;
|
||||
canFitSidebar: boolean;
|
||||
isLandscape: boolean;
|
||||
}>;
|
||||
|
||||
// storage key
|
||||
const DESKTOP_UI_MODE_STORAGE_KEY = "excalidraw.desktopUIMode";
|
||||
|
||||
// breakpoints
|
||||
// mobile: up to 699px
|
||||
export const MQ_MAX_MOBILE = 599;
|
||||
|
||||
export const MQ_MAX_WIDTH_LANDSCAPE = 1000;
|
||||
export const MQ_MAX_HEIGHT_LANDSCAPE = 500;
|
||||
|
||||
// tablets
|
||||
export const MQ_MIN_TABLET = MQ_MAX_MOBILE + 1; // lower bound (excludes phones)
|
||||
export const MQ_MAX_TABLET = 1400; // upper bound (excludes laptops/desktops)
|
||||
|
||||
// desktop/laptop
|
||||
export const MQ_MIN_WIDTH_DESKTOP = 1440;
|
||||
|
||||
// sidebar
|
||||
export const MQ_RIGHT_SIDEBAR_MIN_WIDTH = 1229;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// user agent detections
|
||||
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 =
|
||||
typeof window !== "undefined" &&
|
||||
"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/i.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 isMobile =
|
||||
// isIOS ||
|
||||
// /android|webos|ipod|blackberry|iemobile|opera mini/i.test(
|
||||
// navigator.userAgent,
|
||||
// ) ||
|
||||
// /android|ios|ipod|blackberry|windows phone/i.test(navigator.platform);
|
||||
|
||||
// utilities
|
||||
export const isMobileBreakpoint = (width: number, height: number) => {
|
||||
return (
|
||||
width <= MQ_MAX_MOBILE ||
|
||||
(height < MQ_MAX_HEIGHT_LANDSCAPE && width < MQ_MAX_WIDTH_LANDSCAPE)
|
||||
);
|
||||
};
|
||||
|
||||
export const isTabletBreakpoint = (
|
||||
editorWidth: number,
|
||||
editorHeight: number,
|
||||
) => {
|
||||
const minSide = Math.min(editorWidth, editorHeight);
|
||||
const maxSide = Math.max(editorWidth, editorHeight);
|
||||
|
||||
return minSide >= MQ_MIN_TABLET && maxSide <= MQ_MAX_TABLET;
|
||||
};
|
||||
|
||||
const isMobileOrTablet = (): boolean => {
|
||||
const ua = navigator.userAgent || "";
|
||||
const platform = navigator.platform || "";
|
||||
const uaData = (navigator as any).userAgentData as
|
||||
| { mobile?: boolean; platform?: string }
|
||||
| undefined;
|
||||
|
||||
// --- 1) chromium: prefer ua client hints -------------------------------
|
||||
if (uaData) {
|
||||
const plat = (uaData.platform || "").toLowerCase();
|
||||
const isDesktopOS =
|
||||
plat === "windows" ||
|
||||
plat === "macos" ||
|
||||
plat === "linux" ||
|
||||
plat === "chrome os";
|
||||
if (uaData.mobile === true) {
|
||||
return true;
|
||||
}
|
||||
if (uaData.mobile === false && plat === "android") {
|
||||
const looksTouchTablet =
|
||||
matchMedia?.("(hover: none)").matches &&
|
||||
matchMedia?.("(pointer: coarse)").matches;
|
||||
return looksTouchTablet;
|
||||
}
|
||||
if (isDesktopOS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2) ios (includes ipad) --------------------------------------------
|
||||
if (isIOS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 3) android legacy ua fallback -------------------------------------
|
||||
if (isAndroid) {
|
||||
const isAndroidPhone = /Mobile/i.test(ua);
|
||||
const isAndroidTablet = !isAndroidPhone;
|
||||
if (isAndroidPhone || isAndroidTablet) {
|
||||
const looksTouchTablet =
|
||||
matchMedia?.("(hover: none)").matches &&
|
||||
matchMedia?.("(pointer: coarse)").matches;
|
||||
return looksTouchTablet;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4) last resort desktop exclusion ----------------------------------
|
||||
const looksDesktopPlatform =
|
||||
/Win|Linux|CrOS|Mac/.test(platform) ||
|
||||
/Windows NT|X11|CrOS|Macintosh/.test(ua);
|
||||
if (looksDesktopPlatform) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getFormFactor = (
|
||||
editorWidth: number,
|
||||
editorHeight: number,
|
||||
): EditorInterface["formFactor"] => {
|
||||
if (isMobileBreakpoint(editorWidth, editorHeight)) {
|
||||
return "phone";
|
||||
}
|
||||
|
||||
if (isTabletBreakpoint(editorWidth, editorHeight)) {
|
||||
return "tablet";
|
||||
}
|
||||
|
||||
return "desktop";
|
||||
};
|
||||
|
||||
export const deriveStylesPanelMode = (
|
||||
editorInterface: EditorInterface,
|
||||
): StylesPanelMode => {
|
||||
if (editorInterface.formFactor === "phone") {
|
||||
return "mobile";
|
||||
}
|
||||
|
||||
if (editorInterface.formFactor === "tablet") {
|
||||
return "compact";
|
||||
}
|
||||
|
||||
return editorInterface.desktopUIMode;
|
||||
};
|
||||
|
||||
export const createUserAgentDescriptor = (
|
||||
userAgentString: string,
|
||||
): EditorInterface["userAgent"] => {
|
||||
const normalizedUA = userAgentString ?? "";
|
||||
let platform: EditorInterface["userAgent"]["platform"] = "unknown";
|
||||
|
||||
if (isIOS) {
|
||||
platform = "ios";
|
||||
} else if (isAndroid) {
|
||||
platform = "android";
|
||||
} else if (normalizedUA) {
|
||||
platform = "other";
|
||||
}
|
||||
|
||||
return {
|
||||
isMobileDevice: isMobileOrTablet(),
|
||||
platform,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const loadDesktopUIModePreference = () => {
|
||||
if (typeof window === "undefined") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const stored = window.localStorage.getItem(DESKTOP_UI_MODE_STORAGE_KEY);
|
||||
if (stored === "compact" || stored === "full") {
|
||||
return stored as EditorInterface["desktopUIMode"];
|
||||
}
|
||||
} catch (error) {
|
||||
// ignore storage access issues (e.g., Safari private mode)
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const persistDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.localStorage.setItem(DESKTOP_UI_MODE_STORAGE_KEY, mode);
|
||||
} catch (error) {
|
||||
// ignore storage access issues (e.g., Safari private mode)
|
||||
}
|
||||
};
|
||||
|
||||
export const setDesktopUIMode = (mode: EditorInterface["desktopUIMode"]) => {
|
||||
if (mode !== "compact" && mode !== "full") {
|
||||
return;
|
||||
}
|
||||
|
||||
persistDesktopUIMode(mode);
|
||||
|
||||
return mode;
|
||||
};
|
||||
@@ -10,3 +10,5 @@ export * from "./random";
|
||||
export * from "./url";
|
||||
export * from "./utils";
|
||||
export * from "./emitter";
|
||||
export * from "./visualdebug";
|
||||
export * from "./editorInterface";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isDarwin } from "./constants";
|
||||
import { isDarwin } from "./editorInterface";
|
||||
|
||||
import type { ValueOf } from "./utility-types";
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { average } from "@excalidraw/math";
|
||||
|
||||
import type {
|
||||
ExcalidrawBindableElement,
|
||||
FontFamilyValues,
|
||||
FontString,
|
||||
} from "@excalidraw/element/types";
|
||||
import type { FontFamilyValues, FontString } from "@excalidraw/element/types";
|
||||
|
||||
import type {
|
||||
ActiveTool,
|
||||
@@ -20,8 +16,6 @@ import {
|
||||
ENV,
|
||||
FONT_FAMILY,
|
||||
getFontFamilyFallbacks,
|
||||
isAndroid,
|
||||
isIOS,
|
||||
WINDOWS_EMOJI_FALLBACK_FONT,
|
||||
} from "./constants";
|
||||
|
||||
@@ -384,6 +378,10 @@ export const removeSelection = () => {
|
||||
|
||||
export const distance = (x: number, y: number) => Math.abs(x - y);
|
||||
|
||||
export const isSelectionLikeTool = (type: ToolType | "custom") => {
|
||||
return type === "selection" || type === "lasso";
|
||||
};
|
||||
|
||||
export const updateActiveTool = (
|
||||
appState: Pick<AppState, "activeTool">,
|
||||
data: ((
|
||||
@@ -560,9 +558,6 @@ export const isTransparent = (color: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const isBindingFallthroughEnabled = (el: ExcalidrawBindableElement) =>
|
||||
el.fillStyle !== "solid" || isTransparent(el.backgroundColor);
|
||||
|
||||
export type ResolvablePromise<T> = Promise<T> & {
|
||||
resolve: [T] extends [undefined]
|
||||
? (value?: MaybePromise<Awaited<T>>) => void
|
||||
@@ -1273,58 +1268,46 @@ export const reduceToCommonValue = <T, R = T>(
|
||||
return commonValue;
|
||||
};
|
||||
|
||||
export const isMobileOrTablet = (): boolean => {
|
||||
const ua = navigator.userAgent || "";
|
||||
const platform = navigator.platform || "";
|
||||
const uaData = (navigator as any).userAgentData as
|
||||
| { mobile?: boolean; platform?: string }
|
||||
| undefined;
|
||||
|
||||
// --- 1) chromium: prefer ua client hints -------------------------------
|
||||
if (uaData) {
|
||||
const plat = (uaData.platform || "").toLowerCase();
|
||||
const isDesktopOS =
|
||||
plat === "windows" ||
|
||||
plat === "macos" ||
|
||||
plat === "linux" ||
|
||||
plat === "chrome os";
|
||||
if (uaData.mobile === true) {
|
||||
return true;
|
||||
}
|
||||
if (uaData.mobile === false && plat === "android") {
|
||||
const looksTouchTablet =
|
||||
matchMedia?.("(hover: none)").matches &&
|
||||
matchMedia?.("(pointer: coarse)").matches;
|
||||
return looksTouchTablet;
|
||||
}
|
||||
if (isDesktopOS) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2) ios (includes ipad) --------------------------------------------
|
||||
if (isIOS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- 3) android legacy ua fallback -------------------------------------
|
||||
if (isAndroid) {
|
||||
const isAndroidPhone = /Mobile/i.test(ua);
|
||||
const isAndroidTablet = !isAndroidPhone;
|
||||
if (isAndroidPhone || isAndroidTablet) {
|
||||
const looksTouchTablet =
|
||||
matchMedia?.("(hover: none)").matches &&
|
||||
matchMedia?.("(pointer: coarse)").matches;
|
||||
return looksTouchTablet;
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4) last resort desktop exclusion ----------------------------------
|
||||
const looksDesktopPlatform =
|
||||
/Win|Linux|CrOS|Mac/.test(platform) ||
|
||||
/Windows NT|X11|CrOS|Macintosh/.test(ua);
|
||||
if (looksDesktopPlatform) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
type FEATURE_FLAGS = {
|
||||
COMPLEX_BINDINGS: boolean;
|
||||
};
|
||||
|
||||
const FEATURE_FLAGS_STORAGE_KEY = "excalidraw-feature-flags";
|
||||
const DEFAULT_FEATURE_FLAGS: FEATURE_FLAGS = {
|
||||
COMPLEX_BINDINGS: false,
|
||||
};
|
||||
let featureFlags: FEATURE_FLAGS | null = null;
|
||||
|
||||
export const getFeatureFlag = <F extends keyof FEATURE_FLAGS>(
|
||||
flag: F,
|
||||
): FEATURE_FLAGS[F] => {
|
||||
if (!featureFlags) {
|
||||
try {
|
||||
const serializedFlags = localStorage.getItem(FEATURE_FLAGS_STORAGE_KEY);
|
||||
if (serializedFlags) {
|
||||
const flags = JSON.parse(serializedFlags);
|
||||
featureFlags = flags ?? DEFAULT_FEATURE_FLAGS;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return (featureFlags || DEFAULT_FEATURE_FLAGS)[flag];
|
||||
};
|
||||
|
||||
export const setFeatureFlag = <F extends keyof FEATURE_FLAGS>(
|
||||
flag: F,
|
||||
value: FEATURE_FLAGS[F],
|
||||
) => {
|
||||
try {
|
||||
featureFlags = {
|
||||
...(featureFlags || DEFAULT_FEATURE_FLAGS),
|
||||
[flag]: value,
|
||||
};
|
||||
localStorage.setItem(
|
||||
FEATURE_FLAGS_STORAGE_KEY,
|
||||
JSON.stringify(featureFlags),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("unable to set feature flag", e);
|
||||
}
|
||||
};
|
||||
|
||||
167
packages/common/src/visualdebug.ts
Normal file
167
packages/common/src/visualdebug.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import {
|
||||
isLineSegment,
|
||||
lineSegment,
|
||||
pointFrom,
|
||||
type GlobalPoint,
|
||||
type LocalPoint,
|
||||
} from "@excalidraw/math";
|
||||
|
||||
import { isBounds } from "@excalidraw/element";
|
||||
|
||||
import type { Curve } from "@excalidraw/math";
|
||||
import type { LineSegment } from "@excalidraw/utils";
|
||||
|
||||
import type { Bounds } from "@excalidraw/element";
|
||||
|
||||
// The global data holder to collect the debug operations
|
||||
declare global {
|
||||
interface Window {
|
||||
visualDebug?: {
|
||||
data: DebugElement[][];
|
||||
currentFrame?: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type DebugElement = {
|
||||
color: string;
|
||||
data: LineSegment<GlobalPoint> | Curve<GlobalPoint>;
|
||||
permanent: boolean;
|
||||
};
|
||||
|
||||
export const debugDrawCubicBezier = (
|
||||
c: Curve<GlobalPoint>,
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
},
|
||||
) => {
|
||||
addToCurrentFrame({
|
||||
color: opts?.color ?? "purple",
|
||||
permanent: !!opts?.permanent,
|
||||
data: c,
|
||||
});
|
||||
};
|
||||
|
||||
export const debugDrawLine = (
|
||||
segment: LineSegment<GlobalPoint> | LineSegment<GlobalPoint>[],
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
},
|
||||
) => {
|
||||
const segments = (
|
||||
isLineSegment(segment) ? [segment] : segment
|
||||
) as LineSegment<GlobalPoint>[];
|
||||
|
||||
segments.forEach((data) =>
|
||||
addToCurrentFrame({
|
||||
color: opts?.color ?? "red",
|
||||
data,
|
||||
permanent: !!opts?.permanent,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
export const debugDrawPoint = (
|
||||
p: GlobalPoint,
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
fuzzy?: boolean;
|
||||
},
|
||||
) => {
|
||||
const xOffset = opts?.fuzzy ? Math.random() * 3 : 0;
|
||||
const yOffset = opts?.fuzzy ? Math.random() * 3 : 0;
|
||||
|
||||
debugDrawLine(
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset - 10),
|
||||
pointFrom<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset + 10),
|
||||
),
|
||||
{
|
||||
color: opts?.color ?? "cyan",
|
||||
permanent: opts?.permanent,
|
||||
},
|
||||
);
|
||||
debugDrawLine(
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(p[0] + xOffset - 10, p[1] + yOffset + 10),
|
||||
pointFrom<GlobalPoint>(p[0] + xOffset + 10, p[1] + yOffset - 10),
|
||||
),
|
||||
{
|
||||
color: opts?.color ?? "cyan",
|
||||
permanent: opts?.permanent,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const debugDrawBounds = (
|
||||
box: Bounds | Bounds[],
|
||||
opts?: {
|
||||
color?: string;
|
||||
permanent?: boolean;
|
||||
},
|
||||
) => {
|
||||
(isBounds(box) ? [box] : box).forEach((bbox) =>
|
||||
debugDrawLine(
|
||||
[
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox[0], bbox[1]),
|
||||
pointFrom<GlobalPoint>(bbox[2], bbox[1]),
|
||||
),
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox[2], bbox[1]),
|
||||
pointFrom<GlobalPoint>(bbox[2], bbox[3]),
|
||||
),
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox[2], bbox[3]),
|
||||
pointFrom<GlobalPoint>(bbox[0], bbox[3]),
|
||||
),
|
||||
lineSegment(
|
||||
pointFrom<GlobalPoint>(bbox[0], bbox[3]),
|
||||
pointFrom<GlobalPoint>(bbox[0], bbox[1]),
|
||||
),
|
||||
],
|
||||
{
|
||||
color: opts?.color ?? "green",
|
||||
permanent: !!opts?.permanent,
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const debugDrawPoints = (
|
||||
{
|
||||
x,
|
||||
y,
|
||||
points,
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
points: readonly LocalPoint[];
|
||||
},
|
||||
options?: any,
|
||||
) => {
|
||||
points.forEach((p) =>
|
||||
debugDrawPoint(pointFrom<GlobalPoint>(x + p[0], y + p[1]), options),
|
||||
);
|
||||
};
|
||||
|
||||
export const debugCloseFrame = () => {
|
||||
window.visualDebug?.data.push([]);
|
||||
};
|
||||
|
||||
export const debugClear = () => {
|
||||
if (window.visualDebug?.data) {
|
||||
window.visualDebug.data = [];
|
||||
}
|
||||
};
|
||||
|
||||
const addToCurrentFrame = (element: DebugElement) => {
|
||||
if (window.visualDebug?.data && window.visualDebug.data.length === 0) {
|
||||
window.visualDebug.data[0] = [];
|
||||
}
|
||||
window.visualDebug?.data &&
|
||||
window.visualDebug.data[window.visualDebug.data.length - 1].push(element);
|
||||
};
|
||||
Reference in New Issue
Block a user