diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c44d6b6fa39..a242ec2a773 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,5 +1,6 @@ name: 'Close stale issues and PRs' on: + workflow_dispatch: schedule: - cron: '0 0 * * *' @@ -11,15 +12,19 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: - 'This issue is stale because it has been open many days with no - activity. It will be closed soon unless the stale label is removed - or a comment is made.' + 'This issue has automatically been marked stale because there has + been no activity in a while. Please leave a comment if the issue has + not been resolved, or if it is not stale for any other reason. After + 2 weeks, this issue will automatically be closed, unless a comment + is made or the stale label is removed.' stale-issue-label: 'stale' exempt-issue-label: '💥 Crash Report' stale-pr-message: - 'This PR is stale because it has been open many days with no - activity. It will be closed soon unless the stale label is removed - or a comment is made.' + 'This PR has automatically been marked stale because there has been + no activity in a while. Please leave a comment if the issue has not + been resolved, or if it is not stale for any other reason. After 2 + weeks, this issue will automatically be closed, unless a comment is + made or the stale label is removed.' stale-pr-label: 'stale' exempt-pr-label: '💥 Crash Report' days-before-stale: 90 diff --git a/packages/app/src/app/overmind/namespaces/editor/actions.ts b/packages/app/src/app/overmind/namespaces/editor/actions.ts index cc04f0eb167..d62c8b0b7f8 100755 --- a/packages/app/src/app/overmind/namespaces/editor/actions.ts +++ b/packages/app/src/app/overmind/namespaces/editor/actions.ts @@ -868,9 +868,10 @@ export const updateEnvironmentVariables: AsyncAction = asyn effects.codesandboxApi.restartSandbox(); }; -export const deleteEnvironmentVariable: AsyncAction<{ - name: string; -}> = async ({ state, effects }, { name }) => { +export const deleteEnvironmentVariable: AsyncAction = async ( + { effects, state }, + name +) => { if (!state.editor.currentSandbox) { return; } diff --git a/packages/app/src/app/overmind/namespaces/profile/actions.ts b/packages/app/src/app/overmind/namespaces/profile/actions.ts index 4301ea077d3..e19b9a8bc6a 100755 --- a/packages/app/src/app/overmind/namespaces/profile/actions.ts +++ b/packages/app/src/app/overmind/namespaces/profile/actions.ts @@ -243,6 +243,9 @@ export const addFeaturedSandboxes: AsyncAction<{ sandbox => sandbox.id ); + // already featured + if (currentFeaturedSandboxIds.includes(sandboxId)) return; + // optimistic update actions.profile.addFeaturedSandboxesInState({ sandboxId }); @@ -294,6 +297,50 @@ export const removeFeaturedSandboxes: AsyncAction<{ } }; +export const reorderFeaturedSandboxesInState: Action<{ + startPosition: number; + endPosition: number; +}> = ({ state, actions, effects }, { startPosition, endPosition }) => { + if (!state.profile.current) return; + + // optimisic update + const featuredSandboxes = [...state.profile.current.featuredSandboxes]; + const sandbox = featuredSandboxes[startPosition]!; + + // remove element first + featuredSandboxes.splice(startPosition, 1); + // now add at new position + featuredSandboxes.splice(endPosition, 0, sandbox); + + state.profile.current.featuredSandboxes = featuredSandboxes; +}; + +export const saveFeaturedSandboxesOrder: AsyncAction = async ({ + actions, + effects, + state, +}) => { + if (!state.profile.current) return; + + try { + const featuredSandboxIds = state.profile.current.featuredSandboxes.map( + s => s.id + ); + const profile = await effects.api.updateUserFeaturedSandboxes( + state.profile.current.id, + featuredSandboxIds + ); + state.profile.current.featuredSandboxes = profile.featuredSandboxes; + } catch (error) { + // TODO: rollback optimisic update + + actions.internal.handleError({ + message: "We weren't able to re-order your pinned sandboxes", + error, + }); + } +}; + export const changeSandboxPrivacyInState: Action<{ sandboxId: string; privacy: 0 | 1 | 2; diff --git a/packages/app/src/app/overmind/namespaces/profile/state.ts b/packages/app/src/app/overmind/namespaces/profile/state.ts index b1b3f58608d..b334e0060e8 100755 --- a/packages/app/src/app/overmind/namespaces/profile/state.ts +++ b/packages/app/src/app/overmind/namespaces/profile/state.ts @@ -50,6 +50,8 @@ export const state: State = { searchQuery: null, isLoadingSandboxes: false, sandboxToDeleteId: null, + currentSortBy: 'view_count', + currentSortDirection: 'desc', isProfileCurrentUser: derived((currentState: State, rootState: RootState) => Boolean( rootState.user && rootState.user.id === currentState.currentProfileId @@ -75,6 +77,4 @@ export const state: State = { ? currentState.sandboxes[currentState.current.username] : [] ), - currentSortBy: 'view_count', - currentSortDirection: 'desc', }; diff --git a/packages/app/src/app/overmind/namespaces/server/actions.ts b/packages/app/src/app/overmind/namespaces/server/actions.ts index 3c0eb7abb9d..d4093f0fb34 100755 --- a/packages/app/src/app/overmind/namespaces/server/actions.ts +++ b/packages/app/src/app/overmind/namespaces/server/actions.ts @@ -13,7 +13,7 @@ export const restartSandbox: Action = ({ effects }) => { effects.executor.emit('sandbox:restart'); }; -export const restartContainer: Action = ({ state, effects }) => { +export const restartContainer: Action = ({ effects, state }) => { state.server.containerStatus = ServerContainerStatus.INITIALIZING; effects.executor.emit('sandbox:restart-container'); }; @@ -117,9 +117,7 @@ export const onSSEMessage: Action<{ actions: { primary: { label: 'Open Browser Pane', - run: () => { - actions.server.onBrowserFromPortOpened({ port }); - }, + run: () => actions.server.onBrowserFromPortOpened(port), }, }, }); @@ -172,16 +170,13 @@ export const onCodeSandboxAPIMessage: Action<{ }; type BrowserOptions = { title?: string; url?: string } & ( - | { - port: number; - } + | { port: number } | { url: string } ); - export const onBrowserTabOpened: Action<{ closeable?: boolean; options?: BrowserOptions; -}> = ({ actions, state }, { options, closeable }) => { +}> = ({ actions, state }, { closeable, options }) => { const tab: ViewTab = { id: 'codesandbox.browser', }; @@ -206,17 +201,18 @@ export const onBrowserTabOpened: Action<{ } }; -export const onBrowserFromPortOpened: Action<{ - port: ServerPort; -}> = ({ actions }, { port }) => { - if (port.main) { +export const onBrowserFromPortOpened: Action = ( + { actions }, + { hostname, main, port } +) => { + if (main) { actions.server.onBrowserTabOpened({}); } else { actions.server.onBrowserTabOpened({ closeable: true, options: { - port: port.port, - url: `https://${port.hostname}`, + port, + url: `https://${hostname}`, }, }); } diff --git a/packages/app/src/app/pages/Profile2/AllSandboxes.tsx b/packages/app/src/app/pages/Profile2/AllSandboxes.tsx index 026fa95aac3..58e25006913 100644 --- a/packages/app/src/app/pages/Profile2/AllSandboxes.tsx +++ b/packages/app/src/app/pages/Profile2/AllSandboxes.tsx @@ -8,9 +8,10 @@ import { Menu, } from '@codesandbox/components'; import css from '@styled-system/css'; +import { motion } from 'framer-motion'; import { useOvermind } from 'app/overmind'; import { SandboxCard, SkeletonCard } from './SandboxCard'; -import { SANDBOXES_PER_PAGE } from './constants'; +import { SANDBOXES_PER_PAGE, SandboxTypes } from './constants'; export const AllSandboxes = ({ menuControls }) => { const { @@ -119,7 +120,13 @@ export const AllSandboxes = ({ menuControls }) => { )) : sandboxes.map((sandbox, index) => ( - + + + ))} diff --git a/packages/app/src/app/pages/Profile2/PinnedSandboxes.tsx b/packages/app/src/app/pages/Profile2/PinnedSandboxes.tsx index 314e60e167c..8a8818cccf6 100644 --- a/packages/app/src/app/pages/Profile2/PinnedSandboxes.tsx +++ b/packages/app/src/app/pages/Profile2/PinnedSandboxes.tsx @@ -1,9 +1,11 @@ import React from 'react'; import { useOvermind } from 'app/overmind'; import { useDrop } from 'react-dnd'; +import { motion } from 'framer-motion'; import { Grid, Column, Stack, Text } from '@codesandbox/components'; import css from '@styled-system/css'; import { SandboxCard } from './SandboxCard'; +import { SandboxTypes } from './constants'; export const PinnedSandboxes = ({ menuControls }) => { const { @@ -16,7 +18,7 @@ export const PinnedSandboxes = ({ menuControls }) => { const myProfile = loggedInUser?.username === user.username; const [{ isOver }, drop] = useDrop({ - accept: 'sandbox', + accept: [SandboxTypes.ALL_SANDBOX, SandboxTypes.PINNED_SANDBOX], drop: () => ({ name: 'PINNED_SANDBOXES' }), collect: monitor => ({ isOver: monitor.isOver(), @@ -31,9 +33,16 @@ export const PinnedSandboxes = ({ menuControls }) => { gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', }} > - {user.featuredSandboxes.map(sandbox => ( + {user.featuredSandboxes.map((sandbox, index) => ( - + + + ))} {myProfile && ( diff --git a/packages/app/src/app/pages/Profile2/SandboxCard.tsx b/packages/app/src/app/pages/Profile2/SandboxCard.tsx index 6189dac8baf..4aaa5bdc213 100644 --- a/packages/app/src/app/pages/Profile2/SandboxCard.tsx +++ b/packages/app/src/app/pages/Profile2/SandboxCard.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useDrag } from 'react-dnd'; +import { useDrag, useDrop } from 'react-dnd'; import { useOvermind } from 'app/overmind'; import { Stack, @@ -11,36 +11,148 @@ import { } from '@codesandbox/components'; import css from '@styled-system/css'; import { sandboxUrl } from '@codesandbox/common/lib/utils/url-generator'; +import { SandboxTypes } from './constants'; + +type DragItem = { type: 'sandbox'; sandboxId: string; index: number | null }; export const SandboxCard = ({ + type = SandboxTypes.DEFAULT_SANDBOX, sandbox, menuControls: { onKeyDown, onContextMenu }, + index = null, }) => { const { state: { user: loggedInUser, - profile: { current: user }, + profile: { + current: { username, featuredSandboxes }, + }, }, actions: { - profile: { addFeaturedSandboxes }, + profile: { + addFeaturedSandboxesInState, + addFeaturedSandboxes, + reorderFeaturedSandboxesInState, + saveFeaturedSandboxesOrder, + removeFeaturedSandboxesInState, + }, }, } = useOvermind(); - const [, drag] = useDrag({ - item: { id: sandbox.id, type: 'sandbox' }, - end: (item: { id: string }, monitor) => { + const ref = React.useRef(null); + let previousPosition: number; + + const [{ isDragging }, drag] = useDrag({ + item: { type, sandboxId: sandbox.id, index }, + collect: monitor => { + const dragItem = monitor.getItem(); + return { + isDragging: dragItem?.sandboxId === sandbox.id, + }; + }, + + begin: () => { + if (type === SandboxTypes.PINNED_SANDBOX) { + previousPosition = index; + } + }, + end: (item: DragItem, monitor) => { const dropResult = monitor.getDropResult(); + + if (!dropResult) { + // This is the cancel event + if (item.type === SandboxTypes.PINNED_SANDBOX) { + // Rollback any reordering + reorderFeaturedSandboxesInState({ + startPosition: index, + endPosition: previousPosition, + }); + } else { + // remove newly added from featured in state + removeFeaturedSandboxesInState({ sandboxId: item.sandboxId }); + } + + return; + } + if (dropResult.name === 'PINNED_SANDBOXES') { - const { id } = item; - addFeaturedSandboxes({ sandboxId: id }); + if (featuredSandboxes.find(s => s.id === item.sandboxId)) { + saveFeaturedSandboxesOrder(); + } else { + addFeaturedSandboxes({ sandboxId: item.sandboxId }); + } + } + }, + }); + + const [, drop] = useDrop({ + accept: [SandboxTypes.ALL_SANDBOX, SandboxTypes.PINNED_SANDBOX], + hover: (item: DragItem, monitor) => { + if (!ref.current) return; + + const hoverIndex = index; + let dragIndex = -1; // not in list + + if (item.type === SandboxTypes.PINNED_SANDBOX) { + dragIndex = item.index; + } + + if (item.type === SandboxTypes.ALL_SANDBOX) { + // When an item from ALL_SANDOXES is hoverered over + // an item in pinned sandboxes, we insert the sandbox + // into featuredSandboxes in state. + if ( + hoverIndex && + !featuredSandboxes.find(s => s.id === item.sandboxId) + ) { + addFeaturedSandboxesInState({ sandboxId: item.sandboxId }); + } + dragIndex = featuredSandboxes.findIndex(s => s.id === item.sandboxId); } + + // If the item doesn't exist in featured sandboxes yet, return + if (dragIndex === -1) return; + + // Don't replace items with themselves + if (dragIndex === hoverIndex) return; + + // Determine rectangle for hoverered item + const hoverBoundingRect = ref.current?.getBoundingClientRect(); + + // Get offsets for dragged item + const dragOffset = monitor.getClientOffset(); + const hoverClientX = dragOffset.x - hoverBoundingRect.left; + + const hoverMiddleX = + (hoverBoundingRect.right - hoverBoundingRect.left) / 2; + + // Only perform the move when the mouse has crossed half of the items width + + // Dragging forward + if (dragIndex < hoverIndex && hoverClientX < hoverMiddleX) return; + + // Dragging backward + if (dragIndex > hoverIndex && hoverClientX > hoverMiddleX) return; + + reorderFeaturedSandboxesInState({ + startPosition: dragIndex, + endPosition: hoverIndex, + }); + // We're mutating the monitor item here to avoid expensive index searches! + item.index = hoverIndex; }, + drop: () => ({ name: 'PINNED_SANDBOXES' }), }); - const myProfile = loggedInUser?.username === user.username; + const myProfile = loggedInUser?.username === username; + + if (myProfile) { + if (type === SandboxTypes.ALL_SANDBOX) drag(ref); + else if (type === SandboxTypes.PINNED_SANDBOX) drag(drop(ref)); + } return ( -
+
onContextMenu(event, sandbox.id)} onKeyDown={event => onKeyDown(event, sandbox.id)} + style={{ opacity: isDragging ? 0.2 : 1 }} css={css({ backgroundColor: 'grays.700', border: '1px solid', @@ -82,8 +195,10 @@ export const SandboxCard = ({ }} /> - - {sandbox.title || sandbox.alias || sandbox.id} + + + {sandbox.title || sandbox.alias || sandbox.id} + { +export const Control: FunctionComponent = () => { const { actions: { server: { restartContainer, restartSandbox }, @@ -24,21 +26,22 @@ export const Control = () => { + diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars/VarForm.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars/VarForm.tsx new file mode 100644 index 00000000000..d49145556ea --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars/VarForm.tsx @@ -0,0 +1,98 @@ +import { + Button, + Element, + FormField, + Input, + Stack, +} from '@codesandbox/components'; +import React, { + FormEvent, + FunctionComponent, + KeyboardEvent, + useState, +} from 'react'; + +const noop = () => undefined; + +type Props = { + name?: string; + onCancel?: () => void; + onSubmit: (args: { name: string; value: string }) => void; + value?: string; +}; +export const VarForm: FunctionComponent = ({ + name: nameProp = '', + onCancel: onCancelProp = noop, + onSubmit: onSubmitProp, + value: valueProp = '', +}) => { + const [name, setName] = useState(nameProp); + const [value, setValue] = useState(valueProp); + + const onSubmit = (event: FormEvent) => { + event.preventDefault(); + + if (name && value) { + onSubmitProp({ name, value }); + setName(''); + setValue(''); + } + }; + + const onCancel = ({ key }: KeyboardEvent) => { + if (key === 'Escape') { + onCancelProp(); + } + }; + + return ( +
+ + + setName(target.value)} + onKeyDown={onCancel} + placeholder="Name" + required + value={name} + /> + + + + + setValue(target.value)} + onKeyDown={onCancel} + placeholder="Value" + required + value={value} + /> + + + + {nameProp && valueProp ? ( + + ) : null} + + + +
+ ); +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars/index.tsx similarity index 71% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars.tsx rename to packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars/index.tsx index 6344248fae8..14cc120e1dd 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/EnvVars/index.tsx @@ -1,6 +1,3 @@ -import { useOvermind } from 'app/overmind'; -import React, { useEffect, useState } from 'react'; - import { Collapsible, Text, @@ -9,35 +6,43 @@ import { ListItem, Stack, } from '@codesandbox/components'; -import { DeleteIcon, EditIcon } from './Icons'; +import React, { FunctionComponent, useEffect, useState } from 'react'; + +import { useOvermind } from 'app/overmind'; + +import { DeleteIcon, EditIcon } from '../Icons'; + import { VarForm } from './VarForm'; -export const EnvVars = () => { - const [editMode, setEditMode] = useState(null); +export const EnvVars: FunctionComponent = () => { const { - actions: { editor }, + actions: { + editor: { + deleteEnvironmentVariable, + fetchEnvironmentVariables, + updateEnvironmentVariables, + }, + }, state: { editor: { currentSandbox }, }, } = useOvermind(); + const [editMode, setEditMode] = useState(null); useEffect(() => { - editor.fetchEnvironmentVariables(); - }, [editor]); - - const deleteEnv = (name: string) => { - editor.deleteEnvironmentVariable({ name }); - }; + fetchEnvironmentVariables(); + }, [fetchEnvironmentVariables]); const envVars = currentSandbox.environmentVariables; return ( - + - + Secrets are available as environment variables. They are kept private and will not be transferred between forks. + {envVars ? ( {Object.keys(envVars).map(keyName => ( @@ -45,24 +50,26 @@ export const EnvVars = () => { {editMode === keyName || !envVars[keyName] ? ( setEditMode(null)} onSubmit={({ name, value }) => { - editor.updateEnvironmentVariables({ name, value }); + updateEnvironmentVariables({ name, value }); setEditMode(null); }} + value={envVars[keyName]} /> ) : ( {keyName} + setEditMode(keyName)} + style={{ cursor: 'pointer' }} /> + deleteEnvironmentVariable(keyName)} style={{ cursor: 'pointer' }} - onClick={() => deleteEnv(keyName)} /> @@ -72,11 +79,7 @@ export const EnvVars = () => { ) : null} - - editor.updateEnvironmentVariables({ name, value }) - } - /> + ); }; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Icons.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Icons.tsx index d35278f933e..af9c0e44970 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Icons.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Icons.tsx @@ -1,6 +1,8 @@ -import React from 'react'; +import React, { FunctionComponent, SVGProps } from 'react'; -export const EditIcon = props => ( +type IconProps = SVGProps; + +export const EditIcon: FunctionComponent = props => ( ( ); -export const DeleteIcon = props => ( +export const DeleteIcon: FunctionComponent = props => ( ( ); -export const RestartServerIcon = props => ( +export const RestartServerIcon: FunctionComponent = props => ( { +export const Ports: FunctionComponent = () => { const { actions: { server: { onBrowserTabOpened, onBrowserFromPortOpened }, }, state: { + editor: { + currentSandbox: { template }, + }, server: { ports }, - editor: { currentSandbox: sandbox }, }, } = useOvermind(); - const openPort = (port: ServerPort) => { - onBrowserFromPortOpened({ port }); - }; - - const openGraphiQLPort = () => { - const url = sandbox.template === 'gridsome' ? '/___explore' : '/___graphql'; + const openGraphiQLPort = () => onBrowserTabOpened({ closeable: true, options: { - url, + url: template === 'gridsome' ? '/___explore' : '/___graphql', title: 'GraphiQL', }, }); - }; return ( - {ports.length ? ( - ports.map((port: ServerPort) => ( - openPort(port)}> - + {ports.length > 0 ? ( + ports.map(port => ( + onBrowserFromPortOpened(port)}> + + {port.name || port.port} - {port.main && main} + + {port.main ? main : null} )) ) : ( - No ports are open. Maybe the server is still starting or it doesn - {"'"}t open any ports. + {`No ports are open. Maybe the server is still starting or it doesn't open any ports.`} )} - {['gatsby', 'gridsome'].includes(sandbox.template) && ports.length ? ( + {['gatsby', 'gridsome'].includes(template) && ports.length > 0 ? ( + GraphiQL diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Status.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Status.tsx index 9ef0a57bbce..99c2351da48 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Status.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Status.tsx @@ -1,89 +1,94 @@ -import React from 'react'; -import styled, { css } from 'styled-components'; -import { Stack, Text, Element } from '@codesandbox/components'; import { SSEContainerStatus, SSEManagerStatus, } from '@codesandbox/common/lib/types'; -import { useOvermind } from 'app/overmind'; +import { Element, Stack, Text } from '@codesandbox/components'; +import React, { FunctionComponent } from 'react'; +import styled, { css } from 'styled-components'; -type StatusCircleProps = { theme: any; color: string }; +import { useOvermind } from 'app/overmind'; -const StatusCircle = styled.div` - ${({ theme, color }: StatusCircleProps) => css` +const StatusCircle = styled.div<{ color: string }>` + ${({ color, theme }) => css` border-radius: 50%; background-color: ${color}; width: ${theme.space[2]}px; height: ${theme.space[2]}px; - `} + `}; `; const STATUS_MESSAGES = { - initializing: 'Container is starting...', - starting: 'Sandbox is starting...', - started: 'Connected to sandbox', error: 'A sandbox error occurred', hibernated: 'Sandbox hibernated', + initializing: 'Container is starting...', + started: 'Connected to sandbox', + starting: 'Sandbox is starting...', }; - -const STATUS_COLOR = { - disconnected: '#FD2439', +const STATUS_COLORS = { connected: '#4CFF00', - initializing: '#FFD399', - hibernated: '#FF662E', + disconnected: '#FD2439', error: '#FD2439', + hibernated: '#FF662E', + initializing: '#FFD399', +}; + +const getContainerStatusInfo = (status: SSEContainerStatus) => { + const containerStatuses = { + 'container-started': { + color: STATUS_COLORS.initializing, + message: STATUS_MESSAGES.starting, + }, + error: { color: STATUS_COLORS.error, message: STATUS_MESSAGES.error }, + hibernated: { + color: STATUS_COLORS.hibernated, + message: STATUS_MESSAGES.hibernated, + }, + initializing: { + color: STATUS_COLORS.initializing, + message: STATUS_MESSAGES.initializing, + }, + 'sandbox-started': { + color: STATUS_COLORS.connected, + message: STATUS_MESSAGES.started, + }, + stopped: { + color: STATUS_COLORS.initializing, + message: STATUS_MESSAGES.starting, + }, + }; + + return containerStatuses[status]; }; -function getContainerStatusMessageAndColor( - containerStatus: SSEContainerStatus -) { - switch (containerStatus) { - case 'initializing': - return { color: '#FFD399', message: STATUS_MESSAGES.initializing }; - case 'container-started': - case 'stopped': - return { color: '#FFD399', message: STATUS_MESSAGES.starting }; - case 'sandbox-started': - return { color: '#4CFF00', message: STATUS_MESSAGES.started }; - case 'error': - return { color: '#FD2439', message: STATUS_MESSAGES.error }; - case 'hibernated': - return { color: '#FF662E', message: STATUS_MESSAGES.hibernated }; - default: - return undefined; - } -} +const getManagerStatusInfo = (status: SSEManagerStatus) => { + const managerStatuses = { + connected: undefined, + disconnected: undefined, + initializing: { + color: STATUS_COLORS.initializing, + message: STATUS_MESSAGES.initializing, + }, + }; -function getManagerStatusMessageAndColor(managerStatus: SSEManagerStatus) { - switch (managerStatus) { - case 'connected': - case 'disconnected': - return undefined; - case 'initializing': - return { - message: STATUS_MESSAGES.initializing, - color: STATUS_COLOR.initializing, - }; - default: - return undefined; - } -} + return managerStatuses[status]; +}; -export const Status = () => { +export const Status: FunctionComponent = () => { const { state: { server: { containerStatus, status: managerStatus }, }, } = useOvermind(); const { color, message } = - getManagerStatusMessageAndColor(managerStatus) || - getContainerStatusMessageAndColor(containerStatus); + getManagerStatusInfo(managerStatus) || + getContainerStatusInfo(containerStatus); return ( + {message} ); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Tasks.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Tasks.tsx index dbe82e84b97..0f2941718b2 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Tasks.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/Tasks.tsx @@ -1,65 +1,67 @@ -import React from 'react'; +import { List, ListAction, Text, Collapsible } from '@codesandbox/components'; import { dispatch } from 'codesandbox-api'; - -import BuildIcon from 'react-icons/lib/fa/wrench'; -import FlaskIcon from 'react-icons/lib/fa/flask'; +import React, { FunctionComponent } from 'react'; import DownloadIcon from 'react-icons/lib/fa/download'; +import FlaskIcon from 'react-icons/lib/fa/flask'; +import BuildIcon from 'react-icons/lib/fa/wrench'; import NodeIcon from 'react-icons/lib/io/social-nodejs'; + import { useOvermind } from 'app/overmind'; -import { List, ListAction, Text, Collapsible } from '@codesandbox/components'; // These scripts are only supposed to run on the main thread. const blacklistedScripts = ['dev', 'develop', 'serve', 'start']; -const getIcon = (scriptName: string) => { - switch (scriptName) { - case 'test': - case 'lint': - return ; - case 'node': - return ; - case 'install': - return ; - default: { - return ; - } - } +const getIcon = (script: string) => { + const iconsByScript = { + install: DownloadIcon, + lint: FlaskIcon, + node: NodeIcon, + test: FlaskIcon, + }; + + return iconsByScript[script] || BuildIcon; }; -export const Tasks = () => { +export const Tasks: FunctionComponent = () => { const { state: { editor: { parsedConfigurations: pkg }, }, } = useOvermind(); - const runTask = (task: string) => { - dispatch({ - type: 'codesandbox:create-shell', - script: task, - }); - }; if (!pkg?.scripts) { return null; } - const commands = Object.keys(pkg.scripts).filter( - x => !blacklistedScripts.includes(x) - ); - + const runTask = (script: string) => + dispatch({ script, type: 'codesandbox:create-shell' }); return ( - + - {commands.map(task => ( - runTask(task)} key={task}> - {getIcon(task)} yarn {task} - - ))} + {Object.keys(pkg.scripts) + .filter(script => !blacklistedScripts.includes(script)) + .map(script => { + const Icon = getIcon(script); + + return ( + runTask(script)}> + + + yarn {script} + + ); + })} + runTask('install')}> - {getIcon('install')} yarn install + + + yarn install + runTask('node')}> - {getIcon('node')} node + + + node diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/VarForm.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/VarForm.tsx deleted file mode 100644 index ccf691b67cf..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/VarForm.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useState } from 'react'; - -import { - Element, - FormField, - Input, - Button, - Stack, -} from '@codesandbox/components'; - -export const VarForm = props => { - const [name, setName] = useState(props.name || ''); - const [value, setValue] = useState(props.value || ''); - - const submit = e => { - e.preventDefault(); - - if (name && value) { - props.onSubmit({ - name, - value, - }); - setName(''); - setValue(''); - } - }; - - const onCancel = e => { - if (e.key === 'Escape' && props.onCancel) { - props.onCancel(); - } - }; - - return ( -
- - - - - - - - {props.name && props.value ? ( - - ) : null} - - -
- ); -}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/index.tsx index ac9370e431a..627242ea2eb 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/screens/Server/index.tsx @@ -1,26 +1,33 @@ -import React from 'react'; -import { Collapsible, Text, Element } from '@codesandbox/components'; -import { Tasks } from './Tasks'; -import { Status } from './Status'; -import { Ports } from './Ports'; +import { Collapsible, Element, Text } from '@codesandbox/components'; +import React, { FunctionComponent } from 'react'; + import { Control } from './Control'; import { EnvVars } from './EnvVars'; +import { Ports } from './Ports'; +import { Status } from './Status'; +import { Tasks } from './Tasks'; -export const Server = () => ( +export const Server: FunctionComponent = () => ( - - + + Server Sandbox - + + This sandbox is executed on a server. You can control the server from this panel. + + + + + ); diff --git a/packages/app/src/app/pages/common/Modals/PreferencesModal/EditorPageSettings/EditorSettings.tsx b/packages/app/src/app/pages/common/Modals/PreferencesModal/EditorPageSettings/EditorSettings.tsx index d7db2b40246..a0bc721bfd6 100644 --- a/packages/app/src/app/pages/common/Modals/PreferencesModal/EditorPageSettings/EditorSettings.tsx +++ b/packages/app/src/app/pages/common/Modals/PreferencesModal/EditorPageSettings/EditorSettings.tsx @@ -12,7 +12,6 @@ import { VSCodePlaceholder } from '../VSCodePlaceholder'; import { PreferenceContainer } from '../elements'; const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); -const isFF = navigator.userAgent.toLowerCase().includes('firefox'); export const EditorSettings: FunctionComponent = () => { const { @@ -45,7 +44,7 @@ export const EditorSettings: FunctionComponent = () => { {/* {Vim mode does not work on FF or Safari */} - + { - {isSafari || isFF ? ( + {isSafari ? ( - The VIM extension currently only works on Chrome and Microsoft Edge. + The VIM extension currently does not work on Safari ) : null} diff --git a/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/__snapshots__/index.test.ts.snap b/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/__snapshots__/index.test.ts.snap index 3f12bc7d294..ac8c4acbc89 100644 --- a/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/__snapshots__/index.test.ts.snap +++ b/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/__snapshots__/index.test.ts.snap @@ -1,11 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`convert-esmodule can convert + + 1`] = ` -"\\"use strict\\"; -c = (10, + +15); -" -`; - exports[`convert-esmodule can convert class default exports 1`] = ` "\\"use strict\\"; Object.defineProperty(exports, \\"__esModule\\", { @@ -298,7 +292,7 @@ exports[`convert-esmodule doesn't set var definitions 1`] = ` Object.defineProperty(exports, \\"__esModule\\", { value: true }); -var global = typeof window !== \\"undefined\\" ? window : {}; +var global = typeof window !== \\"undefined\\" ? window : {}; exports.global = global; " `; @@ -635,6 +629,12 @@ var WS_CHARS = \\"u2000- 

   \\"; " `; +exports[`convert-esmodule printer issues can convert + + 1`] = ` +"\\"use strict\\"; +c = (10, + + 15); +" +`; + exports[`convert-esmodule renames exports that are already defined, even in block scope 1`] = ` "\\"use strict\\"; var __$csb_exports = \\"testtest\\"; diff --git a/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/generator.ts b/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/generator.ts index 5d51094df0a..0459dc8f75f 100644 --- a/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/generator.ts +++ b/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/generator.ts @@ -69,10 +69,7 @@ export const customGenerator = { ) { if (node.prefix) { state.write(node.operator); - if ( - node.operator.length > 1 || - node.argument.type === 'UnaryExpression' - ) { + if (node.operator.length > 1) { state.write(' '); } if ( @@ -83,6 +80,7 @@ export const customGenerator = { this[node.argument.type](node.argument, state); state.write(')'); } else { + state.write(' '); this[node.argument.type](node.argument, state); } } else { diff --git a/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/index.test.ts b/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/index.test.ts index 4faf4c62732..c4a370e75fd 100644 --- a/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/index.test.ts +++ b/packages/app/src/sandbox/eval/transpilers/babel/convert-esmodule/index.test.ts @@ -424,11 +424,18 @@ describe('convert-esmodule', () => { expect(convertEsModule(code)).toMatchSnapshot(); }); - it('can convert + +', () => { - const code = ` - c = (10.0, + +(15)) - `; - - expect(convertEsModule(code)).toMatchSnapshot(); + describe('printer issues', () => { + it('can convert + +', () => { + const code = ` + c = (10.0, + +(15)) + `; + + expect(convertEsModule(code)).toMatchSnapshot(); + }); + + it('can convert -(--i)', () => { + const code = `a = -(--i)`; + expect(convertEsModule(code)).toBe('"use strict";\na = - --i;\n'); + }); }); });