Mark Tolmacs c71ccaf17a
Refactor
2025-05-23 13:46:49 +02:00

214 lines
7.2 KiB
TypeScript

import {
loginIcon,
ExcalLogo,
eyeIcon,
pngIcon,
} from "@excalidraw/excalidraw/components/icons";
import { MainMenu } from "@excalidraw/excalidraw/index";
import React from "react";
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";
import { saveDebugState } from "./DebugCanvas";
export const AppMainMenu: React.FC<{
onCollabDialogOpen: () => any;
isCollaborating: boolean;
isCollabEnabled: boolean;
theme: Theme | "system";
setTheme: (theme: Theme | "system") => void;
refresh: () => void;
excalidrawAPI: ExcalidrawImperativeAPI | null;
}> = React.memo((props) => {
return (
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
<MainMenu.DefaultItems.Export />
<MainMenu.DefaultItems.SaveAsImage />
{props.isCollabEnabled && (
<MainMenu.DefaultItems.LiveCollaborationTrigger
isCollaborating={props.isCollaborating}
onSelect={() => props.onCollabDialogOpen()}
/>
)}
<MainMenu.DefaultItems.CommandPalette className="highlighted" />
<MainMenu.DefaultItems.SearchMenu />
<MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
<MainMenu.ItemLink
icon={ExcalLogo}
href={`${
import.meta.env.VITE_APP_PLUS_LP
}/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger`}
className=""
>
Excalidraw+
</MainMenu.ItemLink>
<MainMenu.DefaultItems.Socials />
<MainMenu.ItemLink
icon={loginIcon}
href={`${import.meta.env.VITE_APP_PLUS_APP}${
isExcalidrawPlusSignedUser ? "" : "/sign-up"
}?utm_source=signin&utm_medium=app&utm_content=hamburger`}
className="highlighted"
>
{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>
{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
allowSystemTheme
theme={props.theme}
onSelect={props.setTheme}
/>
<MainMenu.ItemCustom>
<LanguageList style={{ width: "100%" }} />
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.ChangeCanvasBackground />
</MainMenu>
);
});