From bc79eab7e17b763b96ede01b219cfad8a4b68335 Mon Sep 17 00:00:00 2001 From: chinmay17 Date: Fri, 4 Oct 2019 08:35:05 +0530 Subject: [PATCH 1/4] Refactor /editor/content/preview/index to replace Cerebral with Overmind --- .../Sandbox/Editor/Content/Preview/index.tsx | 408 ++++++++++-------- 1 file changed, 230 insertions(+), 178 deletions(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx index 3fe2dc1f7bf..34c1784eace 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx @@ -1,6 +1,7 @@ // @flow -import React, { Component } from 'react'; -import { inject, observer } from 'app/componentConnectors'; +import React, { useState, useCallback, useEffect } from 'react'; +import _property from 'lodash/property'; +import { useOvermind } from 'app/overmind'; import BasePreview from '@codesandbox/common/lib/components/Preview'; import RunOnClick from '@codesandbox/common/lib/components/RunOnClick'; @@ -9,200 +10,251 @@ import getTemplate from '@codesandbox/common/lib/templates'; type Props = { hidden?: boolean; runOnClick?: boolean; - store: any; - signals: any; options: { url?: string }; - reaction: any; }; -type State = { - running: boolean; +const useForceUpdate = () => { + const [callback, setCallback] = useState(() => () => {}); + const forceUpdate = useCallback( + cb => { + setCallback(() => cb); + }, + [setCallback] + ); + + useEffect(() => { + callback(); + }, [callback]); + + return forceUpdate; }; -class PreviewComponent extends Component { - state: State = { - running: !this.props.runOnClick, - }; - - onPreviewInitialized = preview => { - const disposeHandleProjectViewChange = this.props.reaction( - ({ editor }) => editor.isInProjectView, - this.handleProjectView.bind(this, preview) - ); - const disposeHandleForcedRenders = this.props.reaction( - ({ editor }) => editor.forceRender, - this.handleExecuteCode.bind(this, preview) - ); - const disposeHandleExternalResources = this.props.reaction( - ({ editor }) => editor.currentSandbox.externalResources.length, - this.handleExecuteCode.bind(this, preview) - ); - const disposeHandleModuleSyncedChange = this.props.reaction( - ({ editor }) => editor.isAllModulesSynced, - this.handleModuleSyncedChange.bind(this, preview) - ); - const disposeHandleCodeChange = this.props.reaction( - ({ editor }) => String(editor.currentSandbox.modules.map(m => m.code)), - () => { - this.handleCodeChange(preview); - } - ); - const disposeHandleModuleChange = this.props.reaction( - ({ editor }) => editor.currentModule, - () => { - if (!this.props.store.editor.isInProjectView) { - this.handleCodeChange(preview); - } - } - ); - const disposeHandleStructureChange = this.props.reaction( - this.detectStructureChange, - this.handleStructureChange.bind(this, preview) - ); - const disposeDependenciesHandler = this.props.reaction( - ({ editor }) => - editor.currentSandbox.npmDependencies.keys - ? editor.currentSandbox.npmDependencies.keys().length - : Object.keys(editor.currentSandbox.npmDependencies), - this.handleDependenciesChange.bind(this, preview) - ); - - return () => { - disposeHandleModuleChange(); - disposeHandleProjectViewChange(); - disposeHandleForcedRenders(); - disposeHandleExternalResources(); - disposeHandleModuleSyncedChange(); - disposeHandleCodeChange(); - disposeHandleStructureChange(); - disposeDependenciesHandler(); - }; - }; - - detectStructureChange = ({ editor }) => { - const sandbox = editor.currentSandbox; - - return String( - sandbox.modules - .map(module => module.directoryShortid + module.title) - .concat( - sandbox.directories.map( - directory => directory.directoryShortid + directory.title +const PreviewComponent: React.FC = ({ + hidden, + runOnClick, + options: { url }, +}) => { + const [running, setRunning] = useState(!runOnClick); + const handleRunOnClick = useCallback(() => { + setRunning(true); + }, [setRunning]); + const forceUpdate = useForceUpdate(); + + const { + state: { + editor: { + currentSandbox, + currentModule, + isInProjectView, + previewWindowVisible, + initialPath, + isResizing, + }, + preferences: { settings }, + server, + }, + actions: { + editor: { errorsCleared, previewActionReceived, projectViewToggled }, + }, + reaction, + } = useOvermind(); + const { template } = currentSandbox; + const { livePreviewEnabled, instantPreviewEnabled } = settings; + + const handleErrorsCleared = useCallback(() => errorsCleared(), [ + errorsCleared, + ]); + + const handleProjectViewToggled = useCallback(() => projectViewToggled(), [ + projectViewToggled, + ]); + + const handlePreviewAction = useCallback( + action => previewActionReceived({ action }), + [previewActionReceived] + ); + + const detectStructureChange = useCallback( + ({ editor: { currentSandbox: sandbox } }) => + String( + sandbox.modules + .map(module => module.directoryShortid + module.title) + .concat( + sandbox.directories.map( + directory => directory.directoryShortid + directory.title + ) ) - ) - ); - }; + ), + [] + ); - handleDependenciesChange = preview => { + const handleDependenciesChange = useCallback(preview => { preview.handleDependenciesChange(); - }; - - handleCodeChange = preview => { - const { settings } = this.props.store.preferences; - const { isServer } = getTemplate( - this.props.store.editor.currentSandbox.template - ); - if (!isServer && settings.livePreviewEnabled) { - if (settings.instantPreviewEnabled) { - preview.executeCodeImmediately(); - } else { - preview.executeCode(); + }, []); + + const handleCodeChange = useCallback( + preview => { + const { isServer } = getTemplate(template); + if (!isServer && livePreviewEnabled) { + if (instantPreviewEnabled) { + preview.executeCodeImmediately(); + } else { + preview.executeCode(); + } } - } - }; + }, + [template, livePreviewEnabled, instantPreviewEnabled] + ); - handleStructureChange = preview => { - const { settings } = this.props.store.preferences; - if (settings.livePreviewEnabled) { - if (settings.instantPreviewEnabled) { - preview.executeCodeImmediately(); - } else { - preview.executeCode(); + const handleStructureChange = useCallback( + preview => { + if (livePreviewEnabled) { + if (instantPreviewEnabled) { + preview.executeCodeImmediately(); + } else { + preview.executeCode(); + } } - } - }; - - handleModuleSyncedChange = (preview, change) => { - const { settings } = this.props.store.preferences; - - if ( - change && - (this.props.store.editor.currentSandbox.template === 'static' || - !settings.livePreviewEnabled) - ) { - preview.handleRefresh(); - } - }; - - handleExecuteCode = preview => { + }, + [livePreviewEnabled, instantPreviewEnabled] + ); + + const handleModuleSyncedChange = useCallback( + (preview, change) => { + if (change && (template === 'static' || !livePreviewEnabled)) { + preview.handleRefresh(); + } + }, + [template, livePreviewEnabled] + ); + + const handleExecuteCode = useCallback(preview => { preview.executeCodeImmediately(); - }; + }, []); + + const handleProjectView = useCallback( + preview => { + forceUpdate(() => { + preview.executeCodeImmediately(); + }); + }, + [forceUpdate] + ); + + const handlePreviewInitialized = useCallback( + preview => { + const disposeHandleProjectViewChange = reaction( + _property('editor.isInProjectView'), + () => handleProjectView(preview) + ); + const disposeHandleForcedRenders = reaction( + _property('editor.forceRender'), + () => handleExecuteCode(preview) + ); + const disposeHandleExternalResources = reaction( + _property('editor.currentSandbox.externalResources.length'), + () => handleExecuteCode(preview) + ); + const disposeHandleModuleSyncedChange = reaction( + _property('editor.isAllModulesSynced'), + change => handleModuleSyncedChange(preview, change) + ); + const disposeHandleCodeChange = reaction( + ({ editor: { currentSandbox: _currentSandbox } }) => + String(_currentSandbox.modules.map(m => m.code)), + () => { + handleCodeChange(preview); + } + ); + const disposeHandleModuleChange = reaction( + _property('editor.currentModule'), + () => { + if (!isInProjectView) { + handleCodeChange(preview); + } + } + ); + const disposeHandleStructureChange = reaction(detectStructureChange, () => + handleStructureChange(preview) + ); + const disposeDependenciesHandler = reaction( + ({ editor: { currentSandbox: _currentSandbox } }) => + _currentSandbox.npmDependencies.keys + ? _currentSandbox.npmDependencies.keys().length + : Object.keys(_currentSandbox.npmDependencies), + () => handleDependenciesChange(preview) + ); + + return () => { + disposeHandleModuleChange(); + disposeHandleProjectViewChange(); + disposeHandleForcedRenders(); + disposeHandleExternalResources(); + disposeHandleModuleSyncedChange(); + disposeHandleCodeChange(); + disposeHandleStructureChange(); + disposeDependenciesHandler(); + }; + }, + [ + detectStructureChange, + handleCodeChange, + handleDependenciesChange, + handleExecuteCode, + handleModuleSyncedChange, + handleProjectView, + handleStructureChange, + isInProjectView, + reaction, + ] + ); - handleProjectView = preview => { - this.forceUpdate(() => { - preview.executeCodeImmediately(); - }); - }; + const completelyHidden = !previewWindowVisible; /** * Responsible for showing a message when something is happening with SSE. Only used * for server sandboxes right now, but we can extend it in the future. It would require * a better design if we want to use it for more though. */ - getOverlayMessage = () => { - const { - containerStatus, - error, - hasUnrecoverableError, - } = this.props.store.server; - - if (containerStatus === 'hibernated') { - return 'The container has been hibernated because of inactivity, you can start it by refreshing the browser.'; - } - - if (containerStatus === 'stopped') { - return 'Restarting the sandbox...'; - } - - if (error && hasUnrecoverableError) { - return 'A sandbox error occurred, you can refresh the page to restart the container.'; - } - - return undefined; - }; - - render() { - const { store, signals, options } = this.props; - - const completelyHidden = !store.editor.previewWindowVisible; - - return this.state.running ? ( - signals.editor.errorsCleared()} - onAction={action => signals.editor.previewActionReceived({ action })} - hide={this.props.hidden} - noPreview={completelyHidden} - onToggleProjectView={() => signals.editor.projectViewToggled()} - isResizing={store.editor.isResizing} - overlayMessage={this.getOverlayMessage()} - /> - ) : ( - { - this.setState({ running: true }); - }} - /> - ); + let overlayMessage: string; + const { containerStatus, error, hasUnrecoverableError } = server; + + if (containerStatus === 'hibernated') { + overlayMessage = + 'The container has been hibernated because of inactivity, you can start it by refreshing the browser.'; + } + + if (containerStatus === 'stopped') { + overlayMessage = 'Restarting the sandbox...'; } -} -export const Preview = inject('signals', 'store')(observer(PreviewComponent)); + if (error && hasUnrecoverableError) { + overlayMessage = + 'A sandbox error occurred, you can refresh the page to restart the container.'; + } + + return running ? ( + + ) : ( + + ); +}; + +export const Preview = PreviewComponent; From 77b4e65ea873b6062f7cc3539e008a016d8b5e2a Mon Sep 17 00:00:00 2001 From: chinmay17 Date: Fri, 4 Oct 2019 09:45:03 +0530 Subject: [PATCH 2/4] Fix useForceUpdate for cases when a callback is not passed --- .../src/app/pages/Sandbox/Editor/Content/Preview/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx index 34c1784eace..30362425c52 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx @@ -1,6 +1,7 @@ // @flow import React, { useState, useCallback, useEffect } from 'react'; import _property from 'lodash/property'; +import _isFunction from 'lodash/isFunction'; import { useOvermind } from 'app/overmind'; import BasePreview from '@codesandbox/common/lib/components/Preview'; @@ -23,7 +24,9 @@ const useForceUpdate = () => { ); useEffect(() => { - callback(); + if (_isFunction(callback)) { + callback(); + } }, [callback]); return forceUpdate; From 05ddb0a0118cd8c7436f7d7240d6aa3c20030252 Mon Sep 17 00:00:00 2001 From: chinmay17 Date: Fri, 4 Oct 2019 15:09:51 +0530 Subject: [PATCH 3/4] Upgrade to overmind@next and overmind-react@next and remove `npmDependencies.keys()` --- .../src/app/pages/Sandbox/Editor/Content/Preview/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx index 30362425c52..bf19f61a1e7 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx @@ -182,9 +182,7 @@ const PreviewComponent: React.FC = ({ ); const disposeDependenciesHandler = reaction( ({ editor: { currentSandbox: _currentSandbox } }) => - _currentSandbox.npmDependencies.keys - ? _currentSandbox.npmDependencies.keys().length - : Object.keys(_currentSandbox.npmDependencies), + Object.keys(_currentSandbox.npmDependencies), () => handleDependenciesChange(preview) ); From cd12f7b8b21ddcdd7a379237e39bc656080c8742 Mon Sep 17 00:00:00 2001 From: chinmay17 Date: Mon, 4 Nov 2019 09:37:28 +0530 Subject: [PATCH 4/4] Fix interface name and exports --- .../src/app/pages/Sandbox/Editor/Content/Preview/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx index bf19f61a1e7..dadb7e55378 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.tsx @@ -8,7 +8,7 @@ import BasePreview from '@codesandbox/common/lib/components/Preview'; import RunOnClick from '@codesandbox/common/lib/components/RunOnClick'; import getTemplate from '@codesandbox/common/lib/templates'; -type Props = { +type IPreviewProps = { hidden?: boolean; runOnClick?: boolean; options: { url?: string }; @@ -32,7 +32,7 @@ const useForceUpdate = () => { return forceUpdate; }; -const PreviewComponent: React.FC = ({ +export const Preview: React.FC = ({ hidden, runOnClick, options: { url }, @@ -257,5 +257,3 @@ const PreviewComponent: React.FC = ({ ); }; - -export const Preview = PreviewComponent;