Refactor
This commit is contained in:
parent
624500b091
commit
c71ccaf17a
@ -878,6 +878,7 @@ const ExcalidrawWrapper = () => {
|
||||
theme={appTheme}
|
||||
setTheme={(theme) => setAppTheme(theme)}
|
||||
refresh={() => forceRefresh((prev) => !prev)}
|
||||
excalidrawAPI={excalidrawAPI}
|
||||
/>
|
||||
<AppWelcomeScreen
|
||||
onCollabDialogOpen={onCollabDialogOpen}
|
||||
@ -1154,25 +1155,38 @@ const ExcalidrawRecorderWrapper = ({ children }: React.PropsWithChildren) => {
|
||||
{ type: string; listener: EventListener }
|
||||
>
|
||||
>(new WeakMap());
|
||||
const dataRef = useRef<object[]>([]);
|
||||
const replayRef = useRef<Map<string, EventListenerOrEventListenerObject[]>>(
|
||||
new Map(),
|
||||
);
|
||||
const dataRef = useRef<object[] | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
dataRef.current.push({
|
||||
time: new Date().getTime(),
|
||||
type: "start",
|
||||
localStorage: JSON.parse(JSON.stringify(window.localStorage)),
|
||||
dimensions: {
|
||||
outerWidth: window.outerWidth,
|
||||
outerHeight: window.outerHeight,
|
||||
},
|
||||
chromeVersion: window.navigator.userAgent
|
||||
.split(" ")
|
||||
.find((v) => v.startsWith("Chrome/"))
|
||||
?.substring(7),
|
||||
});
|
||||
/**
|
||||
* Run the event listeners for the type
|
||||
*/
|
||||
window.runReplay = (type: string, payload: any) => {
|
||||
replayRef.current
|
||||
.get(type)
|
||||
?.forEach((listener) =>
|
||||
Object.hasOwn(listener, "handleEvent")
|
||||
? (listener as EventListenerObject).handleEvent(payload)
|
||||
: (listener as EventListener)(payload),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Access to the recorded data
|
||||
*/
|
||||
window.getRecordedDataRef = (): object[] | null => dataRef.current;
|
||||
window.setRecordedDataRef = (data: object[] | null) => {
|
||||
dataRef.current = data;
|
||||
};
|
||||
|
||||
Window.prototype._removeEventListener =
|
||||
Window.prototype.removeEventListener;
|
||||
/**
|
||||
* removeEventListener
|
||||
*/
|
||||
window.removeEventListener = function <K extends keyof WindowEventMap>(
|
||||
type: K,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
@ -1182,12 +1196,25 @@ const ExcalidrawRecorderWrapper = ({ children }: React.PropsWithChildren) => {
|
||||
if (existing) {
|
||||
window._removeEventListener(type, existing.listener, options);
|
||||
listenerRef.current.delete(listener);
|
||||
const eventListeners = replayRef.current.get(type);
|
||||
if (eventListeners) {
|
||||
const index = eventListeners.indexOf(existing.listener);
|
||||
if (index !== -1) {
|
||||
eventListeners.splice(index, 1);
|
||||
if (eventListeners.length === 0) {
|
||||
replayRef.current.delete(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
window._removeEventListener(type, listener, options);
|
||||
}
|
||||
};
|
||||
|
||||
Window.prototype._addEventListener = Window.prototype.addEventListener;
|
||||
/**
|
||||
* addEventListener
|
||||
*/
|
||||
window.addEventListener = function <K extends keyof WindowEventMap>(
|
||||
type: K,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
@ -1198,7 +1225,7 @@ const ExcalidrawRecorderWrapper = ({ children }: React.PropsWithChildren) => {
|
||||
|
||||
if (!existing || existing?.type !== type) {
|
||||
wrappedListener = function (...args) {
|
||||
dataRef.current.push({
|
||||
dataRef.current?.push({
|
||||
time: new Date().getTime(),
|
||||
type: "event",
|
||||
name: type,
|
||||
@ -1217,6 +1244,11 @@ const ExcalidrawRecorderWrapper = ({ children }: React.PropsWithChildren) => {
|
||||
}
|
||||
|
||||
listenerRef.current.set(listener, { type, listener: wrappedListener });
|
||||
if (replayRef.current.has(type)) {
|
||||
replayRef.current.get(type)?.push(listener);
|
||||
} else {
|
||||
replayRef.current.set(type, [listener]);
|
||||
}
|
||||
|
||||
window._addEventListener(type, wrappedListener, options);
|
||||
};
|
||||
@ -1230,14 +1262,6 @@ const ExcalidrawRecorderWrapper = ({ children }: React.PropsWithChildren) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handle = setInterval(() => {
|
||||
console.log(dataRef.current);
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(handle);
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
|
@ -2,13 +2,21 @@ import {
|
||||
loginIcon,
|
||||
ExcalLogo,
|
||||
eyeIcon,
|
||||
pngIcon,
|
||||
} from "@excalidraw/excalidraw/components/icons";
|
||||
import { MainMenu } from "@excalidraw/excalidraw/index";
|
||||
import React from "react";
|
||||
|
||||
import { isDevEnv } from "@excalidraw/common";
|
||||
import { getVersion, isDevEnv } from "@excalidraw/common";
|
||||
import { fileOpen, fileSave } from "@excalidraw/excalidraw/data/filesystem";
|
||||
import superjson from "superjson";
|
||||
import { getDefaultAppState } from "@excalidraw/excalidraw/appState";
|
||||
|
||||
import type { Theme } from "@excalidraw/element/types";
|
||||
import type {
|
||||
AppState,
|
||||
ExcalidrawImperativeAPI,
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
|
||||
import { LanguageList } from "../app-language/LanguageList";
|
||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||
@ -22,6 +30,7 @@ export const AppMainMenu: React.FC<{
|
||||
theme: Theme | "system";
|
||||
setTheme: (theme: Theme | "system") => void;
|
||||
refresh: () => void;
|
||||
excalidrawAPI: ExcalidrawImperativeAPI | null;
|
||||
}> = React.memo((props) => {
|
||||
return (
|
||||
<MainMenu>
|
||||
@ -60,21 +69,134 @@ export const AppMainMenu: React.FC<{
|
||||
{isExcalidrawPlusSignedUser ? "Sign in" : "Sign up"}
|
||||
</MainMenu.ItemLink>
|
||||
{isDevEnv() && (
|
||||
<MainMenu.Item
|
||||
icon={eyeIcon}
|
||||
onClick={() => {
|
||||
if (window.visualDebug) {
|
||||
delete window.visualDebug;
|
||||
saveDebugState({ enabled: false });
|
||||
} else {
|
||||
window.visualDebug = { data: [] };
|
||||
saveDebugState({ enabled: true });
|
||||
}
|
||||
props?.refresh();
|
||||
}}
|
||||
>
|
||||
Visual Debug
|
||||
</MainMenu.Item>
|
||||
<>
|
||||
<MainMenu.Item
|
||||
icon={eyeIcon}
|
||||
onClick={() => {
|
||||
if (window.visualDebug) {
|
||||
delete window.visualDebug;
|
||||
saveDebugState({ enabled: false });
|
||||
} else {
|
||||
window.visualDebug = { data: [] };
|
||||
saveDebugState({ enabled: true });
|
||||
}
|
||||
props?.refresh();
|
||||
}}
|
||||
>
|
||||
Visual Debug
|
||||
</MainMenu.Item>
|
||||
{props.excalidrawAPI && (
|
||||
<>
|
||||
<MainMenu.Separator />
|
||||
<MainMenu.Item
|
||||
icon={pngIcon}
|
||||
onClick={async () => {
|
||||
const blob = await fileOpen({
|
||||
description: "Excalidraw test case recording",
|
||||
extensions: ["json"],
|
||||
});
|
||||
const text = await blob.text();
|
||||
const recording = superjson.parse<any>(text);
|
||||
|
||||
window.setRecordedDataRef(null);
|
||||
|
||||
const start = recording.shift();
|
||||
|
||||
window.resizeTo(
|
||||
start.dimensions.innerWidth,
|
||||
start.dimensions.innerHeight,
|
||||
);
|
||||
if (
|
||||
Math.abs(start.dimensions.innerWidth - window.innerWidth) >
|
||||
1 ||
|
||||
Math.abs(
|
||||
start.dimensions.innerHeight - window.innerHeight,
|
||||
) > 1
|
||||
) {
|
||||
console.error("Window dimensions do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
props.excalidrawAPI!.resetScene();
|
||||
props.excalidrawAPI!.updateScene({
|
||||
elements: superjson.parse(start.scene),
|
||||
appState: {
|
||||
...getDefaultAppState(),
|
||||
...superjson.parse<AppState>(start.state),
|
||||
},
|
||||
});
|
||||
|
||||
let lastTime = start.time;
|
||||
for (const item of recording) {
|
||||
if (item.type === "event") {
|
||||
const { time, type, name, ...rest } = item;
|
||||
const delay = time - lastTime;
|
||||
lastTime = time;
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, delay),
|
||||
);
|
||||
console.log(type, name, rest);
|
||||
window.runReplay(name, rest);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Run Recording...
|
||||
</MainMenu.Item>
|
||||
<MainMenu.Item
|
||||
icon={pngIcon}
|
||||
onClick={async () => {
|
||||
window.setRecordedDataRef([
|
||||
{
|
||||
time: new Date().getTime(),
|
||||
type: "start",
|
||||
excalidrawVersion: getVersion(),
|
||||
dimensions: {
|
||||
innerWidth: window.innerWidth,
|
||||
innerHeight: window.innerHeight,
|
||||
},
|
||||
chromeVersion: window.navigator.userAgent
|
||||
.split(" ")
|
||||
.find((v) => v.startsWith("Chrome/"))
|
||||
?.substring(7),
|
||||
state: superjson.stringify(
|
||||
props.excalidrawAPI!.getAppState(),
|
||||
),
|
||||
scene: superjson.stringify(
|
||||
props.excalidrawAPI!.getSceneElementsIncludingDeleted(),
|
||||
),
|
||||
},
|
||||
]);
|
||||
}}
|
||||
>
|
||||
Start Recording
|
||||
</MainMenu.Item>
|
||||
<MainMenu.Item
|
||||
icon={pngIcon}
|
||||
onClick={async () => {
|
||||
const blob = new Blob(
|
||||
[superjson.stringify(window.getRecordedDataRef())],
|
||||
{
|
||||
type: "text/json",
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await fileSave(blob, {
|
||||
name: `testcase-${new Date().getTime()}${Math.floor(
|
||||
Math.random() * 10000,
|
||||
)}`,
|
||||
extension: "json",
|
||||
description: "Excalidraw test case recording",
|
||||
});
|
||||
} catch (error) {}
|
||||
}}
|
||||
>
|
||||
Save Recording...
|
||||
</MainMenu.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<MainMenu.Separator />
|
||||
<MainMenu.DefaultItems.ToggleTheme
|
||||
|
@ -36,6 +36,7 @@
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"socket.io-client": "4.7.2",
|
||||
"superjson": "2.2.2",
|
||||
"vite-plugin-html": "3.2.2"
|
||||
},
|
||||
"prettier": "@excalidraw/prettier-config",
|
||||
|
@ -11427,6 +11427,9 @@ declare global {
|
||||
};
|
||||
_addEventListener: typeof window.addEventListener;
|
||||
_removeEventListener: typeof window.removeEventListener;
|
||||
getRecordedDataRef: () => object[] | null;
|
||||
setRecordedDataRef: (data: object[] | null) => void;
|
||||
runReplay: (type: string, payload: any) => void;
|
||||
}
|
||||
}
|
||||
|
||||
|
19
yarn.lock
19
yarn.lock
@ -4189,6 +4189,13 @@ convert-source-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
copy-anything@^3.0.2:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0"
|
||||
integrity sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==
|
||||
dependencies:
|
||||
is-what "^4.1.8"
|
||||
|
||||
core-js-compat@^3.38.0, core-js-compat@^3.40.0:
|
||||
version "3.41.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.41.0.tgz#4cdfce95f39a8f27759b667cf693d96e5dda3d17"
|
||||
@ -6501,6 +6508,11 @@ is-weakset@^2.0.3:
|
||||
call-bound "^1.0.3"
|
||||
get-intrinsic "^1.2.6"
|
||||
|
||||
is-what@^4.1.8:
|
||||
version "4.1.16"
|
||||
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f"
|
||||
integrity sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==
|
||||
|
||||
isarray@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
@ -8941,6 +8953,13 @@ stylis@^4.1.3:
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.6.tgz#7c7b97191cb4f195f03ecab7d52f7902ed378320"
|
||||
integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
|
||||
|
||||
superjson@2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/superjson/-/superjson-2.2.2.tgz#9d52bf0bf6b5751a3c3472f1292e714782ba3173"
|
||||
integrity sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==
|
||||
dependencies:
|
||||
copy-anything "^3.0.2"
|
||||
|
||||
supports-color@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
|
Loading…
x
Reference in New Issue
Block a user