fix: make tunnels work in multi-instance scenarios (#6178)

* fix: make tunnels work in multi-instance scenarios

* factor tunnels out

* use tunnel-rat fork until upsteam updated
This commit is contained in:
David Luzar 2023-02-01 06:16:17 +01:00 committed by GitHub
parent e6de1fe4a4
commit 7562d9b533
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 76 additions and 32 deletions

View File

@ -19,6 +19,7 @@
] ]
}, },
"dependencies": { "dependencies": {
"@dwelle/tunnel-rat": "0.1.1",
"@sentry/browser": "6.2.5", "@sentry/browser": "6.2.5",
"@sentry/integrations": "6.2.5", "@sentry/integrations": "6.2.5",
"@testing-library/jest-dom": "5.16.2", "@testing-library/jest-dom": "5.16.2",

View File

@ -40,17 +40,12 @@ import { actionToggleStats } from "../actions/actionToggleStats";
import Footer from "./footer/Footer"; import Footer from "./footer/Footer";
import { hostSidebarCountersAtom } from "./Sidebar/Sidebar"; import { hostSidebarCountersAtom } from "./Sidebar/Sidebar";
import { jotaiScope } from "../jotai"; import { jotaiScope } from "../jotai";
import { useAtom } from "jotai"; import { Provider, useAtom } from "jotai";
import MainMenu from "./main-menu/MainMenu"; import MainMenu from "./main-menu/MainMenu";
import { ActiveConfirmDialog } from "./ActiveConfirmDialog"; import { ActiveConfirmDialog } from "./ActiveConfirmDialog";
import { HandButton } from "./HandButton"; import { HandButton } from "./HandButton";
import { isHandToolActive } from "../appState"; import { isHandToolActive } from "../appState";
import { import { TunnelsContext, useInitializeTunnels } from "./context/tunnels";
mainMenuTunnel,
welcomeScreenMenuHintTunnel,
welcomeScreenToolbarHintTunnel,
welcomeScreenCenterTunnel,
} from "./tunnels";
interface LayerUIProps { interface LayerUIProps {
actionManager: ActionManager; actionManager: ActionManager;
@ -130,6 +125,8 @@ const LayerUI = ({
}: LayerUIProps) => { }: LayerUIProps) => {
const device = useDevice(); const device = useDevice();
const tunnels = useInitializeTunnels();
const renderJSONExportDialog = () => { const renderJSONExportDialog = () => {
if (!UIOptions.canvasActions.export) { if (!UIOptions.canvasActions.export) {
return null; return null;
@ -201,8 +198,8 @@ const LayerUI = ({
<div style={{ position: "relative" }}> <div style={{ position: "relative" }}>
{/* wrapping to Fragment stops React from occasionally complaining {/* wrapping to Fragment stops React from occasionally complaining
about identical Keys */} about identical Keys */}
<mainMenuTunnel.Out /> <tunnels.mainMenuTunnel.Out />
{renderWelcomeScreen && <welcomeScreenMenuHintTunnel.Out />} {renderWelcomeScreen && <tunnels.welcomeScreenMenuHintTunnel.Out />}
</div> </div>
); );
@ -254,7 +251,7 @@ const LayerUI = ({
{(heading: React.ReactNode) => ( {(heading: React.ReactNode) => (
<div style={{ position: "relative" }}> <div style={{ position: "relative" }}>
{renderWelcomeScreen && ( {renderWelcomeScreen && (
<welcomeScreenToolbarHintTunnel.Out /> <tunnels.welcomeScreenToolbarHintTunnel.Out />
)} )}
<Stack.Col gap={4} align="start"> <Stack.Col gap={4} align="start">
<Stack.Row <Stack.Row
@ -354,7 +351,7 @@ const LayerUI = ({
const [hostSidebarCounters] = useAtom(hostSidebarCountersAtom, jotaiScope); const [hostSidebarCounters] = useAtom(hostSidebarCountersAtom, jotaiScope);
return ( const layerUIJSX = (
<> <>
{/* ------------------------- tunneled UI ---------------------------- */} {/* ------------------------- tunneled UI ---------------------------- */}
{/* make sure we render host app components first so that we can detect {/* make sure we render host app components first so that we can detect
@ -434,7 +431,7 @@ const LayerUI = ({
: {} : {}
} }
> >
{renderWelcomeScreen && <welcomeScreenCenterTunnel.Out />} {renderWelcomeScreen && <tunnels.welcomeScreenCenterTunnel.Out />}
{renderFixedSideContainer()} {renderFixedSideContainer()}
<Footer <Footer
appState={appState} appState={appState}
@ -471,6 +468,14 @@ const LayerUI = ({
)} )}
</> </>
); );
return (
<Provider scope={tunnels.jotaiScope}>
<TunnelsContext.Provider value={tunnels}>
{layerUIJSX}
</TunnelsContext.Provider>
</Provider>
);
}; };
const stripIrrelevantAppStateProps = ( const stripIrrelevantAppStateProps = (

View File

@ -19,7 +19,7 @@ import { Stats } from "./Stats";
import { actionToggleStats } from "../actions"; import { actionToggleStats } from "../actions";
import { HandButton } from "./HandButton"; import { HandButton } from "./HandButton";
import { isHandToolActive } from "../appState"; import { isHandToolActive } from "../appState";
import { mainMenuTunnel, welcomeScreenCenterTunnel } from "./tunnels"; import { useTunnels } from "./context/tunnels";
type MobileMenuProps = { type MobileMenuProps = {
appState: AppState; appState: AppState;
@ -58,6 +58,7 @@ export const MobileMenu = ({
renderSidebars, renderSidebars,
device, device,
}: MobileMenuProps) => { }: MobileMenuProps) => {
const { welcomeScreenCenterTunnel, mainMenuTunnel } = useTunnels();
const renderToolbar = () => { const renderToolbar = () => {
return ( return (
<FixedSideContainer side="top" className="App-top-bar"> <FixedSideContainer side="top" className="App-top-bar">

View File

@ -0,0 +1,32 @@
import React from "react";
import tunnel from "@dwelle/tunnel-rat";
type Tunnel = ReturnType<typeof tunnel>;
type TunnelsContextValue = {
mainMenuTunnel: Tunnel;
welcomeScreenMenuHintTunnel: Tunnel;
welcomeScreenToolbarHintTunnel: Tunnel;
welcomeScreenHelpHintTunnel: Tunnel;
welcomeScreenCenterTunnel: Tunnel;
footerCenterTunnel: Tunnel;
jotaiScope: symbol;
};
export const TunnelsContext = React.createContext<TunnelsContextValue>(null!);
export const useTunnels = () => React.useContext(TunnelsContext);
export const useInitializeTunnels = () => {
return React.useMemo((): TunnelsContextValue => {
return {
mainMenuTunnel: tunnel(),
welcomeScreenMenuHintTunnel: tunnel(),
welcomeScreenToolbarHintTunnel: tunnel(),
welcomeScreenHelpHintTunnel: tunnel(),
welcomeScreenCenterTunnel: tunnel(),
footerCenterTunnel: tunnel(),
jotaiScope: Symbol(),
};
}, []);
};

View File

@ -9,10 +9,10 @@ import {
ZoomActions, ZoomActions,
} from "../Actions"; } from "../Actions";
import { useDevice } from "../App"; import { useDevice } from "../App";
import { useTunnels } from "../context/tunnels";
import { HelpButton } from "../HelpButton"; import { HelpButton } from "../HelpButton";
import { Section } from "../Section"; import { Section } from "../Section";
import Stack from "../Stack"; import Stack from "../Stack";
import { footerCenterTunnel, welcomeScreenHelpHintTunnel } from "../tunnels";
const Footer = ({ const Footer = ({
appState, appState,
@ -25,6 +25,8 @@ const Footer = ({
showExitZenModeBtn: boolean; showExitZenModeBtn: boolean;
renderWelcomeScreen: boolean; renderWelcomeScreen: boolean;
}) => { }) => {
const { footerCenterTunnel, welcomeScreenHelpHintTunnel } = useTunnels();
const device = useDevice(); const device = useDevice();
const showFinalize = const showFinalize =
!appState.viewModeEnabled && appState.multiElement && device.isTouchScreen; !appState.viewModeEnabled && appState.multiElement && device.isTouchScreen;

View File

@ -1,9 +1,10 @@
import clsx from "clsx"; import clsx from "clsx";
import { useExcalidrawAppState } from "../App"; import { useExcalidrawAppState } from "../App";
import { footerCenterTunnel } from "../tunnels"; import { useTunnels } from "../context/tunnels";
import "./FooterCenter.scss"; import "./FooterCenter.scss";
const FooterCenter = ({ children }: { children?: React.ReactNode }) => { const FooterCenter = ({ children }: { children?: React.ReactNode }) => {
const { footerCenterTunnel } = useTunnels();
const appState = useExcalidrawAppState(); const appState = useExcalidrawAppState();
return ( return (
<footerCenterTunnel.In> <footerCenterTunnel.In>

View File

@ -1,5 +1,6 @@
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import React, { useLayoutEffect } from "react"; import React, { useLayoutEffect } from "react";
import { useTunnels } from "../context/tunnels";
export const withInternalFallback = <P,>( export const withInternalFallback = <P,>(
componentName: string, componentName: string,
@ -17,7 +18,8 @@ export const withInternalFallback = <P,>(
__fallback?: boolean; __fallback?: boolean;
} }
> = (props) => { > = (props) => {
const [counter, setCounter] = useAtom(counterAtom); const { jotaiScope } = useTunnels();
const [counter, setCounter] = useAtom(counterAtom, jotaiScope);
useLayoutEffect(() => { useLayoutEffect(() => {
setCounter((counter) => counter + 1); setCounter((counter) => counter + 1);

View File

@ -13,7 +13,7 @@ import { t } from "../../i18n";
import { HamburgerMenuIcon } from "../icons"; import { HamburgerMenuIcon } from "../icons";
import { withInternalFallback } from "../hoc/withInternalFallback"; import { withInternalFallback } from "../hoc/withInternalFallback";
import { composeEventHandlers } from "../../utils"; import { composeEventHandlers } from "../../utils";
import { mainMenuTunnel } from "../tunnels"; import { useTunnels } from "../context/tunnels";
const MainMenu = Object.assign( const MainMenu = Object.assign(
withInternalFallback( withInternalFallback(
@ -28,6 +28,7 @@ const MainMenu = Object.assign(
*/ */
onSelect?: (event: Event) => void; onSelect?: (event: Event) => void;
}) => { }) => {
const { mainMenuTunnel } = useTunnels();
const device = useDevice(); const device = useDevice();
const appState = useExcalidrawAppState(); const appState = useExcalidrawAppState();
const setAppState = useExcalidrawSetAppState(); const setAppState = useExcalidrawSetAppState();

View File

@ -1,8 +0,0 @@
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();

View File

@ -6,8 +6,8 @@ import {
useExcalidrawActionManager, useExcalidrawActionManager,
useExcalidrawAppState, useExcalidrawAppState,
} from "../App"; } from "../App";
import { useTunnels } from "../context/tunnels";
import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons"; import { ExcalLogo, HelpIcon, LoadIcon, usersIcon } from "../icons";
import { welcomeScreenCenterTunnel } from "../tunnels";
const WelcomeScreenMenuItemContent = ({ const WelcomeScreenMenuItemContent = ({
icon, icon,
@ -89,6 +89,7 @@ const WelcomeScreenMenuItemLink = ({
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink"; WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
const Center = ({ children }: { children?: React.ReactNode }) => { const Center = ({ children }: { children?: React.ReactNode }) => {
const { welcomeScreenCenterTunnel } = useTunnels();
return ( return (
<welcomeScreenCenterTunnel.In> <welcomeScreenCenterTunnel.In>
<div className="welcome-screen-center"> <div className="welcome-screen-center">

View File

@ -1,16 +1,13 @@
import { t } from "../../i18n"; import { t } from "../../i18n";
import { useTunnels } from "../context/tunnels";
import { import {
WelcomeScreenHelpArrow, WelcomeScreenHelpArrow,
WelcomeScreenMenuArrow, WelcomeScreenMenuArrow,
WelcomeScreenTopToolbarArrow, WelcomeScreenTopToolbarArrow,
} from "../icons"; } from "../icons";
import {
welcomeScreenMenuHintTunnel,
welcomeScreenToolbarHintTunnel,
welcomeScreenHelpHintTunnel,
} from "../tunnels";
const MenuHint = ({ children }: { children?: React.ReactNode }) => { const MenuHint = ({ children }: { children?: React.ReactNode }) => {
const { welcomeScreenMenuHintTunnel } = useTunnels();
return ( return (
<welcomeScreenMenuHintTunnel.In> <welcomeScreenMenuHintTunnel.In>
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu"> <div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
@ -25,6 +22,7 @@ const MenuHint = ({ children }: { children?: React.ReactNode }) => {
MenuHint.displayName = "MenuHint"; MenuHint.displayName = "MenuHint";
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => { const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
const { welcomeScreenToolbarHintTunnel } = useTunnels();
return ( return (
<welcomeScreenToolbarHintTunnel.In> <welcomeScreenToolbarHintTunnel.In>
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar"> <div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
@ -39,6 +37,7 @@ const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
ToolbarHint.displayName = "ToolbarHint"; ToolbarHint.displayName = "ToolbarHint";
const HelpHint = ({ children }: { children?: React.ReactNode }) => { const HelpHint = ({ children }: { children?: React.ReactNode }) => {
const { welcomeScreenHelpHintTunnel } = useTunnels();
return ( return (
<welcomeScreenHelpHintTunnel.In> <welcomeScreenHelpHintTunnel.In>
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help"> <div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">

View File

@ -1437,6 +1437,13 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
"@dwelle/tunnel-rat@0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@dwelle/tunnel-rat/-/tunnel-rat-0.1.1.tgz#0a0b235f8fc22ff1cf47ed102f4cc612eb51bc71"
integrity sha512-jb5/ZsT/af1J7tnbBXp7KO1xEyw61lWSDqJ+Bqdc6JlL3vbAvsifNhe+/mRFs6aSBCRaDqp5f2pJDHtA3MUZLw==
dependencies:
zustand "^4.3.2"
"@eslint/eslintrc@^0.4.3": "@eslint/eslintrc@^0.4.3":
version "0.4.3" version "0.4.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
@ -11028,7 +11035,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand@^4.1.0: zustand@^4.1.0, zustand@^4.3.2:
version "4.3.2" version "4.3.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.2.tgz#bb121fcad84c5a569e94bd1a2695e1a93ba85d39" resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.2.tgz#bb121fcad84c5a569e94bd1a2695e1a93ba85d39"
integrity sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw== integrity sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw==