Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8d4b0a8df | ||
|
|
1caec99b29 |
@@ -97,8 +97,8 @@ const config = {
|
|||||||
href: "https://discord.gg/UexuTaE",
|
href: "https://discord.gg/UexuTaE",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Twitter",
|
label: "𝕏",
|
||||||
href: "https://twitter.com/excalidraw",
|
href: "https://x.com/excalidraw",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Linkedin",
|
label: "Linkedin",
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ import { useHandleAppTheme } from "./useHandleAppTheme";
|
|||||||
import { getPreferredLanguage } from "./app-language/language-detector";
|
import { getPreferredLanguage } from "./app-language/language-detector";
|
||||||
import { useAppLangCode } from "./app-language/language-state";
|
import { useAppLangCode } from "./app-language/language-state";
|
||||||
import DebugCanvas, {
|
import DebugCanvas, {
|
||||||
ConsoleLogger,
|
|
||||||
debugRenderer,
|
debugRenderer,
|
||||||
isVisualDebuggerEnabled,
|
isVisualDebuggerEnabled,
|
||||||
loadSavedDebugState,
|
loadSavedDebugState,
|
||||||
@@ -1262,7 +1261,6 @@ const ExcalidrawWrapper = () => {
|
|||||||
ref={debugCanvasRef}
|
ref={debugCanvasRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isVisualDebuggerEnabled() && <ConsoleLogger />}
|
|
||||||
</Excalidraw>
|
</Excalidraw>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ export const STORAGE_KEYS = {
|
|||||||
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
|
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
|
||||||
LOCAL_STORAGE_THEME: "excalidraw-theme",
|
LOCAL_STORAGE_THEME: "excalidraw-theme",
|
||||||
LOCAL_STORAGE_DEBUG: "excalidraw-debug",
|
LOCAL_STORAGE_DEBUG: "excalidraw-debug",
|
||||||
LOCAL_STORAGE_DEBUG_CONSOLE: "excalidraw-debug-console",
|
|
||||||
VERSION_DATA_STATE: "version-dataState",
|
VERSION_DATA_STATE: "version-dataState",
|
||||||
VERSION_FILES: "version-files",
|
VERSION_FILES: "version-files",
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import {
|
|||||||
eyeIcon,
|
eyeIcon,
|
||||||
} from "@excalidraw/excalidraw/components/icons";
|
} from "@excalidraw/excalidraw/components/icons";
|
||||||
import { MainMenu } from "@excalidraw/excalidraw/index";
|
import { MainMenu } from "@excalidraw/excalidraw/index";
|
||||||
import DropdownMenuItemCheckbox from "@excalidraw/excalidraw/components/dropdownMenu/DropdownMenuItemCheckbox";
|
import React from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
import { isDevEnv } from "@excalidraw/common";
|
import { isDevEnv } from "@excalidraw/common";
|
||||||
|
|
||||||
@@ -14,29 +13,7 @@ import type { Theme } from "@excalidraw/element/types";
|
|||||||
import { LanguageList } from "../app-language/LanguageList";
|
import { LanguageList } from "../app-language/LanguageList";
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
|
|
||||||
import {
|
import { saveDebugState } from "./DebugCanvas";
|
||||||
isVisualDebuggerEnabled,
|
|
||||||
loadConsoleLoggerState,
|
|
||||||
saveDebugState,
|
|
||||||
setConsoleLoggerEnabled,
|
|
||||||
} from "./DebugCanvas";
|
|
||||||
|
|
||||||
const ConsoleLoggerToggle = () => {
|
|
||||||
const [checked, setChecked] = useState(() => loadConsoleLoggerState());
|
|
||||||
return (
|
|
||||||
<DropdownMenuItemCheckbox
|
|
||||||
checked={checked}
|
|
||||||
onSelect={(event) => {
|
|
||||||
const next = !checked;
|
|
||||||
setChecked(next);
|
|
||||||
setConsoleLoggerEnabled(next);
|
|
||||||
event.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Show console log overlay
|
|
||||||
</DropdownMenuItemCheckbox>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AppMainMenu: React.FC<{
|
export const AppMainMenu: React.FC<{
|
||||||
onCollabDialogOpen: () => any;
|
onCollabDialogOpen: () => any;
|
||||||
@@ -100,13 +77,7 @@ export const AppMainMenu: React.FC<{
|
|||||||
</MainMenu.Item>
|
</MainMenu.Item>
|
||||||
)}
|
)}
|
||||||
<MainMenu.Separator />
|
<MainMenu.Separator />
|
||||||
<MainMenu.DefaultItems.Preferences
|
<MainMenu.DefaultItems.Preferences />
|
||||||
additionalItems={
|
|
||||||
isDevEnv() && isVisualDebuggerEnabled() ? (
|
|
||||||
<ConsoleLoggerToggle />
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<MainMenu.DefaultItems.ToggleTheme
|
<MainMenu.DefaultItems.ToggleTheme
|
||||||
allowSystemTheme
|
allowSystemTheme
|
||||||
theme={props.theme}
|
theme={props.theme}
|
||||||
|
|||||||
@@ -4,21 +4,32 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
gap: 1rem;
|
gap: 1.5rem;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|
||||||
|
.link-button {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
width: 100%;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-sidebar-promo-image {
|
.app-sidebar-promo-image {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
|
|
||||||
height: 16.25rem;
|
height: 295px;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
background-size: calc(
|
||||||
|
var(--right-sidebar-width, 100%) - var(--space-factor) * 2
|
||||||
|
);
|
||||||
|
|
||||||
background-image: radial-gradient(
|
background-image: radial-gradient(
|
||||||
circle,
|
circle,
|
||||||
transparent 60%,
|
transparent 0%,
|
||||||
var(--sidebar-bg-color) 100%
|
var(--sidebar-bg-color) 100%
|
||||||
),
|
),
|
||||||
var(--image-source);
|
var(--image-source);
|
||||||
@@ -33,4 +44,49 @@
|
|||||||
.link-button {
|
.link-button {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-sidebar-promo-text {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-promo-illustration {
|
||||||
|
position: relative;
|
||||||
|
min-height: 7rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 0.125rem;
|
||||||
|
|
||||||
|
.app-sidebar-promo-heart {
|
||||||
|
width: 46px;
|
||||||
|
transform: translateX(-14px);
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-promo-trial-note {
|
||||||
|
position: absolute;
|
||||||
|
top: 2.75rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-56%) rotate(-7deg);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.15;
|
||||||
|
color: var(--color-primary);
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-sidebar-promo-trial-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 3.35rem;
|
||||||
|
left: 55%;
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 3rem;
|
||||||
|
transform: translateX(1.75rem) rotate(10deg);
|
||||||
|
color: var(--color-primary);
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,63 @@ import { useUIAppState } from "@excalidraw/excalidraw/context/ui-appState";
|
|||||||
|
|
||||||
import "./AppSidebar.scss";
|
import "./AppSidebar.scss";
|
||||||
|
|
||||||
|
type SidebarPromoCopyProps = {
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SidebarPromoCopy = (props: SidebarPromoCopyProps) => {
|
||||||
|
return (
|
||||||
|
<div className="app-sidebar-promo-copy">
|
||||||
|
<div className="app-sidebar-promo-illustration" aria-hidden="true">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 300 250"
|
||||||
|
className="app-sidebar-promo-heart"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M 145 75
|
||||||
|
C 110 35, 60 55, 65 120
|
||||||
|
C 70 180, 140 190, 215 200
|
||||||
|
C 225 180, 260 110, 235 55
|
||||||
|
C 210 -5, 140 20, 160 105"
|
||||||
|
fill="none"
|
||||||
|
stroke="#D06B64"
|
||||||
|
strokeWidth="16"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div className="app-sidebar-promo-trial-note excalifont">
|
||||||
|
14 days of
|
||||||
|
<br />
|
||||||
|
free trial
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
className="app-sidebar-promo-trial-arrow"
|
||||||
|
viewBox="0 0 72 48"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M5 6C23 1 50 8 48 32"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M42 26L48 32L54 26"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="app-sidebar-promo-text">{props.text}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const AppSidebar = () => {
|
export const AppSidebar = () => {
|
||||||
const { theme, openSidebar } = useUIAppState();
|
const { theme, openSidebar } = useUIAppState();
|
||||||
|
|
||||||
@@ -32,15 +89,13 @@ export const AppSidebar = () => {
|
|||||||
<div
|
<div
|
||||||
className="app-sidebar-promo-image"
|
className="app-sidebar-promo-image"
|
||||||
style={{
|
style={{
|
||||||
["--image-source" as any]: `url(/oss_promo_comments_${
|
["--image-source" as any]: `url(/sidebar-comments-promo-${
|
||||||
theme === THEME.DARK ? "dark" : "light"
|
theme === THEME.DARK ? "dark" : "light"
|
||||||
}.jpg)`,
|
}.jpg)`,
|
||||||
opacity: 0.7,
|
opacity: 0.9,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="app-sidebar-promo-text">
|
<SidebarPromoCopy text="Make comments with Excalidraw+" />
|
||||||
Make comments with Excalidraw+
|
|
||||||
</div>
|
|
||||||
<LinkButton
|
<LinkButton
|
||||||
href={`${
|
href={`${
|
||||||
import.meta.env.VITE_APP_PLUS_LP
|
import.meta.env.VITE_APP_PLUS_LP
|
||||||
@@ -55,16 +110,13 @@ export const AppSidebar = () => {
|
|||||||
<div
|
<div
|
||||||
className="app-sidebar-promo-image"
|
className="app-sidebar-promo-image"
|
||||||
style={{
|
style={{
|
||||||
["--image-source" as any]: `url(/oss_promo_presentations_${
|
["--image-source" as any]: `url(/sidebar-presentation-promo-${
|
||||||
theme === THEME.DARK ? "dark" : "light"
|
theme === THEME.DARK ? "dark" : "light"
|
||||||
}.svg)`,
|
}.jpg)`,
|
||||||
backgroundSize: "60%",
|
opacity: 0.7,
|
||||||
opacity: 0.4,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="app-sidebar-promo-text">
|
<SidebarPromoCopy text="Create presentation with Excalidraw+" />
|
||||||
Create presentations with Excalidraw+
|
|
||||||
</div>
|
|
||||||
<LinkButton
|
<LinkButton
|
||||||
href={`${
|
href={`${
|
||||||
import.meta.env.VITE_APP_PLUS_LP
|
import.meta.env.VITE_APP_PLUS_LP
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from "@excalidraw/excalidraw/renderer/helpers";
|
} from "@excalidraw/excalidraw/renderer/helpers";
|
||||||
import { type AppState } from "@excalidraw/excalidraw/types";
|
import { type AppState } from "@excalidraw/excalidraw/types";
|
||||||
import { arrayToMap, throttleRAF } from "@excalidraw/common";
|
import { arrayToMap, throttleRAF } from "@excalidraw/common";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getGlobalFixedPointForBindableElement,
|
getGlobalFixedPointForBindableElement,
|
||||||
@@ -435,34 +435,6 @@ export const loadSavedDebugState = () => {
|
|||||||
export const isVisualDebuggerEnabled = () =>
|
export const isVisualDebuggerEnabled = () =>
|
||||||
Array.isArray(window.visualDebug?.data);
|
Array.isArray(window.visualDebug?.data);
|
||||||
|
|
||||||
export const loadConsoleLoggerState = (): boolean => {
|
|
||||||
try {
|
|
||||||
const raw = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_DEBUG_CONSOLE);
|
|
||||||
if (raw !== null) {
|
|
||||||
return JSON.parse(raw) === true;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const saveConsoleLoggerState = (enabled: boolean) => {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(
|
|
||||||
STORAGE_KEYS.LOCAL_STORAGE_DEBUG_CONSOLE,
|
|
||||||
JSON.stringify(enabled),
|
|
||||||
);
|
|
||||||
} catch {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const CONSOLE_LOGGER_TOGGLE_EVENT = "excalidraw-debug-console-toggle";
|
|
||||||
|
|
||||||
export const setConsoleLoggerEnabled = (enabled: boolean) => {
|
|
||||||
saveConsoleLoggerState(enabled);
|
|
||||||
window.dispatchEvent(
|
|
||||||
new CustomEvent<boolean>(CONSOLE_LOGGER_TOGGLE_EVENT, { detail: enabled }),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
||||||
const moveForward = useCallback(() => {
|
const moveForward = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
@@ -487,7 +459,6 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
|||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
window.visualDebug!.currentFrame = undefined;
|
window.visualDebug!.currentFrame = undefined;
|
||||||
_clearLogsCallback?.();
|
|
||||||
onChange();
|
onChange();
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
const trashFrames = useCallback(() => {
|
const trashFrames = useCallback(() => {
|
||||||
@@ -495,7 +466,6 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
|
|||||||
window.visualDebug.currentFrame = undefined;
|
window.visualDebug.currentFrame = undefined;
|
||||||
window.visualDebug.data = [];
|
window.visualDebug.data = [];
|
||||||
}
|
}
|
||||||
_clearLogsCallback?.();
|
|
||||||
onChange();
|
onChange();
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
|
|
||||||
@@ -593,181 +563,4 @@ const DebugCanvas = React.forwardRef<HTMLCanvasElement, DebugCanvasProps>(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
type LogLevel = "log" | "info" | "warn" | "error";
|
|
||||||
|
|
||||||
interface LogEntry {
|
|
||||||
id: number;
|
|
||||||
level: LogLevel;
|
|
||||||
message: string;
|
|
||||||
timestamp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LOG_COLORS: Record<LogLevel, string> = {
|
|
||||||
log: "rgba(220,220,220,0.9)",
|
|
||||||
info: "rgba(100,180,255,0.9)",
|
|
||||||
warn: "rgba(255,200,60,0.9)",
|
|
||||||
error: "rgba(255,90,90,0.9)",
|
|
||||||
};
|
|
||||||
|
|
||||||
const MAX_LOGS = 500;
|
|
||||||
let logIdCounter = 0;
|
|
||||||
let _clearLogsCallback: (() => void) | null = null;
|
|
||||||
|
|
||||||
export const ConsoleLogger = () => {
|
|
||||||
const [enabled, setEnabled] = useState(() => loadConsoleLoggerState());
|
|
||||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
||||||
const logsRef = useRef<LogEntry[]>([]);
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
|
||||||
const dragState = useRef<{ startY: number; startScrollTop: number } | null>(
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
const isDragging = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = (e: Event) => {
|
|
||||||
setEnabled((e as CustomEvent<boolean>).detail);
|
|
||||||
};
|
|
||||||
window.addEventListener(CONSOLE_LOGGER_TOGGLE_EVENT, handler);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener(CONSOLE_LOGGER_TOGGLE_EVENT, handler);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
_clearLogsCallback = () => {
|
|
||||||
logsRef.current = [];
|
|
||||||
setLogs([]);
|
|
||||||
};
|
|
||||||
return () => {
|
|
||||||
_clearLogsCallback = null;
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const originals: Record<LogLevel, (...args: unknown[]) => void> = {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
log: console.log.bind(console),
|
|
||||||
info: console.info.bind(console),
|
|
||||||
warn: console.warn.bind(console),
|
|
||||||
error: console.error.bind(console),
|
|
||||||
};
|
|
||||||
|
|
||||||
const patch = (level: LogLevel) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console[level] = (...args: unknown[]) => {
|
|
||||||
originals[level](...args);
|
|
||||||
const message = args
|
|
||||||
.map((a) =>
|
|
||||||
typeof a === "object" ? JSON.stringify(a, null, 0) : String(a),
|
|
||||||
)
|
|
||||||
.join(" ");
|
|
||||||
const entry: LogEntry = {
|
|
||||||
id: ++logIdCounter,
|
|
||||||
level,
|
|
||||||
message,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
logsRef.current = [...logsRef.current, entry].slice(-MAX_LOGS);
|
|
||||||
setLogs([...logsRef.current]);
|
|
||||||
if (!isDragging.current && scrollRef.current) {
|
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
(["log", "info", "warn", "error"] as LogLevel[]).forEach(patch);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
(["log", "info", "warn", "error"] as LogLevel[]).forEach((level) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console[level] = originals[level];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onPointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
||||||
if (!scrollRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dragState.current = {
|
|
||||||
startY: e.clientY,
|
|
||||||
startScrollTop: scrollRef.current.scrollTop,
|
|
||||||
};
|
|
||||||
scrollRef.current.setPointerCapture(e.pointerId);
|
|
||||||
isDragging.current = true;
|
|
||||||
e.preventDefault();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
|
|
||||||
if (!dragState.current || !scrollRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const delta = dragState.current.startY - e.clientY;
|
|
||||||
scrollRef.current.scrollTop = dragState.current.startScrollTop + delta;
|
|
||||||
e.preventDefault();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onPointerUp = useCallback(() => {
|
|
||||||
dragState.current = null;
|
|
||||||
isDragging.current = false;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!enabled || logs.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={scrollRef}
|
|
||||||
onPointerDown={onPointerDown}
|
|
||||||
onPointerMove={onPointerMove}
|
|
||||||
onPointerUp={onPointerUp}
|
|
||||||
onPointerCancel={onPointerUp}
|
|
||||||
style={{
|
|
||||||
position: "fixed",
|
|
||||||
top: 8,
|
|
||||||
right: 8,
|
|
||||||
zIndex: 9999,
|
|
||||||
maxWidth: 420,
|
|
||||||
maxHeight: "60vh",
|
|
||||||
overflowY: "auto",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: 2,
|
|
||||||
pointerEvents: "all",
|
|
||||||
cursor: "grab",
|
|
||||||
userSelect: "none",
|
|
||||||
scrollbarWidth: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{logs.map((entry) => (
|
|
||||||
<div
|
|
||||||
key={entry.id}
|
|
||||||
style={{
|
|
||||||
background: "rgba(18,18,20,0.55)",
|
|
||||||
backdropFilter: "blur(8px) saturate(1.4)",
|
|
||||||
WebkitBackdropFilter: "blur(8px) saturate(1.4)",
|
|
||||||
borderLeft: `3px solid ${LOG_COLORS[entry.level]}`,
|
|
||||||
borderRadius: 4,
|
|
||||||
padding: "2px 8px",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
fontSize: 11,
|
|
||||||
lineHeight: 1.5,
|
|
||||||
color: LOG_COLORS[entry.level],
|
|
||||||
wordBreak: "break-all",
|
|
||||||
whiteSpace: "pre-wrap",
|
|
||||||
opacity: 0.95,
|
|
||||||
boxShadow: "0 1px 4px rgba(0,0,0,0.35)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ opacity: 0.5, marginRight: 6 }}>
|
|
||||||
{entry.level.toUpperCase()}
|
|
||||||
</span>
|
|
||||||
{entry.message}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DebugCanvas;
|
export default DebugCanvas;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 134 KiB |