Compare commits

...

7 Commits

Author SHA1 Message Date
dependabot[bot] ff5405bacc chore(deps): bump socket.io-client from 2.3.1 to 4.5.1
Bumps [socket.io-client](https://github.com/socketio/socket.io-client) from 2.3.1 to 4.5.1.
- [Release notes](https://github.com/socketio/socket.io-client/releases)
- [Changelog](https://github.com/socketio/socket.io-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io-client/compare/2.3.1...4.5.1)

---
updated-dependencies:
- dependency-name: socket.io-client
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-31 12:55:12 +00:00
David Luzar e6de1fe4a4 feat: rewrite public UI component rendering using tunnels (#6117)
* feat: rewrite public UI component rendering using tunnels

* factor out into components

* comments

* fix variable naming

* fix not hiding welcomeScreen

* factor out AppFooter and memoize components

* remove `UIOptions.welcomeScreen` and render only from host app

* factor out tunnels into own file

* update changelog. Keep `UIOptions.welcomeScreen` as deprecated

* update changelog

* lint

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-01-31 13:53:20 +01:00
Aakansha Doshi 3a141ca77a fix: add 1px width to the container to calculate more accurately (#6174)
* fix: add 1px width to the container to calculate accurately

* fix tests
2023-01-30 18:52:56 +05:30
JUNYI OU 5ae39c9292 fix: quick typo fix (#6167) 2023-01-29 14:22:25 +01:00
Aakansha Doshi e41ea9562b fix: set the width correctly using measureText in editor (#6162) 2023-01-28 12:09:53 +01:00
Ignacio Cuadra b52c8943e4 fix: 🐛 broken emojis when wrap text (#6153)
* fix: 🐛 broken emojis when wrap text

* refactor: Delete unnecessary "else" (reduce indentation)

* fix: remove code block that causes the emojis to disappear

* Apply suggestions from code review

Co-authored-by: David Luzar <luzar.david@gmail.com>

* fix: 🚑 possibly undefined value

* Add spec

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
2023-01-26 11:49:21 +05:30
Aakansha Doshi cf38c0f933 fix: declare css variable for font in excalidraw so its available in host (#6160)
declar css variable for font in excalidraw so its available in host
2023-01-25 15:44:20 +05:30
34 changed files with 581 additions and 627 deletions
+2 -1
View File
@@ -54,7 +54,8 @@
"react-scripts": "5.0.1",
"roughjs": "4.5.2",
"sass": "1.51.0",
"socket.io-client": "2.3.1",
"socket.io-client": "4.5.4",
"tunnel-rat": "0.1.0",
"typescript": "4.9.4",
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
-3
View File
@@ -167,9 +167,6 @@
body,
html {
margin: 0;
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system,
Segoe UI, Roboto, Helvetica, Arial, sans-serif;
font-family: var(--ui-font);
-webkit-text-size-adjust: 100%;
width: 100%;
-2
View File
@@ -589,7 +589,6 @@ class App extends React.Component<AppProps, AppState> {
})
}
langCode={getLanguage().code}
isCollaborating={this.props.isCollaborating}
renderTopRightUI={renderTopRightUI}
renderCustomStats={renderCustomStats}
renderCustomSidebar={this.props.renderSidebar}
@@ -605,7 +604,6 @@ class App extends React.Component<AppProps, AppState> {
onImageAction={this.onImageAction}
renderWelcomeScreen={
!this.state.isLoading &&
this.props.UIOptions.welcomeScreen &&
this.state.showWelcomeScreen &&
this.state.activeTool.type === "selection" &&
!this.scene.getElementsIncludingDeleted().length
+51 -68
View File
@@ -8,15 +8,8 @@ import { NonDeletedExcalidrawElement } from "../element/types";
import { Language, t } from "../i18n";
import { calculateScrollCenter } from "../scene";
import { ExportType } from "../scene/types";
import {
AppProps,
AppState,
ExcalidrawProps,
BinaryFiles,
UIChildrenComponents,
UIWelcomeScreenComponents,
} from "../types";
import { isShallowEqual, muteFSAbortError, getReactChildren } from "../utils";
import { AppProps, AppState, ExcalidrawProps, BinaryFiles } from "../types";
import { isShallowEqual, muteFSAbortError } from "../utils";
import { SelectedShapeActions, ShapesSwitcher } from "./Actions";
import { ErrorDialog } from "./ErrorDialog";
import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
@@ -45,7 +38,6 @@ import { useDevice } from "../components/App";
import { Stats } from "./Stats";
import { actionToggleStats } from "../actions/actionToggleStats";
import Footer from "./footer/Footer";
import WelcomeScreen from "./welcome-screen/WelcomeScreen";
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
import { jotaiScope } from "../jotai";
import { useAtom } from "jotai";
@@ -53,6 +45,12 @@ import MainMenu from "./main-menu/MainMenu";
import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
import { HandButton } from "./HandButton";
import { isHandToolActive } from "../appState";
import {
mainMenuTunnel,
welcomeScreenMenuHintTunnel,
welcomeScreenToolbarHintTunnel,
welcomeScreenCenterTunnel,
} from "./tunnels";
interface LayerUIProps {
actionManager: ActionManager;
@@ -67,7 +65,6 @@ interface LayerUIProps {
onInsertElements: (elements: readonly NonDeletedExcalidrawElement[]) => void;
showExitZenModeBtn: boolean;
langCode: Language["code"];
isCollaborating: boolean;
renderTopRightUI?: ExcalidrawProps["renderTopRightUI"];
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
renderCustomSidebar?: ExcalidrawProps["renderSidebar"];
@@ -81,6 +78,32 @@ interface LayerUIProps {
children?: React.ReactNode;
}
const DefaultMainMenu: React.FC<{
UIOptions: AppProps["UIOptions"];
}> = ({ UIOptions }) => {
return (
<MainMenu __fallback>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.export && <MainMenu.DefaultItems.Export />}
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.saveAsImage && (
<MainMenu.DefaultItems.SaveAsImage />
)}
<MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
<MainMenu.Group title="Excalidraw links">
<MainMenu.DefaultItems.Socials />
</MainMenu.Group>
<MainMenu.Separator />
<MainMenu.DefaultItems.ToggleTheme />
<MainMenu.DefaultItems.ChangeCanvasBackground />
</MainMenu>
);
};
const LayerUI = ({
actionManager,
appState,
@@ -93,7 +116,6 @@ const LayerUI = ({
onPenModeToggle,
onInsertElements,
showExitZenModeBtn,
isCollaborating,
renderTopRightUI,
renderCustomStats,
renderCustomSidebar,
@@ -108,28 +130,6 @@ const LayerUI = ({
}: LayerUIProps) => {
const device = useDevice();
const [childrenComponents, restChildren] =
getReactChildren<UIChildrenComponents>(children, {
Menu: true,
FooterCenter: true,
WelcomeScreen: true,
});
const [WelcomeScreenComponents] = getReactChildren<UIWelcomeScreenComponents>(
renderWelcomeScreen
? (
childrenComponents?.WelcomeScreen ?? (
<WelcomeScreen>
<WelcomeScreen.Center />
<WelcomeScreen.Hints.MenuHint />
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
</WelcomeScreen>
)
)?.props?.children
: null,
);
const renderJSONExportDialog = () => {
if (!UIOptions.canvasActions.export) {
return null;
@@ -197,37 +197,12 @@ const LayerUI = ({
);
};
const renderMenu = () => {
return (
childrenComponents.Menu || (
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.export && <MainMenu.DefaultItems.Export />}
{/* FIXME we should to test for this inside the item itself */}
{UIOptions.canvasActions.saveAsImage && (
<MainMenu.DefaultItems.SaveAsImage />
)}
<MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
<MainMenu.Group title="Excalidraw links">
<MainMenu.DefaultItems.Socials />
</MainMenu.Group>
<MainMenu.Separator />
<MainMenu.DefaultItems.ToggleTheme />
<MainMenu.DefaultItems.ChangeCanvasBackground />
</MainMenu>
)
);
};
const renderCanvasActions = () => (
<div style={{ position: "relative" }}>
{WelcomeScreenComponents.MenuHint}
{/* wrapping to Fragment stops React from occasionally complaining
about identical Keys */}
<>{renderMenu()}</>
<mainMenuTunnel.Out />
{renderWelcomeScreen && <welcomeScreenMenuHintTunnel.Out />}
</div>
);
@@ -264,7 +239,6 @@ const LayerUI = ({
return (
<FixedSideContainer side="top">
{WelcomeScreenComponents.Center}
<div className="App-menu App-menu_top">
<Stack.Col
gap={6}
@@ -279,7 +253,9 @@ const LayerUI = ({
<Section heading="shapes" className="shapes-section">
{(heading: React.ReactNode) => (
<div style={{ position: "relative" }}>
{WelcomeScreenComponents.ToolbarHint}
{renderWelcomeScreen && (
<welcomeScreenToolbarHintTunnel.Out />
)}
<Stack.Col gap={4} align="start">
<Stack.Row
gap={1}
@@ -380,7 +356,16 @@ const LayerUI = ({
return (
<>
{restChildren}
{/* ------------------------- tunneled UI ---------------------------- */}
{/* make sure we render host app components first so that we can detect
them first on initial render to optimize layout shift */}
{children}
{/* render component fallbacks. Can be rendered anywhere as they'll be
tunneled away. We only render tunneled components that actually
have defaults when host do not render anything. */}
<DefaultMainMenu UIOptions={UIOptions} />
{/* ------------------------------------------------------------------ */}
{appState.isLoading && <LoadingMessage delay={250} />}
{appState.errorMessage && (
<ErrorDialog
@@ -427,8 +412,6 @@ const LayerUI = ({
renderCustomStats={renderCustomStats}
renderSidebars={renderSidebars}
device={device}
renderMenu={renderMenu}
welcomeScreenCenter={WelcomeScreenComponents.Center}
/>
)}
@@ -451,13 +434,13 @@ const LayerUI = ({
: {}
}
>
{renderWelcomeScreen && <welcomeScreenCenterTunnel.Out />}
{renderFixedSideContainer()}
<Footer
appState={appState}
actionManager={actionManager}
showExitZenModeBtn={showExitZenModeBtn}
footerCenter={childrenComponents.FooterCenter}
welcomeScreenHelp={WelcomeScreenComponents.HelpHint}
renderWelcomeScreen={renderWelcomeScreen}
/>
{appState.showStats && (
<Stats
+9 -13
View File
@@ -1,10 +1,5 @@
import React from "react";
import {
AppState,
Device,
ExcalidrawProps,
UIWelcomeScreenComponents,
} from "../types";
import { AppState, Device, ExcalidrawProps } from "../types";
import { ActionManager } from "../actions/manager";
import { t } from "../i18n";
import Stack from "./Stack";
@@ -24,6 +19,7 @@ import { Stats } from "./Stats";
import { actionToggleStats } from "../actions";
import { HandButton } from "./HandButton";
import { isHandToolActive } from "../appState";
import { mainMenuTunnel, welcomeScreenCenterTunnel } from "./tunnels";
type MobileMenuProps = {
appState: AppState;
@@ -45,8 +41,6 @@ type MobileMenuProps = {
renderCustomStats?: ExcalidrawProps["renderCustomStats"];
renderSidebars: () => JSX.Element | null;
device: Device;
renderMenu: () => React.ReactNode;
welcomeScreenCenter: UIWelcomeScreenComponents["Center"];
};
export const MobileMenu = ({
@@ -63,13 +57,11 @@ export const MobileMenu = ({
renderCustomStats,
renderSidebars,
device,
renderMenu,
welcomeScreenCenter,
}: MobileMenuProps) => {
const renderToolbar = () => {
return (
<FixedSideContainer side="top" className="App-top-bar">
{welcomeScreenCenter}
<welcomeScreenCenterTunnel.Out />
<Section heading="shapes">
{(heading: React.ReactNode) => (
<Stack.Col gap={4} align="center">
@@ -135,12 +127,16 @@ export const MobileMenu = ({
const renderAppToolbar = () => {
if (appState.viewModeEnabled) {
return <div className="App-toolbar-content">{renderMenu()}</div>;
return (
<div className="App-toolbar-content">
<mainMenuTunnel.Out />
</div>
);
}
return (
<div className="App-toolbar-content">
{renderMenu()}
<mainMenuTunnel.Out />
{actionManager.renderAction("toggleEditMenu")}
{actionManager.renderAction("undo")}
{actionManager.renderAction("redo")}
@@ -1,6 +1,6 @@
import React from "react";
import {
getDrodownMenuItemClassName,
getDropdownMenuItemClassName,
useHandleDropdownMenuItemClick,
} from "./common";
import MenuItemContent from "./DropdownMenuItemContent";
@@ -26,7 +26,7 @@ const DropdownMenuItem = ({
{...rest}
onClick={handleClick}
type="button"
className={getDrodownMenuItemClassName(className)}
className={getDropdownMenuItemClassName(className)}
title={rest.title ?? rest["aria-label"]}
>
<MenuItemContent icon={icon} shortcut={shortcut}>
@@ -1,7 +1,7 @@
import MenuItemContent from "./DropdownMenuItemContent";
import React from "react";
import {
getDrodownMenuItemClassName,
getDropdownMenuItemClassName,
useHandleDropdownMenuItemClick,
} from "./common";
@@ -29,7 +29,7 @@ const DropdownMenuItemLink = ({
href={href}
target="_blank"
rel="noreferrer"
className={getDrodownMenuItemClassName(className)}
className={getDropdownMenuItemClassName(className)}
title={rest.title ?? rest["aria-label"]}
onClick={handleClick}
>
+1 -1
View File
@@ -6,7 +6,7 @@ export const DropdownMenuContentPropsContext = React.createContext<{
onSelect?: (event: Event) => void;
}>({});
export const getDrodownMenuItemClassName = (className = "") => {
export const getDropdownMenuItemClassName = (className = "") => {
return `dropdown-menu-item dropdown-menu-item-base ${className}`.trim();
};
+6 -11
View File
@@ -1,11 +1,7 @@
import clsx from "clsx";
import { actionShortcuts } from "../../actions";
import { ActionManager } from "../../actions/manager";
import {
AppState,
UIChildrenComponents,
UIWelcomeScreenComponents,
} from "../../types";
import { AppState } from "../../types";
import {
ExitZenModeAction,
FinalizeAction,
@@ -16,19 +12,18 @@ import { useDevice } from "../App";
import { HelpButton } from "../HelpButton";
import { Section } from "../Section";
import Stack from "../Stack";
import { footerCenterTunnel, welcomeScreenHelpHintTunnel } from "../tunnels";
const Footer = ({
appState,
actionManager,
showExitZenModeBtn,
footerCenter,
welcomeScreenHelp,
renderWelcomeScreen,
}: {
appState: AppState;
actionManager: ActionManager;
showExitZenModeBtn: boolean;
footerCenter: UIChildrenComponents["FooterCenter"];
welcomeScreenHelp: UIWelcomeScreenComponents["HelpHint"];
renderWelcomeScreen: boolean;
}) => {
const device = useDevice();
const showFinalize =
@@ -73,14 +68,14 @@ const Footer = ({
</Section>
</Stack.Col>
</div>
{footerCenter}
<footerCenterTunnel.Out />
<div
className={clsx("layer-ui__wrapper__footer-right zen-mode-transition", {
"transition-right disable-pointerEvents": appState.zenModeEnabled,
})}
>
<div style={{ position: "relative" }}>
{welcomeScreenHelp}
{renderWelcomeScreen && <welcomeScreenHelpHintTunnel.Out />}
<HelpButton
onClick={() => actionManager.executeAction(actionShortcuts)}
/>
+11 -8
View File
@@ -1,18 +1,21 @@
import clsx from "clsx";
import { useExcalidrawAppState } from "../App";
import { footerCenterTunnel } from "../tunnels";
import "./FooterCenter.scss";
const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
const appState = useExcalidrawAppState();
return (
<div
className={clsx("footer-center zen-mode-transition", {
"layer-ui__wrapper__footer-left--transition-bottom":
appState.zenModeEnabled,
})}
>
{children}
</div>
<footerCenterTunnel.In>
<div
className={clsx("footer-center zen-mode-transition", {
"layer-ui__wrapper__footer-left--transition-bottom":
appState.zenModeEnabled,
})}
>
{children}
</div>
</footerCenterTunnel.In>
);
};
@@ -0,0 +1,50 @@
import { atom, useAtom } from "jotai";
import React, { useLayoutEffect } from "react";
export const withInternalFallback = <P,>(
componentName: string,
Component: React.FC<P>,
) => {
const counterAtom = atom(0);
// flag set on initial render to tell the fallback component to skip the
// render until mount counter are initialized. This is because the counter
// is initialized in an effect, and thus we could end rendering both
// components at the same time until counter is initialized.
let preferHost = false;
const WrapperComponent: React.FC<
P & {
__fallback?: boolean;
}
> = (props) => {
const [counter, setCounter] = useAtom(counterAtom);
useLayoutEffect(() => {
setCounter((counter) => counter + 1);
return () => {
setCounter((counter) => counter - 1);
};
}, [setCounter]);
if (!props.__fallback) {
preferHost = true;
}
// ensure we don't render fallback and host components at the same time
if (
// either before the counters are initialized
(!counter && props.__fallback && preferHost) ||
// or after the counters are initialized, and both are rendered
// (this is the default when host renders as well)
(counter > 1 && props.__fallback)
) {
return null;
}
return <Component {...props} />;
};
WrapperComponent.displayName = componentName;
return WrapperComponent;
};
+65 -54
View File
@@ -11,62 +11,73 @@ import * as DefaultItems from "./DefaultItems";
import { UserList } from "../UserList";
import { t } from "../../i18n";
import { HamburgerMenuIcon } from "../icons";
import { withInternalFallback } from "../hoc/withInternalFallback";
import { composeEventHandlers } from "../../utils";
import { mainMenuTunnel } from "../tunnels";
const MainMenu = ({
children,
onSelect,
}: {
children?: React.ReactNode;
/**
* Called when any menu item is selected (clicked on).
*/
onSelect?: (event: Event) => void;
}) => {
const device = useDevice();
const appState = useExcalidrawAppState();
const setAppState = useExcalidrawSetAppState();
const onClickOutside = device.isMobile
? undefined
: () => setAppState({ openMenu: null });
const MainMenu = Object.assign(
withInternalFallback(
"MainMenu",
({
children,
onSelect,
}: {
children?: React.ReactNode;
/**
* Called when any menu item is selected (clicked on).
*/
onSelect?: (event: Event) => void;
}) => {
const device = useDevice();
const appState = useExcalidrawAppState();
const setAppState = useExcalidrawSetAppState();
const onClickOutside = device.isMobile
? undefined
: () => setAppState({ openMenu: null });
return (
<DropdownMenu open={appState.openMenu === "canvas"}>
<DropdownMenu.Trigger
onToggle={() => {
setAppState({
openMenu: appState.openMenu === "canvas" ? null : "canvas",
});
}}
>
{HamburgerMenuIcon}
</DropdownMenu.Trigger>
<DropdownMenu.Content
onClickOutside={onClickOutside}
onSelect={composeEventHandlers(onSelect, () => {
setAppState({ openMenu: null });
})}
>
{children}
{device.isMobile && appState.collaborators.size > 0 && (
<fieldset className="UserList-Wrapper">
<legend>{t("labels.collaborators")}</legend>
<UserList mobile={true} collaborators={appState.collaborators} />
</fieldset>
)}
</DropdownMenu.Content>
</DropdownMenu>
);
};
MainMenu.Trigger = DropdownMenu.Trigger;
MainMenu.Item = DropdownMenu.Item;
MainMenu.ItemLink = DropdownMenu.ItemLink;
MainMenu.ItemCustom = DropdownMenu.ItemCustom;
MainMenu.Group = DropdownMenu.Group;
MainMenu.Separator = DropdownMenu.Separator;
MainMenu.DefaultItems = DefaultItems;
return (
<mainMenuTunnel.In>
<DropdownMenu open={appState.openMenu === "canvas"}>
<DropdownMenu.Trigger
onToggle={() => {
setAppState({
openMenu: appState.openMenu === "canvas" ? null : "canvas",
});
}}
>
{HamburgerMenuIcon}
</DropdownMenu.Trigger>
<DropdownMenu.Content
onClickOutside={onClickOutside}
onSelect={composeEventHandlers(onSelect, () => {
setAppState({ openMenu: null });
})}
>
{children}
{device.isMobile && appState.collaborators.size > 0 && (
<fieldset className="UserList-Wrapper">
<legend>{t("labels.collaborators")}</legend>
<UserList
mobile={true}
collaborators={appState.collaborators}
/>
</fieldset>
)}
</DropdownMenu.Content>
</DropdownMenu>
</mainMenuTunnel.In>
);
},
),
{
Trigger: DropdownMenu.Trigger,
Item: DropdownMenu.Item,
ItemLink: DropdownMenu.ItemLink,
ItemCustom: DropdownMenu.ItemCustom,
Group: DropdownMenu.Group,
Separator: DropdownMenu.Separator,
DefaultItems,
},
);
export default MainMenu;
MainMenu.displayName = "Menu";
+8
View File
@@ -0,0 +1,8 @@
import tunnel from "tunnel-rat";
export const mainMenuTunnel = tunnel();
export const welcomeScreenMenuHintTunnel = tunnel();
export const welcomeScreenToolbarHintTunnel = tunnel();
export const welcomeScreenHelpHintTunnel = tunnel();
export const welcomeScreenCenterTunnel = tunnel();
export const footerCenterTunnel = tunnel();
@@ -7,6 +7,7 @@ import {
useExcalidrawAppState,
} from "../App";
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
import { welcomeScreenCenterTunnel } from "../tunnels";
const WelcomeScreenMenuItemContent = ({
icon,
@@ -89,18 +90,20 @@ WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
const Center = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="welcome-screen-center">
{children || (
<>
<Logo />
<Heading>{t("welcomeScreen.defaults.center_heading")}</Heading>
<Menu>
<MenuItemLoadScene />
<MenuItemHelp />
</Menu>
</>
)}
</div>
<welcomeScreenCenterTunnel.In>
<div className="welcome-screen-center">
{children || (
<>
<Logo />
<Heading>{t("welcomeScreen.defaults.center_heading")}</Heading>
<Menu>
<MenuItemLoadScene />
<MenuItemHelp />
</Menu>
</>
)}
</div>
</welcomeScreenCenterTunnel.In>
);
};
Center.displayName = "Center";
@@ -4,37 +4,48 @@ import {
WelcomeScreenMenuArrow,
WelcomeScreenTopToolbarArrow,
} from "../icons";
import {
welcomeScreenMenuHintTunnel,
welcomeScreenToolbarHintTunnel,
welcomeScreenHelpHintTunnel,
} from "../tunnels";
const MenuHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
{WelcomeScreenMenuArrow}
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.menuHint")}
<welcomeScreenMenuHintTunnel.In>
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
{WelcomeScreenMenuArrow}
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.menuHint")}
</div>
</div>
</div>
</welcomeScreenMenuHintTunnel.In>
);
};
MenuHint.displayName = "MenuHint";
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.toolbarHint")}
<welcomeScreenToolbarHintTunnel.In>
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.toolbarHint")}
</div>
{WelcomeScreenTopToolbarArrow}
</div>
{WelcomeScreenTopToolbarArrow}
</div>
</welcomeScreenToolbarHintTunnel.In>
);
};
ToolbarHint.displayName = "ToolbarHint";
const HelpHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
{WelcomeScreenHelpArrow}
</div>
<welcomeScreenHelpHintTunnel.In>
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
{WelcomeScreenHelpArrow}
</div>
</welcomeScreenHelpHintTunnel.In>
);
};
HelpHint.displayName = "HelpHint";
@@ -3,12 +3,21 @@ import { MenuHint, ToolbarHint, HelpHint } from "./WelcomeScreen.Hints";
import "./WelcomeScreen.scss";
const WelcomeScreen = (props: { children: React.ReactNode }) => {
// NOTE this component is used as a dummy wrapper to retrieve child props
// from, and will never be rendered to DOM directly. As such, we can't
// do anything here (use hooks and such)
return null;
const WelcomeScreen = (props: { children?: React.ReactNode }) => {
return (
<>
{props.children || (
<>
<Center />
<MenuHint />
<ToolbarHint />
<HelpHint />
</>
)}
</>
);
};
WelcomeScreen.displayName = "WelcomeScreen";
WelcomeScreen.Center = Center;
-1
View File
@@ -159,7 +159,6 @@ export const DEFAULT_UI_OPTIONS: AppProps["UIOptions"] = {
toggleTheme: null,
saveAsImage: true,
},
welcomeScreen: true,
};
// breakpoints
+4
View File
@@ -8,6 +8,10 @@
}
.excalidraw {
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI,
Roboto, Helvetica, Arial, sans-serif;
font-family: var(--ui-font);
position: relative;
overflow: hidden;
color: var(--text-primary-color);
+15 -1
View File
@@ -12,6 +12,20 @@ describe("Test wrapText", () => {
expect(res).toBe("Hello whats up ");
});
it("should work with emojis", () => {
const text = "😀";
const maxWidth = 1;
const res = wrapText(text, font, maxWidth);
expect(res).toBe("😀");
});
it("should show the text correctly when min width reached", () => {
const text = "Hello😀";
const maxWidth = 10;
const res = wrapText(text, font, maxWidth);
expect(res).toBe("H\ne\nl\nl\no\n😀");
});
describe("When text doesn't contain new lines", () => {
const text = "Hello whats up";
[
@@ -157,7 +171,7 @@ describe("Test measureText", () => {
expect(res.container).toMatchInlineSnapshot(`
<div
style="position: absolute; white-space: pre-wrap; font: Emoji 20px 20px; min-height: 1em; width: 111px; overflow: hidden; word-break: break-word; line-height: 0px;"
style="position: absolute; white-space: pre-wrap; font: Emoji 20px 20px; min-height: 1em; max-width: 191px; overflow: hidden; word-break: break-word; line-height: 0px;"
>
<span
style="display: inline-block; overflow: hidden; width: 1px; height: 1px;"
+79 -82
View File
@@ -271,12 +271,11 @@ export const measureText = (
container.style.whiteSpace = "pre";
container.style.font = font;
container.style.minHeight = "1em";
const textWidth = getTextWidth(text, font);
if (maxWidth) {
const lineHeight = getApproxLineHeight(font);
container.style.width = `${String(Math.min(textWidth, maxWidth) + 1)}px`;
// since we are adding a span of width 1px later
container.style.maxWidth = `${maxWidth + 1}px`;
container.style.overflow = "hidden";
container.style.wordBreak = "break-word";
container.style.lineHeight = `${String(lineHeight)}px`;
@@ -293,11 +292,8 @@ export const measureText = (
container.appendChild(span);
// Baseline is important for positioning text on canvas
const baseline = span.offsetTop + span.offsetHeight;
// Since span adds 1px extra width to the container
let width = container.offsetWidth;
if (maxWidth && textWidth > maxWidth) {
width = width - 1;
}
// since we are adding a span of width 1px
const width = container.offsetWidth + 1;
const height = container.offsetHeight;
document.body.removeChild(container);
if (isTestEnv()) {
@@ -332,8 +328,11 @@ const getLineWidth = (text: string, font: FontString) => {
if (isTestEnv()) {
return metrics.width * 10;
}
// Since measureText behaves differently in different browsers
// OS so considering a adjustment factor of 0.2
const adjustmentFactor = 0.2;
return metrics.width;
return metrics.width + adjustmentFactor;
};
export const getTextWidth = (text: string, font: FontString) => {
@@ -359,96 +358,94 @@ export const wrapText = (text: string, font: FontString, maxWidth: number) => {
// This means its newline so push it
if (words.length === 1 && words[0] === "") {
lines.push(words[0]);
} else {
let currentLine = "";
let currentLineWidthTillNow = 0;
return; // continue
}
let currentLine = "";
let currentLineWidthTillNow = 0;
let index = 0;
while (index < words.length) {
const currentWordWidth = getLineWidth(words[index], font);
let index = 0;
while (index < words.length) {
const currentWordWidth = getLineWidth(words[index], font);
// Start breaking longer words exceeding max width
if (currentWordWidth >= maxWidth) {
// push current line since the current word exceeds the max width
// so will be appended in next line
// Start breaking longer words exceeding max width
if (currentWordWidth >= maxWidth) {
// push current line since the current word exceeds the max width
// so will be appended in next line
push(currentLine);
currentLine = "";
currentLineWidthTillNow = 0;
while (words[index].length > 0) {
const currentChar = String.fromCodePoint(
words[index].codePointAt(0)!,
);
const width = charWidth.calculate(currentChar, font);
currentLineWidthTillNow += width;
words[index] = words[index].slice(currentChar.length);
if (currentLineWidthTillNow >= maxWidth) {
// only remove last trailing space which we have added when joining words
if (currentLine.slice(-1) === " ") {
currentLine = currentLine.slice(0, -1);
}
push(currentLine);
currentLine = currentChar;
currentLineWidthTillNow = width;
} else {
currentLine += currentChar;
}
}
// push current line if appending space exceeds max width
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
push(currentLine);
currentLine = "";
currentLineWidthTillNow = 0;
while (words[index].length > 0) {
const currentChar = words[index][0];
const width = charWidth.calculate(currentChar, font);
currentLineWidthTillNow += width;
words[index] = words[index].slice(1);
if (currentLineWidthTillNow >= maxWidth) {
// only remove last trailing space which we have added when joining words
if (currentLine.slice(-1) === " ") {
currentLine = currentLine.slice(0, -1);
}
push(currentLine);
currentLine = currentChar;
currentLineWidthTillNow = width;
if (currentLineWidthTillNow === maxWidth) {
currentLine = "";
currentLineWidthTillNow = 0;
}
} else {
currentLine += currentChar;
}
}
// push current line if appending space exceeds max width
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
push(currentLine);
currentLine = "";
currentLineWidthTillNow = 0;
} else {
// space needs to be appended before next word
// as currentLine contains chars which couldn't be appended
// to previous line
currentLine += " ";
currentLineWidthTillNow += spaceWidth;
}
index++;
} else {
// Start appending words in a line till max width reached
while (currentLineWidthTillNow < maxWidth && index < words.length) {
const word = words[index];
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
// space needs to be appended before next word
// as currentLine contains chars which couldn't be appended
// to previous line
currentLine += " ";
currentLineWidthTillNow += spaceWidth;
}
if (currentLineWidthTillNow >= maxWidth) {
push(currentLine);
currentLineWidthTillNow = 0;
currentLine = "";
index++;
} else {
// Start appending words in a line till max width reached
while (currentLineWidthTillNow < maxWidth && index < words.length) {
const word = words[index];
currentLineWidthTillNow = getLineWidth(currentLine + word, font);
break;
}
index++;
currentLine += `${word} `;
if (currentLineWidthTillNow >= maxWidth) {
push(currentLine);
currentLineWidthTillNow = 0;
currentLine = "";
// Push the word if appending space exceeds max width
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
const word = currentLine.slice(0, -1);
push(word);
currentLine = "";
currentLineWidthTillNow = 0;
break;
}
break;
}
if (currentLineWidthTillNow === maxWidth) {
index++;
currentLine += `${word} `;
// Push the word if appending space exceeds max width
if (currentLineWidthTillNow + spaceWidth >= maxWidth) {
const word = currentLine.slice(0, -1);
push(word);
currentLine = "";
currentLineWidthTillNow = 0;
break;
}
}
}
if (currentLine) {
// only remove last trailing space which we have added when joining words
if (currentLine.slice(-1) === " ") {
currentLine = currentLine.slice(0, -1);
if (currentLineWidthTillNow === maxWidth) {
currentLine = "";
currentLineWidthTillNow = 0;
}
push(currentLine);
}
}
if (currentLine) {
// only remove last trailing space which we have added when joining words
if (currentLine.slice(-1) === " ") {
currentLine = currentLine.slice(0, -1);
}
push(currentLine);
}
});
return lines.join("\n");
};
+3 -3
View File
@@ -862,7 +862,7 @@ describe("textWysiwyg", () => {
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
Array [
110,
109.5,
17,
]
`);
@@ -910,7 +910,7 @@ describe("textWysiwyg", () => {
resize(rectangle, "ne", [rectangle.x + 100, rectangle.y - 100]);
expect([h.elements[1].x, h.elements[1].y]).toMatchInlineSnapshot(`
Array [
425,
424,
-539,
]
`);
@@ -1026,7 +1026,7 @@ describe("textWysiwyg", () => {
mouse.up(rectangle.x + 100, rectangle.y + 50);
expect(rectangle.x).toBe(80);
expect(rectangle.y).toBe(85);
expect(text.x).toBe(90);
expect(text.x).toBe(89.5);
expect(text.y).toBe(90);
Keyboard.withModifierKeys({ ctrl: true }, () => {
+12 -4
View File
@@ -142,11 +142,11 @@ export const textWysiwyg = ({
const appState = app.state;
const updatedTextElement =
Scene.getScene(element)?.getElement<ExcalidrawTextElement>(id);
if (!updatedTextElement) {
return;
}
const { textAlign, verticalAlign } = updatedTextElement;
const approxLineHeight = getApproxLineHeight(
getFontString(updatedTextElement),
);
@@ -161,6 +161,7 @@ export const textWysiwyg = ({
// Set to element height by default since that's
// what is going to be used for unbounded text
let textElementHeight = updatedTextElement.height;
if (container && updatedTextElement.containerId) {
if (isArrowElement(container)) {
const boundTextCoords =
@@ -206,7 +207,6 @@ export const textWysiwyg = ({
maxHeight = getMaxContainerHeight(container);
// autogrow container height if text exceeds
if (!isArrowElement(container) && textElementHeight > maxHeight) {
const diff = Math.min(
textElementHeight - maxHeight,
@@ -276,7 +276,6 @@ export const textWysiwyg = ({
// Make sure text editor height doesn't go beyond viewport
const editorMaxHeight =
(appState.height - viewportY) / appState.zoom.value;
Object.assign(editable.style, {
font: getFontString(updatedTextElement),
// must be defined *after* font ¯\_(ツ)_/¯
@@ -395,11 +394,12 @@ export const textWysiwyg = ({
// first line as well as setting height to "auto"
// doubles the height as soon as user starts typing
if (isBoundToContainer(element) && lines > 1) {
const container = getContainerElement(element);
let height = "auto";
editable.style.height = "0px";
let heightSet = false;
if (lines === 2) {
const container = getContainerElement(element);
const actualLineCount = wrapText(
editable.value,
font,
@@ -416,6 +416,14 @@ export const textWysiwyg = ({
heightSet = true;
}
}
const wrappedText = wrapText(
normalizeText(editable.value),
font,
getMaxContainerWidth(container!),
);
const width = getTextWidth(wrappedText, font);
editable.style.width = `${width}px`;
if (!heightSet) {
editable.style.height = `${editable.scrollHeight}px`;
}
@@ -0,0 +1,21 @@
import React from "react";
import { Footer } from "../../packages/excalidraw/index";
import { EncryptedIcon } from "./EncryptedIcon";
import { ExcalidrawPlusAppLink } from "./ExcalidrawPlusAppLink";
export const AppFooter = React.memo(() => {
return (
<Footer>
<div
style={{
display: "flex",
gap: ".5rem",
alignItems: "center",
}}
>
<ExcalidrawPlusAppLink />
<EncryptedIcon />
</div>
</Footer>
);
});
@@ -0,0 +1,40 @@
import React from "react";
import { PlusPromoIcon } from "../../components/icons";
import { MainMenu } from "../../packages/excalidraw/index";
import { LanguageList } from "./LanguageList";
export const AppMainMenu: React.FC<{
setCollabDialogShown: (toggle: boolean) => any;
isCollaborating: boolean;
}> = React.memo((props) => {
return (
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
<MainMenu.DefaultItems.Export />
<MainMenu.DefaultItems.SaveAsImage />
<MainMenu.DefaultItems.LiveCollaborationTrigger
isCollaborating={props.isCollaborating}
onSelect={() => props.setCollabDialogShown(true)}
/>
<MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
<MainMenu.ItemLink
icon={PlusPromoIcon}
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger"
className="ExcalidrawPlus"
>
Excalidraw+
</MainMenu.ItemLink>
<MainMenu.DefaultItems.Socials />
<MainMenu.Separator />
<MainMenu.DefaultItems.ToggleTheme />
<MainMenu.ItemCustom>
<LanguageList style={{ width: "100%" }} />
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.ChangeCanvasBackground />
</MainMenu>
);
});
@@ -0,0 +1,64 @@
import React from "react";
import { PlusPromoIcon } from "../../components/icons";
import { t } from "../../i18n";
import { WelcomeScreen } from "../../packages/excalidraw/index";
import { isExcalidrawPlusSignedUser } from "../app_constants";
export const AppWelcomeScreen: React.FC<{
setCollabDialogShown: (toggle: boolean) => any;
}> = React.memo((props) => {
let headingContent;
if (isExcalidrawPlusSignedUser) {
headingContent = t("welcomeScreen.app.center_heading_plus")
.split(/(Excalidraw\+)/)
.map((bit, idx) => {
if (bit === "Excalidraw+") {
return (
<a
style={{ pointerEvents: "all" }}
href={`${process.env.REACT_APP_PLUS_APP}?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}
key={idx}
>
Excalidraw+
</a>
);
}
return bit;
});
} else {
headingContent = t("welcomeScreen.app.center_heading");
}
return (
<WelcomeScreen>
<WelcomeScreen.Hints.MenuHint>
{t("welcomeScreen.app.menuHint")}
</WelcomeScreen.Hints.MenuHint>
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>
{headingContent}
</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemLoadScene />
<WelcomeScreen.Center.MenuItemHelp />
<WelcomeScreen.Center.MenuItemLiveCollaborationTrigger
onSelect={() => props.setCollabDialogShown(true)}
/>
{!isExcalidrawPlusSignedUser && (
<WelcomeScreen.Center.MenuItemLink
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
shortcut={null}
icon={PlusPromoIcon}
>
Try Excalidraw Plus!
</WelcomeScreen.Center.MenuItemLink>
)}
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
</WelcomeScreen>
);
});
+10 -108
View File
@@ -1,6 +1,6 @@
import polyfill from "../polyfill";
import LanguageDetector from "i18next-browser-languagedetector";
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { trackEvent } from "../analytics";
import { getDefaultAppState } from "../appState";
import { ErrorDialog } from "../components/ErrorDialog";
@@ -24,10 +24,7 @@ import { t } from "../i18n";
import {
Excalidraw,
defaultLang,
Footer,
MainMenu,
LiveCollaborationTrigger,
WelcomeScreen,
} from "../packages/excalidraw/index";
import {
AppState,
@@ -47,7 +44,6 @@ import {
} from "../utils";
import {
FIREBASE_STORAGE_PREFIXES,
isExcalidrawPlusSignedUser,
STORAGE_KEYS,
SYNC_BROWSER_TABS_TIMEOUT,
} from "./app_constants";
@@ -85,10 +81,9 @@ import { atom, Provider, useAtom } from "jotai";
import { jotaiStore, useAtomWithInitialValue } from "../jotai";
import { reconcileElements } from "./collab/reconciliation";
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
import { EncryptedIcon } from "./components/EncryptedIcon";
import { ExcalidrawPlusAppLink } from "./components/ExcalidrawPlusAppLink";
import { LanguageList } from "./components/LanguageList";
import { PlusPromoIcon } from "../components/icons";
import { AppMainMenu } from "./components/AppMainMenu";
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
import { AppFooter } from "./components/AppFooter";
polyfill();
@@ -604,96 +599,6 @@ const ExcalidrawWrapper = () => {
localStorage.setItem(STORAGE_KEYS.LOCAL_STORAGE_LIBRARY, serializedItems);
};
const renderMenu = () => {
return (
<MainMenu>
<MainMenu.DefaultItems.LoadScene />
<MainMenu.DefaultItems.SaveToActiveFile />
<MainMenu.DefaultItems.Export />
<MainMenu.DefaultItems.SaveAsImage />
<MainMenu.DefaultItems.LiveCollaborationTrigger
isCollaborating={isCollaborating}
onSelect={() => setCollabDialogShown(true)}
/>
<MainMenu.DefaultItems.Help />
<MainMenu.DefaultItems.ClearCanvas />
<MainMenu.Separator />
<MainMenu.ItemLink
icon={PlusPromoIcon}
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=hamburger"
className="ExcalidrawPlus"
>
Excalidraw+
</MainMenu.ItemLink>
<MainMenu.DefaultItems.Socials />
<MainMenu.Separator />
<MainMenu.DefaultItems.ToggleTheme />
<MainMenu.ItemCustom>
<LanguageList style={{ width: "100%" }} />
</MainMenu.ItemCustom>
<MainMenu.DefaultItems.ChangeCanvasBackground />
</MainMenu>
);
};
const welcomeScreenJSX = useMemo(() => {
let headingContent;
if (isExcalidrawPlusSignedUser) {
headingContent = t("welcomeScreen.app.center_heading_plus")
.split(/(Excalidraw\+)/)
.map((bit, idx) => {
if (bit === "Excalidraw+") {
return (
<a
style={{ pointerEvents: "all" }}
href={`${process.env.REACT_APP_PLUS_APP}?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenSignedInUser`}
key={idx}
>
Excalidraw+
</a>
);
}
return bit;
});
} else {
headingContent = t("welcomeScreen.app.center_heading");
}
return (
<WelcomeScreen>
<WelcomeScreen.Hints.MenuHint>
{t("welcomeScreen.app.menuHint")}
</WelcomeScreen.Hints.MenuHint>
<WelcomeScreen.Hints.ToolbarHint />
<WelcomeScreen.Hints.HelpHint />
<WelcomeScreen.Center>
<WelcomeScreen.Center.Logo />
<WelcomeScreen.Center.Heading>
{headingContent}
</WelcomeScreen.Center.Heading>
<WelcomeScreen.Center.Menu>
<WelcomeScreen.Center.MenuItemLoadScene />
<WelcomeScreen.Center.MenuItemHelp />
<WelcomeScreen.Center.MenuItemLiveCollaborationTrigger
onSelect={() => setCollabDialogShown(true)}
/>
{!isExcalidrawPlusSignedUser && (
<WelcomeScreen.Center.MenuItemLink
href="https://plus.excalidraw.com/plus?utm_source=excalidraw&utm_medium=app&utm_content=welcomeScreenGuest"
shortcut={null}
icon={PlusPromoIcon}
>
Try Excalidraw Plus!
</WelcomeScreen.Center.MenuItemLink>
)}
</WelcomeScreen.Center.Menu>
</WelcomeScreen.Center>
</WelcomeScreen>
);
}, [setCollabDialogShown]);
return (
<div
style={{ height: "100%" }}
@@ -750,15 +655,12 @@ const ExcalidrawWrapper = () => {
);
}}
>
{renderMenu()}
<Footer>
<div style={{ display: "flex", gap: ".5rem", alignItems: "center" }}>
<ExcalidrawPlusAppLink />
<EncryptedIcon />
</div>
</Footer>
{welcomeScreenJSX}
<AppMainMenu
setCollabDialogShown={setCollabDialogShown}
isCollaborating={isCollaborating}
/>
<AppWelcomeScreen setCollabDialogShown={setCollabDialogShown} />
<AppFooter />
</Excalidraw>
{excalidrawAPI && <Collab excalidrawAPI={excalidrawAPI} />}
{errorMessage && (
+5
View File
@@ -15,8 +15,13 @@ Please add the latest change on the top under the correct section.
### Features
- Welcome screen no longer renders by default, and you need to render it yourself. `UIOptions.welcomeScreen` option is now deprecated. [#6117](https://github.com/excalidraw/excalidraw/pull/6117)
- `MainMenu`, `MainMenu.Item`, and `MainMenu.ItemLink` components now all support `onSelect(event: Event): void` callback. If you call `event.preventDefault()`, it will prevent the menu from closing when an item is selected (clicked on). [#6152](https://github.com/excalidraw/excalidraw/pull/6152)
### Fixes
- declare css variable for font in excalidraw so its available in host [#6160](https://github.com/excalidraw/excalidraw/pull/6160)
## 0.14.1 (2023-01-16)
### Fixes
-1
View File
@@ -53,7 +53,6 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
...DEFAULT_UI_OPTIONS.canvasActions,
...canvasActions,
},
welcomeScreen: props.UIOptions?.welcomeScreen ?? true,
};
if (canvasActions?.export) {
@@ -5,7 +5,7 @@ exports[`Test Linear Elements Test bound text element should match styles for te
class="excalidraw-wysiwyg"
data-type="wysiwyg"
dir="auto"
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 0px; height: 0px; left: 40px; top: 20px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -20px; font: Emoji 20px 20px; line-height: 0px; font-family: Virgil, Segoe UI Emoji;"
style="position: absolute; display: inline-block; min-height: 1em; margin: 0px; padding: 0px; border: 0px; outline: 0; resize: none; background: transparent; overflow: hidden; z-index: var(--zIndex-wysiwyg); word-break: break-word; white-space: pre-wrap; overflow-wrap: break-word; box-sizing: content-box; width: 1px; height: 0px; left: 39.5px; top: 20px; transform: translate(0px, 0px) scale(1) rotate(0deg); text-align: center; vertical-align: middle; color: rgb(0, 0, 0); opacity: 1; filter: var(--theme-filter); max-height: -20px; font: Emoji 20px 20px; line-height: 0px; font-family: Virgil, Segoe UI Emoji;"
tabindex="0"
wrap="off"
/>
@@ -312,7 +312,7 @@ Object {
"versionNonce": 0,
"verticalAlign": "middle",
"width": 100,
"x": 0,
"x": -0.5,
"y": 0,
}
`;
+2 -2
View File
@@ -1036,7 +1036,7 @@ describe("Test Linear Elements", () => {
expect(getBoundTextElementPosition(container, textElement))
.toMatchInlineSnapshot(`
Object {
"x": 387,
"x": 386.5,
"y": 70,
}
`);
@@ -1095,7 +1095,7 @@ describe("Test Linear Elements", () => {
expect(getBoundTextElementPosition(container, textElement))
.toMatchInlineSnapshot(`
Object {
"x": 190,
"x": 189.5,
"y": 20,
}
`);
+3 -32
View File
@@ -385,15 +385,16 @@ type CanvasActions = Partial<{
type UIOptions = Partial<{
dockedSidebarBreakpoint: number;
welcomeScreen: boolean;
canvasActions: CanvasActions;
/** @deprecated does nothing. Will be removed in 0.15 */
welcomeScreen?: boolean;
}>;
export type AppProps = Merge<
ExcalidrawProps,
{
UIOptions: Merge<
MarkRequired<UIOptions, "welcomeScreen">,
UIOptions,
{
canvasActions: Required<CanvasActions> & { export: ExportOpts };
}
@@ -523,33 +524,3 @@ export type Device = Readonly<{
isTouchScreen: boolean;
canDeviceFitSidebar: boolean;
}>;
export type UIChildrenComponents = {
[k in "FooterCenter" | "Menu" | "WelcomeScreen"]?: React.ReactElement<
{ children?: React.ReactNode },
React.JSXElementConstructor<any>
>;
};
export type UIWelcomeScreenComponents = {
[k in
| "Center"
| "MenuHint"
| "ToolbarHint"
| "HelpHint"]?: React.ReactElement<
{ children?: React.ReactNode },
React.JSXElementConstructor<any>
>;
};
export type UIWelcomeScreenCenterComponents = {
[k in
| "Logo"
| "Heading"
| "Menu"
| "MenuItemLoadScene"
| "MenuItemHelp"]?: React.ReactElement<
{ children?: React.ReactNode },
React.JSXElementConstructor<any>
>;
};
-43
View File
@@ -15,7 +15,6 @@ import { FontFamilyValues, FontString } from "./element/types";
import { AppState, DataURL, LastActiveTool, Zoom } from "./types";
import { unstable_batchedUpdates } from "react-dom";
import { SHAPES } from "./shapes";
import React from "react";
import { isEraserActive, isHandToolActive } from "./appState";
let mockDateTime: string | null = null;
@@ -690,48 +689,6 @@ export const queryFocusableElements = (container: HTMLElement | null) => {
: [];
};
/**
* Partitions React children into named components and the rest of children.
*
* Returns known children as a dictionary of react children keyed by their
* displayName, and the rest children as an array.
*
* NOTE all named react components are included in the dictionary, irrespective
* of the supplied type parameter. This means you may be throwing away
* children that you aren't expecting, but should nonetheless be rendered.
* To guard against this (provided you care about the rest children at all),
* supply a second parameter with an object with keys of the expected children.
*/
export const getReactChildren = <
KnownChildren extends {
[k in string]?: React.ReactNode;
},
>(
children: React.ReactNode,
expectedComponents?: Record<keyof KnownChildren, any>,
) => {
const restChildren: React.ReactNode[] = [];
const knownChildren = React.Children.toArray(children).reduce(
(acc, child) => {
if (
React.isValidElement(child) &&
(!expectedComponents ||
((child.type as any).displayName as string) in expectedComponents)
) {
// @ts-ignore
acc[child.type.displayName] = child;
} else {
restChildren.push(child);
}
return acc;
},
{} as Partial<KnownChildren>,
);
return [knownChildren, restChildren] as const;
};
export const isShallowEqual = <T extends Record<string, any>>(
objA: T,
objB: T,
+60 -152
View File
@@ -2280,6 +2280,11 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@socket.io/component-emitter@~3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
"@surma/rollup-plugin-off-main-thread@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
@@ -3138,11 +3143,6 @@ adjust-sourcemap-loader@^4.0.0:
loader-utils "^2.0.0"
regex-parser "^2.2.11"
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
integrity sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -3377,11 +3377,6 @@ array.prototype.tosorted@^1.1.1:
es-shim-unscopables "^1.0.0"
get-intrinsic "^1.1.3"
arraybuffer.slice@~0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
asap@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@@ -3402,11 +3397,6 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
async-limiter@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
async@^2.6.4:
version "2.6.4"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
@@ -3597,11 +3587,6 @@ babel-preset-react-app@^10.0.1:
babel-plugin-macros "^3.1.0"
babel-plugin-transform-react-remove-prop-types "^0.4.24"
backo2@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
integrity sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -3612,11 +3597,6 @@ base64-arraybuffer-es6@^0.7.0:
resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.7.0.tgz#dbe1e6c87b1bf1ca2875904461a7de40f21abc86"
integrity sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==
base64-arraybuffer@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
integrity sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==
basic-auth@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
@@ -3649,11 +3629,6 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
blob@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
@@ -4049,21 +4024,6 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
component-bind@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
integrity sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==
component-emitter@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
component-inherit@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
integrity sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==
compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
@@ -4446,7 +4406,7 @@ debug@2.6.9, debug@^2.6.0, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4:
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -4460,13 +4420,6 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
decimal.js@^10.2.1:
version "10.4.2"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e"
@@ -4807,33 +4760,21 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
engine.io-client@~3.4.0:
version "3.4.4"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.4.tgz#77d8003f502b0782dd792b073a4d2cf7ca5ab967"
integrity sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==
engine.io-client@~6.2.3:
version "6.2.3"
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.2.3.tgz#a8cbdab003162529db85e9de31575097f6d29458"
integrity sha512-aXPtgF1JS3RuuKcpSrBtimSjYvrbhKW9froICH4s0F3XQWLxsKNxqzG39nnvQZQnva4CMvUK63T7shevxRyYHw==
dependencies:
component-emitter "~1.3.0"
component-inherit "0.0.3"
debug "~3.1.0"
engine.io-parser "~2.2.0"
has-cors "1.1.0"
indexof "0.0.1"
parseqs "0.0.6"
parseuri "0.0.6"
ws "~6.1.0"
xmlhttprequest-ssl "~1.5.4"
yeast "0.1.2"
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
engine.io-parser "~5.0.3"
ws "~8.2.3"
xmlhttprequest-ssl "~2.0.0"
engine.io-parser@~2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7"
integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==
dependencies:
after "0.8.2"
arraybuffer.slice "~0.0.7"
base64-arraybuffer "0.1.4"
blob "0.0.5"
has-binary2 "~1.0.2"
engine.io-parser@~5.0.3:
version "5.0.6"
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45"
integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==
enhanced-resolve@^5.10.0:
version "5.10.0"
@@ -5860,18 +5801,6 @@ has-bigints@^1.0.1, has-bigints@^1.0.2:
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-binary2@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
dependencies:
isarray "2.0.1"
has-cors@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
integrity sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -6191,11 +6120,6 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
integrity sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -6463,11 +6387,6 @@ is-wsl@^2.2.0:
dependencies:
is-docker "^2.0.0"
isarray@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
integrity sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
@@ -7988,16 +7907,6 @@ parse5@6.0.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parseqs@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
parseuri@0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
parseurl@~1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
@@ -9646,31 +9555,23 @@ sliced@^1.0.1:
resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41"
integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==
socket.io-client@2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.1.tgz#91a4038ef4d03c19967bb3c646fec6e0eaa78cff"
integrity sha512-YXmXn3pA8abPOY//JtYxou95Ihvzmg8U6kQyolArkIyLd0pgVhrfor/iMsox8cn07WCOOvvuJ6XKegzIucPutQ==
socket.io-client@4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.5.4.tgz#d3cde8a06a6250041ba7390f08d2468ccebc5ac9"
integrity sha512-ZpKteoA06RzkD32IbqILZ+Cnst4xewU7ZYK12aS1mzHftFFjpoMz69IuhP/nL25pJfao/amoPI527KnuhFm01g==
dependencies:
backo2 "1.0.2"
component-bind "1.0.0"
component-emitter "~1.3.0"
debug "~3.1.0"
engine.io-client "~3.4.0"
has-binary2 "~1.0.2"
indexof "0.0.1"
parseqs "0.0.6"
parseuri "0.0.6"
socket.io-parser "~3.3.0"
to-array "0.1.4"
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.2"
engine.io-client "~6.2.3"
socket.io-parser "~4.2.1"
socket.io-parser@~3.3.0:
version "3.3.3"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.3.tgz#3a8b84823eba87f3f7624e64a8aaab6d6318a72f"
integrity sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg==
socket.io-parser@~4.2.1:
version "4.2.2"
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
dependencies:
component-emitter "~1.3.0"
debug "~3.1.0"
isarray "2.0.1"
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
sockjs@^0.3.24:
version "0.3.24"
@@ -10160,11 +10061,6 @@ tmpl@1.0.5:
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
to-array@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
integrity sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -10238,6 +10134,13 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
tunnel-rat@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/tunnel-rat/-/tunnel-rat-0.1.0.tgz#62cfbaf1b24cabac9318fe45ef26d70dc40e86fe"
integrity sha512-/FKZLBXCoKhA7Wz+dsqitrItaLXYmT2bkZXod+1UuR4JqHtdb54yHvHhmMgLg+eyH1Od/CCnhA2VQQ2A/54Tcw==
dependencies:
zustand "^4.1.0"
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
@@ -10406,6 +10309,11 @@ url-parse@^1.5.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -10941,12 +10849,10 @@ ws@^8.4.2:
resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
ws@~6.1.0:
version "6.1.4"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
dependencies:
async-limiter "~1.0.0"
ws@~8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
xml-name-validator@^3.0.0:
version "3.0.0"
@@ -10958,10 +10864,10 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
integrity sha512-/bFPLUgJrfGUL10AIv4Y7/CUt6so9CLtB/oFxQSHseSDNNCdC6vwwKEqwLN6wNPBg9YWXAiMu8jkf6RPRS/75Q==
xmlhttprequest-ssl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
xmlhttprequest@1.8.0:
version "1.8.0"
@@ -11006,12 +10912,14 @@ yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yeast@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
integrity sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand@^4.1.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.2.tgz#bb121fcad84c5a569e94bd1a2695e1a93ba85d39"
integrity sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw==
dependencies:
use-sync-external-store "1.2.0"