From 42331701c620c67551f0b6e34ef6752cbee0b5b5 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Mon, 27 Jun 2022 15:12:00 +0000 Subject: [PATCH 1/3] Update 4 files --- sandpack-client/src/iframe-protocol.ts | 3 +- sandpack-react/src/Playground.stories.tsx | 17 +- .../src/contexts/sandpackContext.test.tsx | 212 ++++++++++++++++-- .../src/contexts/sandpackContext.tsx | 64 +++--- 4 files changed, 246 insertions(+), 50 deletions(-) diff --git a/sandpack-client/src/iframe-protocol.ts b/sandpack-client/src/iframe-protocol.ts index cbbfaa3aa..9358bccd4 100644 --- a/sandpack-client/src/iframe-protocol.ts +++ b/sandpack-client/src/iframe-protocol.ts @@ -13,7 +13,7 @@ export class IFrameProtocol { private globalListenersCount = 0; // React to messages from the iframe owned by this instance - private channelListeners: Record = {}; + public channelListeners: Record = {}; private channelListenersCount = 0; // Random number to identify this instance of the client when messages are coming from multiple iframes @@ -102,6 +102,7 @@ export class IFrameProtocol { const listenerId = this.channelListenersCount; this.channelListeners[listenerId] = listener; this.channelListenersCount++; + return (): void => { delete this.channelListeners[listenerId]; }; diff --git a/sandpack-react/src/Playground.stories.tsx b/sandpack-react/src/Playground.stories.tsx index 256efc46c..945fb73e0 100644 --- a/sandpack-react/src/Playground.stories.tsx +++ b/sandpack-react/src/Playground.stories.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as themes from "@codesandbox/sandpack-themes"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import type { CodeEditorProps } from "./components/CodeEditor"; import { SANDBOX_TEMPLATES } from "./templates"; @@ -12,11 +12,24 @@ import { SandpackLayout, SandpackFileExplorer, } from "./"; +import { useSandpack } from "./hooks"; export default { title: "Intro/Playground", }; +const Listerner = () => { + const { listen } = useSandpack(); + + useEffect(() => { + const foo = listen(console.log); + + return () => foo(); + }, [listen]); + + return null; +}; + export const Main = (): JSX.Element => { const [config, setConfig] = useState({ Components: { Preview: true, Editor: true, FileExplorer: true }, @@ -142,6 +155,8 @@ export const Main = (): JSX.Element => { showRefreshButton={config.Options?.showRefreshButton} /> )} + + diff --git a/sandpack-react/src/contexts/sandpackContext.test.tsx b/sandpack-react/src/contexts/sandpackContext.test.tsx index 591f6ef3b..55c25c638 100644 --- a/sandpack-react/src/contexts/sandpackContext.test.tsx +++ b/sandpack-react/src/contexts/sandpackContext.test.tsx @@ -1,15 +1,28 @@ +/** + * @jest-environment jsdom + */ import React from "react"; import { create } from "react-test-renderer"; -import { REACT_TEMPLATE } from ".."; +import * as sandpackClient from "@codesandbox/sandpack-client"; -import { SandpackProvider } from "./sandpackContext"; +import { REACT_TEMPLATE, SandpackState, SandpackProviderState } from ".."; + +import { SandpackProvider, SandpackProviderClass } from "./sandpackContext"; + +const createContext = (): SandpackProviderClass => { + const root = create(); + const instance = root.getInstance(); + + instance.runSandpack(); + + return instance; +}; describe(SandpackProvider, () => { describe("updateFile", () => { it("adds a file", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); instance.addFile({ "new-file.js": "new-content" }); @@ -17,8 +30,7 @@ describe(SandpackProvider, () => { }); it("deletes a file", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); instance.deleteFile("/App.js"); @@ -32,8 +44,7 @@ describe(SandpackProvider, () => { }); it("updates a file", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); expect(instance.state.files["/App.js"]).toEqual({ code: `export default function App() { @@ -48,8 +59,7 @@ describe(SandpackProvider, () => { }); it("updates multiples files", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); instance.updateFile({ "/App.js": "Foo", "/index.js": "Baz" }); @@ -58,8 +68,7 @@ describe(SandpackProvider, () => { }); it("updates multiples files in a row", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); instance.updateFile("/App.js", "Foo"); instance.updateFile("/index.js", "Baz"); @@ -71,15 +80,13 @@ describe(SandpackProvider, () => { describe("editorState", () => { it("should return the same initial state", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); expect(instance.state.editorState).toBe("pristine"); }); it("should return a dirty value after updating a file", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); expect(instance.state.editorState).toBe("pristine"); @@ -88,8 +95,7 @@ describe(SandpackProvider, () => { }); it("should return a pristine value after reseting files", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); expect(instance.state.editorState).toBe("pristine"); @@ -101,8 +107,7 @@ describe(SandpackProvider, () => { }); it("should return a pristine value after reverting a change", () => { - const root = create(); - const instance = root.getInstance(); + const instance = createContext(); expect(instance.state.editorState).toBe("pristine"); instance.updateFile("/App.js", "Foo"); @@ -113,4 +118,171 @@ describe(SandpackProvider, () => { expect(instance.state.editorState).toBe("pristine"); }); }); + + describe("listeners", () => { + it("sets a listener, but the client hasn't been created yet - no global listener", () => { + const instance = createContext(); + + // Act: Add listener + const mock = jest.fn(); + instance.addListener(mock, "client-id"); + + // Act: Create client + instance.registerBundler(document.createElement("iframe"), "client-id"); + + // Expect: one pending unsubscribe function + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(1); + + // Expect: no global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(0); + + // Expect: one client + expect(Object.keys(instance.clients)).toEqual(["client-id"]); + + /** + * TODO: figure out how to mock SandpackClient and invoke the listener func + */ + // expect(mock).toHaveBeenCalled(); + }); + + it("sets a listener, but the client hasn't been created yet - global listener", () => { + const instance = createContext(); + + // Act: Add listener + const mock = jest.fn(); + instance.addListener(mock /* , no client-id */); + + // Act: Create client + instance.registerBundler(document.createElement("iframe"), "client-id"); + + // Expect: one pending unsubscribe function + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(1); + + // Expect: no global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(1); + + // Expect: one listener in the client + expect( + Object.keys( + instance.clients["client-id"].iframeProtocol.globalListeners + ).length + ).toBe(1); + }); + + it("set a listener, but the client has already been created - no global listener", () => { + const instance = createContext(); + + // Act: Create client + instance.registerBundler(document.createElement("iframe"), "client-id"); + + // Expect: no pending unsubscribe function + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(0); + + // Expect: no global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(0); + + // Act: Add listener + const mock = jest.fn(); + instance.addListener(mock, "client-id"); + + // Expect: no pending unsubscribe function + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(0); + + // Expect: no global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(0); + + // Expect: one listener in the client + expect( + Object.keys( + instance.clients["client-id"].iframeProtocol.channelListeners + ).length + ).toBe(1); + }); + + it("set a listener, but the client has already been created - global listener", () => { + const instance = createContext(); + + // Act: Create client + instance.registerBundler(document.createElement("iframe"), "client-id"); + + // Expect: no pending unsubscribe function + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(0); + + // Expect: no global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(0); + + // Act: Add listener + const mock = jest.fn(); + instance.addListener(mock /* , no client-id */); + + // Expect: no pending unsubscribe function, because it's a global + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(0); + + // Expect: one global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(1); + + // Expect: one listener in the client + expect( + Object.keys( + instance.clients["client-id"].iframeProtocol.channelListeners + ).length + ).toBe(1); + }); + + it("sets a new listener and then one more client has been created", () => { + const instance = createContext(); + + // Act: Add listener + const mock = jest.fn(); + instance.addListener(mock, "client-id"); + + // Act: Create client + instance.registerBundler(document.createElement("iframe"), "client-id"); + + // Expect: one pending unsubscribe function + expect( + Object.keys(instance.unsubscribeClientListeners["client-id"]).length + ).toBe(1); + + // Expect: no global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(0); + + // Expect: one listener in the client + expect( + Object.keys( + instance.clients["client-id"].iframeProtocol.channelListeners + ).length + ).toBe(1); + + // Act: Add one more listener + const anotherMock = jest.fn(); + instance.addListener(anotherMock /* , no client-id */); + + console.log( + instance.clients["client-id"].iframeProtocol.channelListenersCount + ); + + // Expect: one global listener + expect(Object.keys(instance.queuedListeners.global).length).toBe(1); + + // Expect: two listener in the client + expect( + Object.keys( + instance.clients["client-id"].iframeProtocol.channelListeners + ).length + ).toBe(2); + }); + }); }); diff --git a/sandpack-react/src/contexts/sandpackContext.tsx b/sandpack-react/src/contexts/sandpackContext.tsx index 3f5934e22..146418337 100644 --- a/sandpack-react/src/contexts/sandpackContext.tsx +++ b/sandpack-react/src/contexts/sandpackContext.tsx @@ -1,7 +1,6 @@ import { ClasserProvider } from "@code-hike/classer"; import type { ListenerFunction, - SandpackBundlerFiles, SandpackMessage, UnsubscribeFunction, ReactDevToolsMode, @@ -40,7 +39,7 @@ const BUNDLER_TIMEOUT = 30000; // 30 seconds timeout for the bundler to respond. * @category Provider * @hidden */ -class SandpackProviderClass extends React.PureComponent< +export class SandpackProviderClass extends React.PureComponent< SandpackProviderProps, SandpackProviderState > { @@ -55,7 +54,7 @@ class SandpackProviderClass extends React.PureComponent< intersectionObserver?: IntersectionObserver; queuedListeners: Record>; - unsubscribeQueuedListeners: Record< + unsubscribeClientListeners: Record< string, Record >; @@ -98,7 +97,7 @@ class SandpackProviderClass extends React.PureComponent< /** * Global list of unsubscribe function for the listeners */ - this.unsubscribeQueuedListeners = {}; + this.unsubscribeClientListeners = {}; this.preregisteredIframes = {}; this.clients = {}; @@ -369,6 +368,9 @@ class SandpackProviderClass extends React.PureComponent< }, BUNDLER_TIMEOUT); } + this.unsubscribeClientListeners[clientId] = + this.unsubscribeClientListeners[clientId] || {}; + /** * Register any potential listeners that subscribed before sandpack ran */ @@ -376,7 +378,7 @@ class SandpackProviderClass extends React.PureComponent< Object.keys(this.queuedListeners[clientId]).forEach((listenerId) => { const listener = this.queuedListeners[clientId][listenerId]; const unsubscribe = client.listen(listener) as () => void; - this.unsubscribeQueuedListeners[clientId][listenerId] = unsubscribe; + this.unsubscribeClientListeners[clientId][listenerId] = unsubscribe; }); // Clear the queued listeners after they were registered @@ -389,7 +391,7 @@ class SandpackProviderClass extends React.PureComponent< const globalListeners = Object.entries(this.queuedListeners.global); globalListeners.forEach(([listenerId, listener]) => { const unsubscribe = client.listen(listener) as () => void; - this.unsubscribeQueuedListeners[clientId][listenerId] = unsubscribe; + this.unsubscribeClientListeners[clientId][listenerId] = unsubscribe; /** * Important: Do not clean the global queue @@ -432,6 +434,16 @@ class SandpackProviderClass extends React.PureComponent< clearTimeout(this.timeoutHook); } + const unsubscribeQueuedClients = Object.values( + this.unsubscribeClientListeners + ); + + // Unsubscribing all listener registered + unsubscribeQueuedClients.forEach((listenerOfClient) => { + const listenerFunctions = Object.values(listenerOfClient); + listenerFunctions.forEach((unsubscribe) => unsubscribe()); + }); + this.setState({ sandpackStatus: "idle" }); }; @@ -523,26 +535,32 @@ class SandpackProviderClass extends React.PureComponent< return unsubscribeListener; } else { - // When listeners are added before the client is instantiated, they are stored with an unique id - // When the client is eventually instantiated, the listeners are registered on the spot - // Their unsubscribe functions are stored in unsubscribeQueuedListeners for future cleanup + /** + * When listeners are added before the client is instantiated, they are stored with an unique id + * When the client is eventually instantiated, the listeners are registered on the spot + * Their unsubscribe functions are stored in unsubscribeClientListeners for future cleanup + */ const listenerId = generateRandomId(); this.queuedListeners[clientId] = this.queuedListeners[clientId] || {}; - this.unsubscribeQueuedListeners[clientId] = - this.unsubscribeQueuedListeners[clientId] || {}; + this.unsubscribeClientListeners[clientId] = + this.unsubscribeClientListeners[clientId] || {}; this.queuedListeners[clientId][listenerId] = listener; const unsubscribeListener = (): void => { if (this.queuedListeners[clientId][listenerId]) { - // unsubscribe was called before the client was instantiated - // common example - a component with autorun=false that unmounted + /** + * Unsubscribe was called before the client was instantiated + * common example - a component with autorun=false that unmounted + */ delete this.queuedListeners[clientId][listenerId]; - } else if (this.unsubscribeQueuedListeners[clientId][listenerId]) { - // unsubscribe was called for a listener that got added before the client was instantiated - // call the unsubscribe function and remove it from memory - this.unsubscribeQueuedListeners[clientId][listenerId](); - delete this.unsubscribeQueuedListeners[clientId][listenerId]; + } else if (this.unsubscribeClientListeners[clientId][listenerId]) { + /** + * unsubscribe was called for a listener that got added before the client was instantiated + * call the unsubscribe function and remove it from memory + */ + this.unsubscribeClientListeners[clientId][listenerId](); + delete this.unsubscribeClientListeners[clientId][listenerId]; } }; @@ -560,16 +578,6 @@ class SandpackProviderClass extends React.PureComponent< ); const unsubscribeListener = (): void => { - const unsubscribeQueuedClients = Object.values( - this.unsubscribeQueuedListeners - ); - - // Unsubscribing all listener registered - unsubscribeQueuedClients.forEach((listenerOfClient) => { - const listenerFunctions = Object.values(listenerOfClient); - listenerFunctions.forEach((unsubscribe) => unsubscribe()); - }); - // Unsubscribing from the clients already created currentClientUnsubscribeListeners.forEach((unsubscribe) => unsubscribe() From 931f77c64054a9a660148ae532114456565b7df4 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Mon, 27 Jun 2022 15:56:10 +0000 Subject: [PATCH 2/3] Update Playground.stories.tsx and sandpackContext.test.tsx --- sandpack-react/src/Playground.stories.tsx | 2 +- .../src/contexts/sandpackContext.test.tsx | 116 +++++++++++++----- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/sandpack-react/src/Playground.stories.tsx b/sandpack-react/src/Playground.stories.tsx index 945fb73e0..d56aa37ec 100644 --- a/sandpack-react/src/Playground.stories.tsx +++ b/sandpack-react/src/Playground.stories.tsx @@ -3,6 +3,7 @@ import * as themes from "@codesandbox/sandpack-themes"; import { useEffect, useState } from "react"; import type { CodeEditorProps } from "./components/CodeEditor"; +import { useSandpack } from "./hooks"; import { SANDBOX_TEMPLATES } from "./templates"; import { @@ -12,7 +13,6 @@ import { SandpackLayout, SandpackFileExplorer, } from "./"; -import { useSandpack } from "./hooks"; export default { title: "Intro/Playground", diff --git a/sandpack-react/src/contexts/sandpackContext.test.tsx b/sandpack-react/src/contexts/sandpackContext.test.tsx index 55c25c638..515de2d78 100644 --- a/sandpack-react/src/contexts/sandpackContext.test.tsx +++ b/sandpack-react/src/contexts/sandpackContext.test.tsx @@ -4,11 +4,10 @@ import React from "react"; import { create } from "react-test-renderer"; -import * as sandpackClient from "@codesandbox/sandpack-client"; +import { REACT_TEMPLATE } from ".."; -import { REACT_TEMPLATE, SandpackState, SandpackProviderState } from ".."; - -import { SandpackProvider, SandpackProviderClass } from "./sandpackContext"; +import type { SandpackProviderClass } from "./sandpackContext"; +import { SandpackProvider } from "./sandpackContext"; const createContext = (): SandpackProviderClass => { const root = create(); @@ -19,6 +18,18 @@ const createContext = (): SandpackProviderClass => { return instance; }; +const getAmountOfListener = ( + instance: SandpackProviderClass, + name = "client-id", + ignoreGlobalListener = false +): number => { + return ( + Object.keys(instance.clients[name].iframeProtocol.channelListeners).length - + 1 - // less protocol listener + (ignoreGlobalListener ? 0 : 1) // less the global Sandpack-react listener + ); +}; + describe(SandpackProvider, () => { describe("updateFile", () => { it("adds a file", () => { @@ -166,11 +177,7 @@ describe(SandpackProvider, () => { expect(Object.keys(instance.queuedListeners.global).length).toBe(1); // Expect: one listener in the client - expect( - Object.keys( - instance.clients["client-id"].iframeProtocol.globalListeners - ).length - ).toBe(1); + expect(getAmountOfListener(instance)).toBe(1); }); it("set a listener, but the client has already been created - no global listener", () => { @@ -200,11 +207,7 @@ describe(SandpackProvider, () => { expect(Object.keys(instance.queuedListeners.global).length).toBe(0); // Expect: one listener in the client - expect( - Object.keys( - instance.clients["client-id"].iframeProtocol.channelListeners - ).length - ).toBe(1); + expect(getAmountOfListener(instance)).toBe(1); }); it("set a listener, but the client has already been created - global listener", () => { @@ -234,14 +237,10 @@ describe(SandpackProvider, () => { expect(Object.keys(instance.queuedListeners.global).length).toBe(1); // Expect: one listener in the client - expect( - Object.keys( - instance.clients["client-id"].iframeProtocol.channelListeners - ).length - ).toBe(1); + expect(getAmountOfListener(instance)).toBe(1); }); - it("sets a new listener and then one more client has been created", () => { + it("sets a new listener, and then create one more client", () => { const instance = createContext(); // Act: Add listener @@ -260,29 +259,78 @@ describe(SandpackProvider, () => { expect(Object.keys(instance.queuedListeners.global).length).toBe(0); // Expect: one listener in the client - expect( - Object.keys( - instance.clients["client-id"].iframeProtocol.channelListeners - ).length - ).toBe(1); + expect(getAmountOfListener(instance)).toBe(1); // Act: Add one more listener const anotherMock = jest.fn(); instance.addListener(anotherMock /* , no client-id */); - console.log( - instance.clients["client-id"].iframeProtocol.channelListenersCount - ); - // Expect: one global listener expect(Object.keys(instance.queuedListeners.global).length).toBe(1); // Expect: two listener in the client - expect( - Object.keys( - instance.clients["client-id"].iframeProtocol.channelListeners - ).length - ).toBe(2); + expect(getAmountOfListener(instance)).toBe(2); + }); + + it("unsubscribes only from the assigned client id", () => { + const instance = createContext(); + + instance.registerBundler(document.createElement("iframe"), "client-1"); + instance.registerBundler(document.createElement("iframe"), "client-2"); + + // Initial state + expect(getAmountOfListener(instance, "client-1")).toBe(0); + expect(getAmountOfListener(instance, "client-2", true)).toBe(0); + + // Add listeners + instance.addListener(jest.fn(), "client-1"); + const unsubscribeClientTwo = instance.addListener(jest.fn(), "client-2"); + + expect(getAmountOfListener(instance, "client-1")).toBe(1); + expect(getAmountOfListener(instance, "client-2", true)).toBe(1); + + unsubscribeClientTwo(); + + expect(getAmountOfListener(instance, "client-1")).toBe(1); + expect(getAmountOfListener(instance, "client-2", true)).toBe(0); + }); + + it("doesn't trigger global unsubscribe", () => { + const instance = createContext(); + + instance.registerBundler(document.createElement("iframe"), "client-1"); + instance.registerBundler(document.createElement("iframe"), "client-2"); + + instance.addListener(jest.fn()); + instance.addListener(jest.fn()); + const unsubscribe = instance.addListener(jest.fn()); + + expect(getAmountOfListener(instance, "client-1")).toBe(3); + expect(getAmountOfListener(instance, "client-2", true)).toBe(3); + + unsubscribe(); + + expect(getAmountOfListener(instance, "client-1")).toBe(2); + expect(getAmountOfListener(instance, "client-2", true)).toBe(2); + }); + + it("unsubscribe all the listeners from a specific client when it unmonts", () => { + const instance = createContext(); + + instance.registerBundler(document.createElement("iframe"), "client-1"); + instance.registerBundler(document.createElement("iframe"), "client-2"); + + instance.addListener(jest.fn()); + instance.addListener(jest.fn()); + instance.addListener(jest.fn()); + + expect(getAmountOfListener(instance, "client-1")).toBe(3); + expect(getAmountOfListener(instance, "client-2", true)).toBe(3); + + instance.unregisterBundler("client-2"); + + expect(getAmountOfListener(instance, "client-1")).toBe(3); + expect(instance.clients["client-2"]).toBe(undefined); }); }); }); From 6fd811330e3dfbb26fb4ad38624fab88b7bb9421 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Mon, 27 Jun 2022 16:10:16 +0000 Subject: [PATCH 3/3] Update iframe-protocol.ts and Playground.stories.tsx --- sandpack-client/src/iframe-protocol.ts | 1 - sandpack-react/src/Playground.stories.tsx | 17 +---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/sandpack-client/src/iframe-protocol.ts b/sandpack-client/src/iframe-protocol.ts index 9358bccd4..9f79d4d0f 100644 --- a/sandpack-client/src/iframe-protocol.ts +++ b/sandpack-client/src/iframe-protocol.ts @@ -102,7 +102,6 @@ export class IFrameProtocol { const listenerId = this.channelListenersCount; this.channelListeners[listenerId] = listener; this.channelListenersCount++; - return (): void => { delete this.channelListeners[listenerId]; }; diff --git a/sandpack-react/src/Playground.stories.tsx b/sandpack-react/src/Playground.stories.tsx index d56aa37ec..256efc46c 100644 --- a/sandpack-react/src/Playground.stories.tsx +++ b/sandpack-react/src/Playground.stories.tsx @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as themes from "@codesandbox/sandpack-themes"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import type { CodeEditorProps } from "./components/CodeEditor"; -import { useSandpack } from "./hooks"; import { SANDBOX_TEMPLATES } from "./templates"; import { @@ -18,18 +17,6 @@ export default { title: "Intro/Playground", }; -const Listerner = () => { - const { listen } = useSandpack(); - - useEffect(() => { - const foo = listen(console.log); - - return () => foo(); - }, [listen]); - - return null; -}; - export const Main = (): JSX.Element => { const [config, setConfig] = useState({ Components: { Preview: true, Editor: true, FileExplorer: true }, @@ -155,8 +142,6 @@ export const Main = (): JSX.Element => { showRefreshButton={config.Options?.showRefreshButton} /> )} - -