diff --git a/packages/app/scripts/start.js b/packages/app/scripts/start.js
index 041f7e8692d..3eb1427ce4c 100644
--- a/packages/app/scripts/start.js
+++ b/packages/app/scripts/start.js
@@ -212,15 +212,6 @@ function addMiddleware(devServer, index) {
changeOrigin: true,
})
);
-
- // devServer.use(
- // '/socket.io',
- // proxy({
- // target: 'https://sse.codesandbox.io',
- // changeOrigin: true,
- // secure: false,
- // })
- // );
}
if (process.env.VSCODE) {
devServer.use(
diff --git a/packages/app/src/app/components/Preview/DevTools/Tabs/index.tsx b/packages/app/src/app/components/Preview/DevTools/Tabs/index.tsx
index 2bb70534e82..8ceb57b55cc 100644
--- a/packages/app/src/app/components/Preview/DevTools/Tabs/index.tsx
+++ b/packages/app/src/app/components/Preview/DevTools/Tabs/index.tsx
@@ -36,7 +36,7 @@ const DevToolTabs = ({
const currentPane = panes[currentPaneIndex];
const actions =
typeof currentPane.actions === 'function'
- ? currentPane.actions(owned)
+ ? currentPane.actions({ owned })
: currentPane.actions;
const TypedTabDropZone = (TabDropZone as unknown) as React.SFC<
@@ -89,21 +89,27 @@ const DevToolTabs = ({
- {actions.map(({ title, onClick, Icon }) => (
-
- {
+ return (
+
-
- ))}
+ delay={disabled ? [0, 0] : [500, 0]}
+ >
+
+
+ );
+ })}
);
diff --git a/packages/app/src/app/components/Preview/DevTools/Terminal/index.js b/packages/app/src/app/components/Preview/DevTools/Terminal/index.js
index 021a832daed..7e538b93456 100644
--- a/packages/app/src/app/components/Preview/DevTools/Terminal/index.js
+++ b/packages/app/src/app/components/Preview/DevTools/Terminal/index.js
@@ -229,16 +229,17 @@ export default {
id: 'codesandbox.terminal',
title: 'Terminal',
Content: withTheme(TerminalComponent),
- actions: (owner: boolean) =>
+ actions: ({ owned }) =>
[
- owner && {
- title: 'Add Terminal',
+ {
+ title: owned ? 'Add Terminal' : 'Fork to add a terminal',
onClick: () => {
- if (createShell) {
+ if (createShell && owned) {
createShell();
}
},
Icon: PlusIcon,
+ disabled: !owned,
},
].filter(Boolean),
};
diff --git a/packages/app/src/app/components/Preview/DevTools/index.tsx b/packages/app/src/app/components/Preview/DevTools/index.tsx
index c250703819a..5f40889d1fb 100644
--- a/packages/app/src/app/components/Preview/DevTools/index.tsx
+++ b/packages/app/src/app/components/Preview/DevTools/index.tsx
@@ -46,13 +46,14 @@ export interface IViewAction {
title: string;
onClick: () => void;
Icon: React.ComponentClass;
+ disabled?: boolean;
}
export interface IViewType {
id: string;
title: string;
Content: React.ComponentClass;
- actions: IViewAction[] | ((owner: boolean) => IViewAction[]);
+ actions: IViewAction[] | ((info: { owned: boolean }) => IViewAction[]);
}
export type StatusType = 'info' | 'warning' | 'error' | 'success' | 'clear';
diff --git a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.js b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.js
index 8d815ef0f8e..50878e4a206 100644
--- a/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.js
+++ b/packages/app/src/app/pages/Sandbox/Editor/Content/Preview/index.js
@@ -217,9 +217,14 @@ class Preview extends Component {
onToggleProjectView={() => signals.editor.projectViewToggled()}
showDevtools={store.preferences.showDevtools}
isResizing={store.editor.isResizing}
- setServerStatus={(status: string) => {
+ setSSEManagerStatus={(status: string) => {
signals.server.statusChanged({ status });
}}
+ setSSEContainerStatus={(status: string) => {
+ signals.server.containerStatusChanged({ status });
+ }}
+ managerStatus={store.server.status}
+ containerStatus={store.server.containerStatus}
syncSandbox={signals.files.syncSandbox}
/>
) : (
diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.js
deleted file mode 100644
index a3b5b30da95..00000000000
--- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.js
+++ /dev/null
@@ -1,53 +0,0 @@
-// @flow
-import React from 'react';
-import styled from 'styled-components';
-
-type Props = {
- status:
- | 'connected'
- | 'disconnected'
- | 'initializing'
- | 'hibernated'
- | 'error',
-};
-
-const StatusCircle = styled.div`
- border-radius: 50%;
- background-color: ${props => props.color};
- width: 8px;
- height: 8px;
- margin-left: 0.75rem; /* Very precise to keep aligned with script icons */
- margin-right: 0.75rem;
-`;
-
-const Container = styled.div`
- display: flex;
- align-items: center;
-
- color: ${props =>
- props.theme['editor.foreground'] || 'rgba(255, 255, 255, 0.8)'};
-`;
-
-const STATUS_MESSAGES = {
- disconnected: 'Reconnecting to sandbox...',
- connected: 'Connected to sandbox',
- initializing: 'Initializing connection to sandbox...',
- hibernated: 'Sandbox hibernated',
- error: 'Unrecoverable sandbox error',
-};
-
-const STATUS_COLOR = {
- disconnected: '#FD2439',
- connected: '#4CFF00',
- initializing: '#FFD399',
- hibernated: '#FF662E',
- error: '#FD2439',
-};
-
-export default ({ status }: Props) => (
-
-
-
- {STATUS_MESSAGES[status]}
-
-);
diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.tsx
new file mode 100644
index 00000000000..bbba84098af
--- /dev/null
+++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/Status.tsx
@@ -0,0 +1,94 @@
+// @flow
+import React from 'react';
+import styled from 'styled-components';
+
+import {
+ SSEContainerStatus,
+ SSEManagerStatus,
+} from '@codesandbox/common/lib/types';
+
+type Props = {
+ containerStatus: SSEContainerStatus;
+ managerStatus: SSEManagerStatus;
+};
+
+const StatusCircle = styled.div`
+ border-radius: 50%;
+ background-color: ${props => props.color};
+ width: 8px;
+ height: 8px;
+ margin-left: 0.75rem; /* Very precise to keep aligned with script icons */
+ margin-right: 0.75rem;
+`;
+
+const Container = styled.div`
+ display: flex;
+ align-items: center;
+
+ color: ${props =>
+ props.theme['editor.foreground'] || 'rgba(255, 255, 255, 0.8)'};
+`;
+
+const STATUS_MESSAGES = {
+ disconnected: 'Reconnecting to sandbox...',
+ connected: 'Connected to sandbox',
+ initializing: 'Initializing connection to sandbox...',
+ hibernated: 'Sandbox hibernated',
+ error: 'Unrecoverable sandbox error',
+};
+
+const STATUS_COLOR = {
+ disconnected: '#FD2439',
+ connected: '#4CFF00',
+ initializing: '#FFD399',
+ hibernated: '#FF662E',
+ error: '#FD2439',
+};
+
+function getContainerStatusMessageAndColor(
+ containerStatus: SSEContainerStatus
+) {
+ switch (containerStatus) {
+ case 'initializing':
+ return { color: '#FFD399', message: 'Container is starting...' };
+ case 'container-started':
+ case 'stopped':
+ return { color: '#FFD399', message: 'Sandbox is starting...' };
+ case 'sandbox-started':
+ return { color: '#4CFF00', message: 'Connected to sandbox' };
+ case 'error':
+ return { color: '#FD2439', message: 'A sandbox error occurred' };
+ case 'hibernated':
+ return { color: '#FF662E', message: 'Sandbox hibernated' };
+ default:
+ return undefined;
+ }
+}
+
+function getManagerStatusMessageAndColor(managerStatus: SSEManagerStatus) {
+ switch (managerStatus) {
+ case 'connected':
+ case 'disconnected':
+ return undefined;
+ case 'initializing':
+ return {
+ message: 'Initializing connection to sandbox...',
+ color: '#FFD399',
+ };
+ default:
+ return undefined;
+ }
+}
+
+export default ({ containerStatus, managerStatus }: Props) => {
+ const { color, message } =
+ getManagerStatusMessageAndColor(managerStatus) ||
+ getContainerStatusMessageAndColor(containerStatus);
+ return (
+
+
+
+ {message}
+
+ );
+};
diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/index.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/index.js
index 77db1a31e56..b6ca65683f3 100644
--- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/index.js
+++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Server/index.js
@@ -23,9 +23,8 @@ const SubTitle = styled.div`
font-size: 0.875rem;
`;
-const Server = ({ store }) => {
+function Server({ store }) {
const { parsed } = store.editor.parsedConfigurations.package;
-
const disconnected = store.server.status !== 'connected';
return (
@@ -38,7 +37,10 @@ const Server = ({ store }) => {
Status
-
+
@@ -58,17 +60,6 @@ const Server = ({ store }) => {
-
- Secret Keys
-
- Secrets are available as environment variables. They are kept private
- and will not be transferred between forks.
-
-
-
-
-
-
Control Container
@@ -80,10 +71,12 @@ const Server = ({ store }) => {
}}
small
block
- disabled={disconnected}
- onClick={() =>
- dispatch({ type: 'socket:message', channel: 'sandbox:restart' })
+ disabled={
+ disconnected || store.server.containerStatus !== 'sandbox-started'
}
+ onClick={() => {
+ dispatch({ type: 'socket:message', channel: 'sandbox:restart' });
+ }}
>
{
Restart Sandbox
+
+
+
+
+
+
+ Secret Keys
+
+ Secrets are available as environment variables. They are kept private
+ and will not be transferred between forks.
+
+
+
+
);
-};
+}
-export default inject('store')(observer(Server));
+export default inject('store', 'signals')(observer(Server));
diff --git a/packages/app/src/app/store/modules/server/index.js b/packages/app/src/app/store/modules/server/index.js
index 0b83a40a966..cacccb7244b 100644
--- a/packages/app/src/app/store/modules/server/index.js
+++ b/packages/app/src/app/store/modules/server/index.js
@@ -6,8 +6,10 @@ export default Module({
model,
state: {
status: 'initializing',
+ containerStatus: 'initializing',
},
signals: {
statusChanged: sequences.setStatus,
+ containerStatusChanged: sequences.setContainerStatus,
},
});
diff --git a/packages/app/src/app/store/modules/server/model.js b/packages/app/src/app/store/modules/server/model.js
index a13f5db7a84..ef60e8609d8 100644
--- a/packages/app/src/app/store/modules/server/model.js
+++ b/packages/app/src/app/store/modules/server/model.js
@@ -2,9 +2,15 @@ import { types } from 'mobx-state-tree';
export default {
status: types.enumeration('status', [
+ 'initializing',
'connected',
'disconnected',
+ ]),
+ containerStatus: types.enumeration('container-status', [
'initializing',
+ 'container-started',
+ 'sandbox-started',
+ 'stopped',
'hibernated',
'error',
]),
diff --git a/packages/app/src/app/store/modules/server/sequences.js b/packages/app/src/app/store/modules/server/sequences.js
index 5ff7b59b8a5..28dcd3ff4b0 100644
--- a/packages/app/src/app/store/modules/server/sequences.js
+++ b/packages/app/src/app/store/modules/server/sequences.js
@@ -2,3 +2,7 @@ import { set } from 'cerebral/operators';
import { state, props } from 'cerebral/tags';
export const setStatus = [set(state`server.status`, props`status`)];
+
+export const setContainerStatus = [
+ set(state`server.containerStatus`, props`status`),
+];
diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json
index 169f3cf3b65..26fcb21cf54 100644
--- a/packages/app/tsconfig.json
+++ b/packages/app/tsconfig.json
@@ -5,6 +5,7 @@
"esModuleInterop": true,
"baseUrl": "./",
"paths": {
+ "types": ["../common/src/types/index"],
"app/*": ["./src/app/*"],
"embed/*": ["./src/embed/*"],
"sandbox/*": ["./src/sandbox/*"]
diff --git a/packages/common/src/components/Preview/elements.ts b/packages/common/src/components/Preview/elements.ts
index 3ea2eb160b8..9912608a77d 100644
--- a/packages/common/src/components/Preview/elements.ts
+++ b/packages/common/src/components/Preview/elements.ts
@@ -36,7 +36,7 @@ export const Loading = styled.div`
align-items: center;
justify-content: center;
- font-size: 2rem;
+ font-size: 1.5rem;
font-weight: 300;
color: white;
line-height: 1.3;
diff --git a/packages/common/src/components/Preview/index.tsx b/packages/common/src/components/Preview/index.tsx
index de3f21f074b..f452e6b5bb6 100644
--- a/packages/common/src/components/Preview/index.tsx
+++ b/packages/common/src/components/Preview/index.tsx
@@ -1,6 +1,11 @@
// @flow
import * as React from 'react';
-import { Sandbox, Module } from '../../types';
+import {
+ Sandbox,
+ Module,
+ SSEManagerStatus,
+ SSEContainerStatus,
+} from '../../types';
import {
listen,
dispatch,
@@ -44,7 +49,10 @@ export type Props = {
noPreview?: boolean;
alignDirection?: 'right' | 'bottom';
delay?: number;
- setServerStatus?: (status: string) => void;
+ setSSEManagerStatus?: (status: SSEManagerStatus) => void;
+ setSSEContainerStatus?: (status: SSEContainerStatus) => void;
+ managerStatus?: SSEManagerStatus;
+ containerStatus?: SSEContainerStatus;
syncSandbox?: (updates: any) => void;
className?: string;
};
@@ -231,10 +239,10 @@ class BasePreview extends React.Component {
setupSSESockets = async () => {
const hasInitialized = !!this.$socket;
- function onTimeout(comp) {
+ function onTimeout(comp: BasePreview) {
comp.connectTimeout = null;
- if (comp.props.setServerStatus) {
- comp.props.setServerStatus('disconnected');
+ if (comp.props.setSSEManagerStatus) {
+ comp.props.setSSEManagerStatus('disconnected');
}
}
@@ -267,14 +275,12 @@ class BasePreview extends React.Component {
return;
}
- if (this.props.setServerStatus) {
- let status = 'disconnected';
- if (this.state.hibernated) {
- status = 'hibernated';
- } else if (this.state.sseError) {
- status = 'error';
- }
- this.props.setServerStatus(status);
+ if (
+ this.props.setSSEManagerStatus &&
+ this.props.managerStatus === 'connected' &&
+ this.props.containerStatus !== 'hibernated'
+ ) {
+ this.props.setSSEManagerStatus('disconnected');
dispatch({ type: 'codesandbox:sse:disconnect' });
}
});
@@ -285,8 +291,8 @@ class BasePreview extends React.Component {
this.connectTimeout = null;
}
- if (this.props.setServerStatus) {
- this.props.setServerStatus('connected');
+ if (this.props.setSSEManagerStatus) {
+ this.props.setSSEManagerStatus('connected');
}
const { id } = this.props.sandbox;
@@ -322,28 +328,49 @@ class BasePreview extends React.Component {
}
});
+ socket.on('sandbox:status', message => {
+ if (this.props.setSSEContainerStatus) {
+ if (message.status === 'starting-container') {
+ this.props.setSSEContainerStatus('initializing');
+ } else if (message.status === 'installing-packages') {
+ this.props.setSSEContainerStatus('container-started');
+ }
+ }
+ });
+
socket.on('sandbox:start', () => {
sseTerminalMessage(`sandbox ${this.props.sandbox.id} started.`);
if (!this.state.frameInitialized && this.props.onInitialized) {
this.disposeInitializer = this.props.onInitialized(this);
}
- this.handleRefresh();
+
this.setState({
frameInitialized: true,
overlayMessage: null,
});
+ if (this.props.setSSEContainerStatus) {
+ this.props.setSSEContainerStatus('sandbox-started');
+ }
+
+ setTimeout(() => {
+ this.executeCodeImmediately(true);
+ this.handleRefresh();
+ });
});
socket.on('sandbox:hibernate', () => {
sseTerminalMessage(`sandbox ${this.props.sandbox.id} hibernated.`);
+ if (this.props.setSSEContainerStatus) {
+ this.props.setSSEContainerStatus('hibernated');
+ }
+
this.setState(
{
frameInitialized: false,
overlayMessage:
'The sandbox was hibernated because of inactivity. Refresh the page to restart it.',
- hibernated: true,
},
() => this.$socket.close()
);
@@ -352,6 +379,10 @@ class BasePreview extends React.Component {
socket.on('sandbox:stop', () => {
sseTerminalMessage(`sandbox ${this.props.sandbox.id} restarting...`);
+ if (this.props.setSSEContainerStatus) {
+ this.props.setSSEContainerStatus('stopped');
+ }
+
this.setState({
frameInitialized: false,
overlayMessage: 'Restarting the sandbox...',
@@ -607,8 +638,11 @@ class BasePreview extends React.Component {
const modulesToSend = this.getModulesToSend();
if (this.serverPreview) {
const diff = getDiff(this.lastSent.modules, modulesToSend);
-
- this.lastSent.modules = modulesToSend;
+ if (this.props.containerStatus === 'sandbox-started') {
+ // Only mark the last modules if we're sure that the container has been able
+ // to process the last diff
+ this.lastSent.modules = modulesToSend;
+ }
if (Object.keys(diff).length > 0 && this.$socket) {
this.$socket.emit('sandbox:update', diff);
diff --git a/packages/common/src/global.d.ts b/packages/common/src/types/global.d.ts
similarity index 100%
rename from packages/common/src/global.d.ts
rename to packages/common/src/types/global.d.ts
diff --git a/packages/common/src/types.ts b/packages/common/src/types/index.ts
similarity index 94%
rename from packages/common/src/types.ts
rename to packages/common/src/types/index.ts
index 76adacaed79..e329e61dda2 100644
--- a/packages/common/src/types.ts
+++ b/packages/common/src/types/index.ts
@@ -1,5 +1,14 @@
import * as React from 'react';
-import { TemplateType } from './templates';
+import { TemplateType } from '../templates';
+
+export type SSEContainerStatus =
+ | 'initializing'
+ | 'container-started'
+ | 'sandbox-started'
+ | 'stopped'
+ | 'hibernated'
+ | 'error';
+export type SSEManagerStatus = 'connected' | 'disconnected' | 'initializing';
export type ModuleError = {
message: string;
diff --git a/standalone-packages/sse-loading-screen/src/Cube.js b/standalone-packages/sse-loading-screen/src/Cube.js
index d7195d786e5..4d153c96552 100644
--- a/standalone-packages/sse-loading-screen/src/Cube.js
+++ b/standalone-packages/sse-loading-screen/src/Cube.js
@@ -15,14 +15,13 @@ const SHADOW_SIZE = (() => {
}
if (isSafari) {
- return 100;
+ return 50;
}
- return 100;
+ return 50;
})();
-const getContainerAnimation = (offset: number) => {
- return keyframes`
+const getContainerAnimation = () => keyframes`
0% {
transform: translateY(-5px);
}
@@ -33,7 +32,6 @@ const getContainerAnimation = (offset: number) => {
transform: translateY(-5px);
}
`;
-};
const Container = styled.div`
animation: ${() => getContainerAnimation(0)} 8 s ease infinite;