diff --git a/packages/app/src/app/overmind/namespaces/workspace/internalActions.ts b/packages/app/src/app/overmind/namespaces/workspace/internalActions.ts index b87728e647a..aeb896215eb 100644 --- a/packages/app/src/app/overmind/namespaces/workspace/internalActions.ts +++ b/packages/app/src/app/overmind/namespaces/workspace/internalActions.ts @@ -1,6 +1,6 @@ import { Action } from 'app/overmind'; -import getItems from './items'; import { Sandbox } from '@codesandbox/common/lib/types'; +import getItems from './items'; export const setWorkspace: Action = ({ state }, sandbox) => { state.workspace.project.title = sandbox.title || ''; diff --git a/packages/app/src/app/overmind/namespaces/workspace/items.ts b/packages/app/src/app/overmind/namespaces/workspace/items.ts index 59d2e240195..bfec214d8f7 100644 --- a/packages/app/src/app/overmind/namespaces/workspace/items.ts +++ b/packages/app/src/app/overmind/namespaces/workspace/items.ts @@ -1,54 +1,66 @@ import getTemplate from '@codesandbox/common/lib/templates'; -const PROJECT = { +interface INavigationItem { + id: string; + name: string; + hasCustomHeader?: boolean; + defaultOpen?: boolean; +} + +const PROJECT: INavigationItem = { id: 'project', - name: 'Project Info', + name: 'Sandbox Info', +}; + +const PROJECT_TEMPLATE: INavigationItem = { + ...PROJECT, + name: 'Template Info', }; -const PROJECT_SUMMARY = { +const PROJECT_SUMMARY: INavigationItem = { id: 'project-summary', name: 'Sandbox Info', hasCustomHeader: true, }; -const FILES = { +const FILES: INavigationItem = { id: 'files', name: 'Explorer', hasCustomHeader: true, defaultOpen: true, }; -const GITHUB = { +const GITHUB: INavigationItem = { id: 'github', name: 'GitHub', }; -const DEPLOYMENT = { +const DEPLOYMENT: INavigationItem = { id: 'deploy', name: 'Deployment', }; -const CONFIGURATION = { +const CONFIGURATION: INavigationItem = { id: 'config', name: 'Configuration Files', }; -const LIVE = { +const LIVE: INavigationItem = { id: 'live', name: 'Live', }; -const MORE = { +const MORE: INavigationItem = { id: 'more', name: 'More', }; -const SERVER = { +const SERVER: INavigationItem = { id: 'server', name: 'Server Control Panel', }; -export default function getItems(store) { +export default function getItems(store): INavigationItem[] { if ( store.live.isLive && !( @@ -66,7 +78,8 @@ export default function getItems(store) { return [PROJECT_SUMMARY, CONFIGURATION, MORE]; } - const items = [PROJECT, FILES]; + const isCustomTemplate = !!store.editor.currentSandbox.customTemplate; + const items = [isCustomTemplate ? PROJECT_TEMPLATE : PROJECT, FILES]; if (store.isLoggedIn && store.editor.currentSandbox) { const templateDef = getTemplate(store.editor.currentSandbox.template); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Navigation/Navigation.tsx b/packages/app/src/app/pages/Sandbox/Editor/Navigation/Navigation.tsx index eed3aa4c287..8666d2d895a 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Navigation/Navigation.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Navigation/Navigation.tsx @@ -14,9 +14,12 @@ import FilesIcon from '-!svg-react-loader!@codesandbox/common/lib/icons/file.svg import RocketIcon from '-!svg-react-loader!@codesandbox/common/lib/icons/rocket.svg'; // @ts-ignore import ConfigurationIcon from '-!svg-react-loader!@codesandbox/common/lib/icons/cog.svg'; -import getWorkspaceItems from 'app/store/modules/workspace/items'; +import getWorkspaceItems, { + getDisabledItems, + INavigationItem, +} from 'app/store/modules/workspace/items'; import { useSignals, useStore } from 'app/store'; -import { Container, IconContainer } from './elements'; +import { Container, IconContainer, Separator } from './elements'; import ServerIcon from './ServerIcon'; const IDS_TO_ICONS = { @@ -31,6 +34,42 @@ const IDS_TO_ICONS = { server: ServerIcon, }; +interface IconProps { + item: INavigationItem; + isDisabled?: boolean; +} + +const IconComponent = observer(({ item, isDisabled }: IconProps) => { + const { id, name } = item; + const store = useStore(); + const { + workspace: { setWorkspaceHidden, setWorkspaceItem }, + } = useSignals(); + + const Icon = IDS_TO_ICONS[id]; + const selected = + !store.workspace.workspaceHidden && + id === store.workspace.openedWorkspaceItem; + return ( + + { + if (selected) { + setWorkspaceHidden({ hidden: true }); + } else { + setWorkspaceHidden({ hidden: false }); + setWorkspaceItem({ item: id }); + } + }} + > + + + + ); +}); + export const Navigation = observer( ({ topOffset, @@ -39,39 +78,22 @@ export const Navigation = observer( topOffset: number; bottomOffset: number; }) => { - const { - workspace: { setWorkspaceHidden, setWorkspaceItem }, - } = useSignals(); const store = useStore(); + const shownItems = getWorkspaceItems(store); + const disabledItems = getDisabledItems(store); + return ( - {getWorkspaceItems(store) - .filter(w => !w.show || w.show(store)) - .map(item => { - const { id, name } = item; - const Icon = IDS_TO_ICONS[id]; - const selected = - !store.workspace.workspaceHidden && - id === store.workspace.openedWorkspaceItem; - return ( - - { - if (selected) { - setWorkspaceHidden({ hidden: true }); - } else { - setWorkspaceHidden({ hidden: false }); - setWorkspaceItem({ item: id }); - } - }} - > - - - - ); - })} + {shownItems.map(item => ( + + ))} + + + + {disabledItems.map(item => ( + + ))} ); } diff --git a/packages/app/src/app/pages/Sandbox/Editor/Navigation/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Navigation/elements.ts index 7e75ff15292..711b44135ec 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Navigation/elements.ts +++ b/packages/app/src/app/pages/Sandbox/Editor/Navigation/elements.ts @@ -27,26 +27,48 @@ export const Container = styled.div` `} `; -export const IconContainer = styled.div` - ${({ selected, theme }: { selected: boolean; theme: any }) => css` - display: flex; - justify-content: center; - align-items: center; - transition: 0.3s ease all; - height: 3.5rem; - width: 3.5rem; - font-size: 1.875rem; - color: ${theme[`activityBar.inactiveForeground`] || - css`rgba(255, 255, 255, 0.5)`}; - cursor: pointer; +export const IconContainer = styled.div<{ + selected: boolean; + isDisabled: boolean; +}>` + display: flex; + justify-content: center; + align-items: center; + transition: 0.3s ease all; + height: 3.5rem; + width: 3.5rem; + font-size: 1.875rem; + color: ${props => + props.theme[`activityBar.inactiveForeground`] || + css`rgba(255, 255, 255, 0.5)`}; + cursor: pointer; - &:hover { - color: ${theme[`activityBar.foreground`] || css`white`}; - } + &:hover { + color: ${props => props.theme[`activityBar.foreground`] || css`white`}; + } - ${selected && - css` - color: ${theme[`activityBar.foreground`] || css`white`}; - `}; - `} + ${props => + props.selected && + css` + color: ${props.theme[`activityBar.foreground`] || css`white`}; + `}; + + ${props => + props.isDisabled && + !props.selected && + css` + opacity: 0.4; + `} +`; + +export const Separator = styled.hr` + width: calc(100% - 20px); + height: 1px; + background-color: ${props => + props.theme.light ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255,255,255,0.1)'}; + + margin: 0.25rem 0; + + outline: none; + border: none; `; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx index 119093d08bc..a9ffa87f0ba 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx @@ -3,7 +3,9 @@ import { observer } from 'mobx-react-lite'; import React from 'react'; import SocialInfo from 'app/components/SocialInfo'; import { useStore } from 'app/store'; -import getWorkspaceItems from 'app/store/modules/workspace/items'; +import getWorkspaceItems, { + getDisabledItems, +} from 'app/store/modules/workspace/items'; import Files from './items/Files'; import { GitHub } from './items/GitHub'; import Server from './items/Server'; @@ -56,7 +58,9 @@ const Workspace = () => { } const Component = workspaceTabs[activeTab]; - const item = getWorkspaceItems(store).find(({ id }) => id === activeTab); + const item = + getWorkspaceItems(store).find(({ id }) => id === activeTab) || + getDisabledItems(store).find(({ id }) => id === activeTab); return ( diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Deployment/index.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Deployment/index.js deleted file mode 100644 index 62830469d8a..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Deployment/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import React, { Component } from 'react'; -import { inject, observer } from 'mobx-react'; -import { Description } from '../../elements'; -import ZeitDeployments from './Zeit'; -import NetlifyDeployments from './Netlify'; - -class Deployment extends Component { - componentDidMount = () => { - this.props.signals.deployment.getDeploys(); - }; - - render() { - return ( -
- - You can deploy a production version of your sandbox using one our - supported providers. - - - -
- ); - } -} - -export default inject('signals', 'store')(observer(Deployment)); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Deployment/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Deployment/index.tsx new file mode 100644 index 00000000000..86b8b20a43b --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Deployment/index.tsx @@ -0,0 +1,48 @@ +import React, { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; + +import { useStore, useSignals } from 'app/store'; +import { Description } from '../../elements'; +import ZeitDeployments from './Zeit'; +import NetlifyDeployments from './Netlify'; +import { More } from '../More'; + +const Deployment = observer(() => { + const store = useStore(); + const signals = useSignals(); + + const showPlaceholder = + !store.editor.currentSandbox.owned || !store.isLoggedIn; + + useEffect(() => { + if (!showPlaceholder) { + signals.deployment.getDeploys(); + } + }, [showPlaceholder, signals]); + + if (showPlaceholder) { + const message = store.isLoggedIn ? ( + <> + You need to own this sandbox to deploy this sandbox to Netlify or ZEIT.{' '} +

Fork this sandbox to make a deploy!

+ + ) : ( + <>You need to be signed in to deploy this sandbox to Netlify or ZEIT. + ); + + return ; + } + + return ( +
+ + You can deploy a production version of your sandbox using one our + supported providers. + + + +
+ ); +}); + +export default Deployment; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/GitHub/GitHub.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/GitHub/GitHub.tsx index add14f91d3a..8b4c035e038 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/GitHub/GitHub.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/GitHub/GitHub.tsx @@ -9,8 +9,27 @@ import WorkspaceItem from '../../WorkspaceItem'; import { CreateRepo } from './CreateRepo'; import { Git } from './Git'; +import { More } from '../More'; export const GitHub = observer(() => { + const store = useStore(); + + const showPlaceHolder = + !store.editor.currentSandbox.owned || !store.isLoggedIn; + + if (showPlaceHolder) { + const message = store.isLoggedIn ? ( + <> + You need to own this sandbox to export this sandbox to GitHub and make + commits and pull requests to it.

Make a fork to own the sandbox.

+ + ) : ( + `You need to be signed in to export this sandbox to GitHub and make commits and pull requests to it.` + ); + + return ; + } + const { editor: { currentSandbox: { originalGit }, @@ -18,7 +37,7 @@ export const GitHub = observer(() => { user: { integrations: { github }, }, - } = useStore(); + } = store; return github ? ( // eslint-disable-line originalGit ? ( diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js index b2d69c82543..66e46d84502 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js @@ -10,10 +10,29 @@ import { WorkspaceSubtitle, ErrorDescription, } from '../../elements'; +import { More } from '../More'; const Live = ({ signals, store }) => { const hasUnsyncedModules = !store.editor.isAllModulesSynced; + const showPlaceHolder = + (!store.live.isLive && !store.editor.currentSandbox.owned) || + !store.isLoggedIn; + + if (showPlaceHolder) { + const message = store.isLoggedIn ? ( + <> + You need to own this sandbox to open a live session to collaborate with + others in real time.{' '} +

Fork this sandbox to live share it with others!

+ + ) : ( + `You need to be signed in to open a live session to collaborate with others in real time. Sign in to live share this sandbox!` + ); + + return ; + } + return (
{store.live.isLive ? ( diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/More/More.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/More/More.tsx index 2f0a396a254..488d65843e4 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/More/More.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/More/More.tsx @@ -9,10 +9,12 @@ import { useSignals, useStore } from 'app/store'; import { Description } from '../../elements'; -const NOT_OWNED_MESSAGE = `Fork this sandbox to make deployments, commit to GitHub, create live sessions with others and more!`; -const NOT_SIGNED_IN_MESSAGE = `Sign in to be able to organize your sandboxes with a dashboard, make deployments, collaborate live with others, make commits to GitHub and more!`; +interface Props { + id: string; + message: string | JSX.Element; +} -export const More = observer(() => { +export const More = observer(({ id, message }: Props) => { const { editor: { forkSandboxClicked }, } = useSignals(); @@ -23,9 +25,7 @@ export const More = observer(() => { }, } = useStore(); - useEffect(() => track('Workspace - More Opened'), []); - - const message = !owned ? NOT_OWNED_MESSAGE : NOT_SIGNED_IN_MESSAGE; + useEffect(() => track('Workspace - More Opened', { id }), [id]); return (
diff --git a/packages/app/src/app/store/modules/workspace/items.js b/packages/app/src/app/store/modules/workspace/items.ts similarity index 52% rename from packages/app/src/app/store/modules/workspace/items.js rename to packages/app/src/app/store/modules/workspace/items.ts index 6a8d28d7138..7e244fd5a5e 100644 --- a/packages/app/src/app/store/modules/workspace/items.js +++ b/packages/app/src/app/store/modules/workspace/items.ts @@ -1,59 +1,79 @@ import getTemplate from '@codesandbox/common/lib/templates'; -const PROJECT = { +export interface INavigationItem { + id: string; + name: string; + hasCustomHeader?: boolean; + defaultOpen?: boolean; + /** + * If the item is not applicable in the current situation we sometimes still + * want to show it because of visibility. This boolean decides that. + */ + showAsDisabledIfHidden?: boolean; +} + +const PROJECT: INavigationItem = { id: 'project', name: 'Sandbox Info', }; -const PROJECT_TEMPLATE = { +const PROJECT_TEMPLATE: INavigationItem = { ...PROJECT, name: 'Template Info', }; -const PROJECT_SUMMARY = { +const PROJECT_SUMMARY: INavigationItem = { id: 'project-summary', name: 'Sandbox Info', hasCustomHeader: true, }; -const FILES = { +const FILES: INavigationItem = { id: 'files', name: 'Explorer', hasCustomHeader: true, defaultOpen: true, }; -const GITHUB = { +const GITHUB: INavigationItem = { id: 'github', name: 'GitHub', + showAsDisabledIfHidden: true, }; -const DEPLOYMENT = { +const DEPLOYMENT: INavigationItem = { id: 'deploy', name: 'Deployment', + showAsDisabledIfHidden: true, }; -const CONFIGURATION = { +const CONFIGURATION: INavigationItem = { id: 'config', name: 'Configuration Files', }; -const LIVE = { +const LIVE: INavigationItem = { id: 'live', name: 'Live', + showAsDisabledIfHidden: true, }; -const MORE = { - id: 'more', - name: 'More', -}; - -const SERVER = { +const SERVER: INavigationItem = { id: 'server', name: 'Server Control Panel', }; -export default function getItems(store) { +export function getDisabledItems(store: any): INavigationItem[] { + const { currentSandbox } = store.editor; + + if (!currentSandbox.owned || !store.isLoggedIn) { + return [GITHUB, DEPLOYMENT, LIVE]; + } + + return []; +} + +export default function getItems(store: any): INavigationItem[] { if ( store.live.isLive && !( @@ -70,11 +90,15 @@ export default function getItems(store) { const { currentSandbox } = store.editor; if (!currentSandbox.owned) { - return [PROJECT_SUMMARY, CONFIGURATION, MORE]; + return [PROJECT_SUMMARY, CONFIGURATION]; } const isCustomTemplate = !!currentSandbox.customTemplate; - const items = [isCustomTemplate ? PROJECT_TEMPLATE : PROJECT, FILES]; + const items = [ + isCustomTemplate ? PROJECT_TEMPLATE : PROJECT, + FILES, + CONFIGURATION, + ]; if (store.isLoggedIn && currentSandbox) { const templateDef = getTemplate(currentSandbox.template); @@ -91,15 +115,9 @@ export default function getItems(store) { items.push(DEPLOYMENT); } - items.push(CONFIGURATION); - if (store.isLoggedIn) { items.push(LIVE); } - if (!store.isLoggedIn) { - items.push(MORE); - } - return items; } diff --git a/packages/common/src/components/Tooltip.tsx b/packages/common/src/components/Tooltip.tsx index 43335330367..6f29a2323d9 100644 --- a/packages/common/src/components/Tooltip.tsx +++ b/packages/common/src/components/Tooltip.tsx @@ -25,6 +25,7 @@ const Tooltip = ({ children, style = {}, content, ...props }) => (