From d6f236fcd4fcd01398976df778443fe23ad61a9d Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Fri, 7 Oct 2022 13:18:58 +0100 Subject: [PATCH 01/19] cache babel assets --- beta/src/components/MDX/Sandpack/Preview.tsx | 3 ++- beta/src/components/MDX/Sandpack/SandpackRoot.tsx | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/beta/src/components/MDX/Sandpack/Preview.tsx b/beta/src/components/MDX/Sandpack/Preview.tsx index 20c8311ee46..75f3500579b 100644 --- a/beta/src/components/MDX/Sandpack/Preview.tsx +++ b/beta/src/components/MDX/Sandpack/Preview.tsx @@ -222,7 +222,8 @@ export function Preview({ loading={!isReady && iframeComputedHeight === null} /> - + {/* TODO: temp */} + {/* */} ); } diff --git a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx index 27ea59152d0..8b3b5074c1e 100644 --- a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx +++ b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx @@ -87,8 +87,8 @@ function SandpackRoot(props: SandpackProps) { autorun, initMode: 'user-visible', initModeObserverOptions: {rootMargin: '1400px 0px'}, - bundlerURL: 'https://ac83f2d6.sandpack-bundler.pages.dev', - logLevel: SandpackLogLevel.None, + bundlerURL: 'https://sandpack-bundler.pages.dev', + logLevel: SandpackLogLevel.Debug, // TODO: temp }}> Date: Thu, 13 Oct 2022 21:50:05 +0000 Subject: [PATCH 02/19] Update SandpackRoot.tsx --- beta/src/components/MDX/Sandpack/SandpackRoot.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx index 8b3b5074c1e..c20aee1dd72 100644 --- a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx +++ b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx @@ -87,7 +87,7 @@ function SandpackRoot(props: SandpackProps) { autorun, initMode: 'user-visible', initModeObserverOptions: {rootMargin: '1400px 0px'}, - bundlerURL: 'https://sandpack-bundler.pages.dev', + bundlerURL: 'https://71008268.sandpack-bundler.pages.dev/?babel=minimal', logLevel: SandpackLogLevel.Debug, // TODO: temp }}> Date: Thu, 13 Oct 2022 21:54:06 +0000 Subject: [PATCH 03/19] Update NavigationBar.tsx --- beta/src/components/MDX/Sandpack/NavigationBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/beta/src/components/MDX/Sandpack/NavigationBar.tsx b/beta/src/components/MDX/Sandpack/NavigationBar.tsx index eedf9fc38cb..d9072d25e5f 100644 --- a/beta/src/components/MDX/Sandpack/NavigationBar.tsx +++ b/beta/src/components/MDX/Sandpack/NavigationBar.tsx @@ -94,9 +94,9 @@ export function NavigationBar({providedFiles}: {providedFiles: Array}) { }, [isMultiFile]); const handleReset = () => { - if (confirm('Reset all your edits too?')) { + // if (confirm('Reset all your edits too?')) { sandpack.resetAllFiles(); - } + // } refresh(); }; From 0548b038538249495bece65284e6b24f5ede713a Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Thu, 13 Oct 2022 22:07:27 +0000 Subject: [PATCH 04/19] Update SandpackRoot.tsx --- beta/src/components/MDX/Sandpack/SandpackRoot.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx index c20aee1dd72..9b021e2ee07 100644 --- a/beta/src/components/MDX/Sandpack/SandpackRoot.tsx +++ b/beta/src/components/MDX/Sandpack/SandpackRoot.tsx @@ -87,7 +87,7 @@ function SandpackRoot(props: SandpackProps) { autorun, initMode: 'user-visible', initModeObserverOptions: {rootMargin: '1400px 0px'}, - bundlerURL: 'https://71008268.sandpack-bundler.pages.dev/?babel=minimal', + bundlerURL: 'https://6e2b0f76.sandpack-bundler.pages.dev/?babel=minimal', logLevel: SandpackLogLevel.Debug, // TODO: temp }}> Date: Sat, 15 Oct 2022 19:23:10 +0000 Subject: [PATCH 05/19] Update 7 files --- .../Sandpack/{Error.tsx => ErrorMessage.tsx} | 6 +- .../MDX/Sandpack/LoadingOverlay.tsx | 144 +++++++++++++++ .../components/MDX/Sandpack/NavigationBar.tsx | 15 +- beta/src/components/MDX/Sandpack/Preview.tsx | 164 +++++++++--------- .../components/MDX/Sandpack/SandpackRoot.tsx | 5 +- beta/src/content/test.md | 34 ++++ 6 files changed, 281 insertions(+), 87 deletions(-) rename beta/src/components/MDX/Sandpack/{Error.tsx => ErrorMessage.tsx} (75%) create mode 100644 beta/src/components/MDX/Sandpack/LoadingOverlay.tsx create mode 100644 beta/src/content/test.md diff --git a/beta/src/components/MDX/Sandpack/Error.tsx b/beta/src/components/MDX/Sandpack/ErrorMessage.tsx similarity index 75% rename from beta/src/components/MDX/Sandpack/Error.tsx rename to beta/src/components/MDX/Sandpack/ErrorMessage.tsx index 61344ebac83..d2a3b67c6f0 100644 --- a/beta/src/components/MDX/Sandpack/Error.tsx +++ b/beta/src/components/MDX/Sandpack/ErrorMessage.tsx @@ -10,13 +10,13 @@ interface ErrorType { path?: string; } -export function Error({error}: {error: ErrorType}) { +export function ErrorMessage({error}: {error: ErrorType}) { const {message, title} = error; return ( -
+

{title || 'Error'}

-
+      
         {message}
       
diff --git a/beta/src/components/MDX/Sandpack/LoadingOverlay.tsx b/beta/src/components/MDX/Sandpack/LoadingOverlay.tsx new file mode 100644 index 00000000000..cf83495af33 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/LoadingOverlay.tsx @@ -0,0 +1,144 @@ +import {useState} from 'react'; + +import { + LoadingOverlayState, + OpenInCodeSandboxButton, + useSandpack, +} from '@codesandbox/sandpack-react'; +import {useEffect} from 'react'; + +const FADE_ANIMATION_DURATION = 200; + +export const LoadingOverlay = ({ + clientId, + dependenciesLoading, + forceLoading, +}: { + clientId: string; + dependenciesLoading: boolean; + forceLoading: boolean; +} & React.HTMLAttributes): JSX.Element | null => { + const loadingOverlayState = useLoadingOverlayState( + clientId, + dependenciesLoading, + forceLoading + ); + + if (loadingOverlayState === 'HIDDEN') { + return null; + } + + if (loadingOverlayState === 'TIMEOUT') { + return ( +
+
+ Unable to establish connection with the sandpack bundler. Make sure + you are online or try again later. If the problem persists, please + report it via{' '} + + email + {' '} + or submit an issue on{' '} + + GitHub. + +
+
+ ); + } + + const stillLoading = + loadingOverlayState === 'LOADING' || loadingOverlayState === 'PRE_FADING'; + + return ( +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +const useLoadingOverlayState = ( + clientId: string, + dependenciesLoading: boolean, + forceLoading: boolean +): LoadingOverlayState => { + const {sandpack, listen} = useSandpack(); + const [state, setState] = useState('LOADING'); + + if (state !== 'LOADING' && forceLoading) { + setState('LOADING'); + } + + /** + * Sandpack listener + */ + useEffect(() => { + sandpack.loadingScreenRegisteredRef.current = true; + + const unsubscribe = listen((message) => { + if (message.type === 'done') { + setState((prev) => { + return prev === 'LOADING' ? 'PRE_FADING' : 'HIDDEN'; + }); + } + }, clientId); + + return (): void => { + unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clientId, sandpack.status === 'idle']); + + /** + * Fading transient state + */ + useEffect(() => { + let fadeTimeout: NodeJS.Timer; + + if (state === 'PRE_FADING' && !dependenciesLoading) { + setState('FADING'); + } else if (state === 'FADING') { + fadeTimeout = setTimeout( + () => setState('HIDDEN'), + FADE_ANIMATION_DURATION + ); + } + + return (): void => { + clearTimeout(fadeTimeout); + }; + }, [state, dependenciesLoading]); + + if (sandpack.status === 'timeout') { + return 'TIMEOUT'; + } + + if (sandpack.status !== 'running') { + return 'HIDDEN'; + } + + return state; +}; diff --git a/beta/src/components/MDX/Sandpack/NavigationBar.tsx b/beta/src/components/MDX/Sandpack/NavigationBar.tsx index d9072d25e5f..2e20c91875c 100644 --- a/beta/src/components/MDX/Sandpack/NavigationBar.tsx +++ b/beta/src/components/MDX/Sandpack/NavigationBar.tsx @@ -94,9 +94,20 @@ export function NavigationBar({providedFiles}: {providedFiles: Array}) { }, [isMultiFile]); const handleReset = () => { - // if (confirm('Reset all your edits too?')) { + /** + * resetAllFiles must come first, otherwise + * the previous content will appears for a second + * when the iframe loads. + * + * Plus, it should only prompts if there's any file changes + */ + if ( + sandpack.editorState === 'dirty' && + confirm('Reset all your edits too?') + ) { sandpack.resetAllFiles(); - // } + } + refresh(); }; diff --git a/beta/src/components/MDX/Sandpack/Preview.tsx b/beta/src/components/MDX/Sandpack/Preview.tsx index 75f3500579b..c0dcd7217f5 100644 --- a/beta/src/components/MDX/Sandpack/Preview.tsx +++ b/beta/src/components/MDX/Sandpack/Preview.tsx @@ -3,26 +3,17 @@ */ /* eslint-disable react-hooks/exhaustive-deps */ -import {useRef, useState, useEffect, useMemo} from 'react'; -import { - useSandpack, - LoadingOverlay, - SandpackStack, -} from '@codesandbox/sandpack-react'; +import {useRef, useState, useEffect, useMemo, useId} from 'react'; +import {useSandpack, SandpackStack} from '@codesandbox/sandpack-react'; import cn from 'classnames'; -import {Error} from './Error'; +import {ErrorMessage} from './ErrorMessage'; import {SandpackConsole} from './Console'; import type {LintDiagnostic} from './useSandpackLint'; - -/** - * TODO: can we use React.useId? - */ -const generateRandomId = (): string => - Math.floor(Math.random() * 10000).toString(); +import {CSSProperties} from 'react'; +import {LoadingOverlay} from './LoadingOverlay'; type CustomPreviewProps = { className?: string; - customStyle?: Record; isExpanded: boolean; lintErrors: LintDiagnostic; }; @@ -40,13 +31,13 @@ function useDebounced(value: any): any { } export function Preview({ - customStyle, isExpanded, className, lintErrors, }: CustomPreviewProps) { const {sandpack, listen} = useSandpack(); - const [isReady, setIsReady] = useState(false); + const [bundlerIsReady, setBundlerIsReady] = useState(false); + const [showLoading, setShowLoading] = useState(false); const [iframeComputedHeight, setComputedAutoHeight] = useState( null ); @@ -95,7 +86,7 @@ export function Preview({ // It changes too fast, causing flicker. const error = useDebounced(rawError); - const clientId = useRef(generateRandomId()); + const clientId = useId(); const iframeRef = useRef(null); // SandpackPreview immediately registers the custom screens/components so the bundler does not render any of them @@ -106,29 +97,43 @@ export function Preview({ useEffect(function createBundler() { const iframeElement = iframeRef.current!; - registerBundler(iframeElement, clientId.current); + registerBundler(iframeElement, clientId); return () => { - unregisterBundler(clientId.current); + unregisterBundler(clientId); }; }, []); useEffect( function bundlerListener() { + let timeout: ReturnType; + const unsubscribe = listen((message: any) => { if (message.type === 'resize') { setComputedAutoHeight(message.height); } else if (message.type === 'start') { + /** + * The spinner component transition might be longer than + * the bundler loading, so we only show the spinner if + * it takes more than 1s to load the bundler. + */ + timeout = setTimeout(() => { + setShowLoading(true); + }, 1000); + if (message.firstLoad) { - setIsReady(false); + setBundlerIsReady(false); } } else if (message.type === 'done') { - setIsReady(true); + setBundlerIsReady(true); + setShowLoading(false); + clearTimeout(timeout); } - }, clientId.current); + }, clientId); return () => { - setIsReady(false); + clearTimeout(timeout); + setBundlerIsReady(false); setComputedAutoHeight(null); unsubscribe(); }; @@ -136,13 +141,7 @@ export function Preview({ [status === 'idle'] ); - const overrideStyle = error - ? { - // Don't collapse errors - maxHeight: undefined, - } - : null; - const hideContent = !isReady || error; + // const hideContent = !bundlerIsReady || error; // WARNING: // The layout and styling here is convoluted and really easy to break. @@ -159,67 +158,72 @@ export function Preview({ // - It should work on mobile. // The best way to test it is to actually go through some challenges. + const iframeWrapperPosition = (): CSSProperties => { + if (!bundlerIsReady || error) { + return {position: 'relative'}; + } + + if (isExpanded) { + return {position: 'sticky', top: '2em'}; + } + + return {}; + }; + return ( - +
-