diff --git a/packages/app/src/app/hooks/index.ts b/packages/app/src/app/hooks/index.ts index 556422fbf84..2e88ceda96b 100644 --- a/packages/app/src/app/hooks/index.ts +++ b/packages/app/src/app/hooks/index.ts @@ -1 +1,2 @@ +export { useInterval } from './useInterval'; export { useScript } from './useScript'; diff --git a/packages/app/src/app/hooks/useInterval.ts b/packages/app/src/app/hooks/useInterval.ts new file mode 100644 index 00000000000..0676f68ec66 --- /dev/null +++ b/packages/app/src/app/hooks/useInterval.ts @@ -0,0 +1,18 @@ +// Based on https://overreacted.io/making-setinterval-declarative-with-react-hooks/ +import { useEffect, useRef } from 'react'; + +const noop = () => undefined; +export const useInterval = (callback: () => void = noop, delay: number) => { + const savedCallback = useRef(null); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + const id = + delay !== null ? setInterval(savedCallback.current, delay) : null; + + return () => clearInterval(id); + }, [delay]); +}; 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 99987e33db3..4c73c7b35f1 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/index.tsx @@ -14,7 +14,7 @@ import ConfigurationFiles from './items/ConfigurationFiles'; import { Deployment } from './items/Deployment'; import Files from './items/Files'; import { GitHub } from './items/GitHub'; -import Live from './items/Live'; +import { Live } from './items/Live'; import { More } from './items/More'; import { NotOwnedSandboxInfo } from './items/NotOwnedSandboxInfo'; import { ProjectInfo } from './items/ProjectInfo'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Countdown.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Countdown.js deleted file mode 100644 index 443c342be07..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Countdown.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; - -const pad = t => { - if (`${t}`.length === 1) { - return `0${t}`; - } - - return `${t}`; -}; - -export default class Countdown extends React.PureComponent { - componentDidMount() { - this.timer = setTimeout(this.tick, 1000); - } - - tick = () => { - this.forceUpdate(); - - this.timer = setTimeout(this.tick, 1000); - }; - - componentWillUnmount() { - clearTimeout(this.timer); - } - - getTimes = () => { - const delta = Date.now() - this.props.time; - - const hours = Math.floor(delta / 1000 / 60 / 60); - const minutes = Math.floor((delta - hours * 1000 * 60 * 60) / 1000 / 60); - const seconds = Math.floor( - (delta - hours * 1000 * 60 * 60 - minutes * 1000 * 60) / 1000 - ); - - return { hours: pad(hours), minutes: pad(minutes), seconds: pad(seconds) }; - }; - - render() { - const { hours, minutes, seconds } = this.getTimes(); - - return ( -
- {hours > 0 && `${hours}:`} - {minutes}:{seconds} -
- ); - } -} diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Live.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Live.tsx new file mode 100644 index 00000000000..afe4de06482 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Live.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; + +import { useSignals, useStore } from 'app/store'; + +import { + ErrorDescription, + WorkspaceInputContainer, + WorkspaceSubtitle, +} from '../../elements'; + +import { More } from '../More'; + +import { Description } from './elements'; +import { LiveButton } from './LiveButton'; +import { LiveInfo } from './LiveInfo'; + +export const Live = observer(() => { + const { + live: { createLiveClicked }, + } = useSignals(); + const { + editor: { currentId, currentSandbox, isAllModulesSynced }, + isLoggedIn, + live: { isLive, isLoading }, + } = useStore(); + + const showPlaceHolder = !(isLoggedIn && (isLive || currentSandbox.owned)); + if (showPlaceHolder) { + const message = 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 ; + } + + const hasUnsyncedModules = !isAllModulesSynced; + return ( +
+ {isLive ? ( + + ) : ( + <> + + {`Invite others to live edit this sandbox with you. We're doing it live!`} + + + Create live room + + + To invite others you need to generate a URL that others can join. + + + {hasUnsyncedModules && ( + + Save all your files before going live + + )} + + + { + createLiveClicked({ sandboxId: currentId }); + }} + /> + + + )} +
+ ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton.js deleted file mode 100644 index e769c1a57ba..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton.js +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import styled, { css } from 'styled-components'; - -import RecordIcon from 'react-icons/lib/md/fiber-manual-record'; - -const styles = css` - display: flex; - align-items: center; - justify-content: center; - - outline: none; - border: none; - padding: 0.5rem; - - background-color: #fd2439b8; - - width: 100%; - color: white; - border-radius: 4px; - font-weight: 800; - - border: 2px solid #fd2439b8; -`; - -const Button = styled.button` - transition: 0.3s ease all; - ${styles}; - cursor: pointer; - - svg { - margin-right: 0.25rem; - } - - ${props => - props.disable - ? css` - pointer-events: none; - background-color: rgba(0, 0, 0, 0.3); - border-color: rgba(0, 0, 0, 0.2); - color: rgba(255, 255, 255, 0.7); - ` - : css` - &:hover { - background-color: #fd2439fa; - } - `}; -`; - -const LoadingDiv = styled.div` - ${styles}; -`; - -const AnimatedRecordIcon = styled(RecordIcon)` - transition: 0.3s ease opacity; -`; - -export default class LiveButton extends React.PureComponent { - state = { - hovering: false, - showIcon: true, - }; - - timer: ?number; - - componentDidUpdate() { - if (this.state.hovering && !this.timer) { - this.timer = setInterval(() => { - this.setState({ showIcon: !this.state.showIcon }); - }, 1000); - } else if (!this.state.hovering && this.timer) { - clearInterval(this.timer); - this.timer = null; - - // eslint-disable-next-line - this.setState({ showIcon: true }); - } - } - - componentWillUnmount() { - clearInterval(this.timer); - } - - startHovering = () => { - this.setState({ hovering: true }); - }; - - stopHovering = () => { - this.setState({ hovering: false }); - }; - - render() { - const { - onClick, - isLoading, - disable, - showIcon = true, - message = 'Go Live', - } = this.props; - - if (isLoading) { - return Creating Session; - } - - return ( - - ); - } -} diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/LiveButton.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/LiveButton.tsx new file mode 100644 index 00000000000..6c153164941 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/LiveButton.tsx @@ -0,0 +1,47 @@ +import noop from 'lodash/noop'; +import React, { useState } from 'react'; + +import { useInterval } from 'app/hooks'; + +import { AnimatedRecordIcon, Button, LoadingDiv } from './elements'; + +export const LiveButton = ({ + disable = false, + icon = true, + isLoading = false, + message = 'Go Live', + onClick = noop, +}) => { + const [hovering, setHovering] = useState(false); + const [showIcon, setShowIcon] = useState(icon); + + useInterval( + () => { + if (hovering) { + setShowIcon(!showIcon); + } + }, + hovering ? 1000 : null + ); + + if (!hovering && !showIcon) { + setShowIcon(true); + } + + if (isLoading) { + return Creating Session; + } + + return ( + + ); +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/elements.ts new file mode 100644 index 00000000000..a4509de93bf --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/elements.ts @@ -0,0 +1,56 @@ +import styled, { css } from 'styled-components'; +import { HTMLAttributes } from 'react'; +import RecordIcon from 'react-icons/lib/md/fiber-manual-record'; + +const styles = css` + display: flex; + align-items: center; + justify-content: center; + outline: none; + padding: 0.5rem; + background-color: #fd2439b8; + width: 100%; + color: white; + border-radius: 4px; + font-weight: 800; + border: 2px solid #fd2439b8; +`; + +interface ButtonProps extends HTMLAttributes { + disable?: boolean; +} +export const Button = styled.button` + ${({ disable }) => css` + transition: 0.3s ease all; + ${styles}; + cursor: pointer; + + svg { + margin-right: 0.25rem; + } + + ${disable + ? css` + pointer-events: none; + background-color: rgba(0, 0, 0, 0.3); + border-color: rgba(0, 0, 0, 0.2); + color: rgba(255, 255, 255, 0.7); + ` + : css` + &:hover { + background-color: #fd2439fa; + } + `}; + `} +`; + +export const LoadingDiv = styled.div` + ${styles}; +`; + +export const AnimatedRecordIcon = styled(RecordIcon)` + ${({ opacity = 1 }) => css` + opacity: ${opacity}; + transition: 0.3s ease opacity; + `} +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/index.ts new file mode 100644 index 00000000000..ff524c90831 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/index.ts @@ -0,0 +1 @@ +export { LiveButton } from './LiveButton'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo.js deleted file mode 100644 index 12d2d7c0ba1..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo.js +++ /dev/null @@ -1,459 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { inject, observer } from 'mobx-react'; -import { sortBy } from 'lodash-es'; - -import RecordIcon from 'react-icons/lib/md/fiber-manual-record'; -import Input from '@codesandbox/common/lib/components/Input'; -import Margin from '@codesandbox/common/lib/components/spacing/Margin'; -import delay from '@codesandbox/common/lib/utils/animation/delay-effect'; -import Switch from '@codesandbox/common/lib/components/Switch'; - -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; - -import AddIcon from 'react-icons/lib/md/add'; -import RemoveIcon from 'react-icons/lib/md/remove'; -import FollowIcon from 'react-icons/lib/io/eye'; -import UnFollowIcon from 'react-icons/lib/io/eye-disabled'; - -import User from './User'; -import Countdown from './Countdown'; -import LiveButton from './LiveButton'; - -import { Description, WorkspaceInputContainer } from '../../elements'; - -const Container = styled.div` - ${delay()}; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)'}; - box-sizing: border-box; -`; - -const Title = styled.div` - color: #fd2439fa; - font-weight: 800; - display: flex; - align-items: center; - vertical-align: middle; - - padding: 0.5rem 1rem; - padding-top: 0; - - svg { - margin-right: 0.25rem; - } -`; - -const StyledInput = styled(Input)` - width: calc(100% - 1.5rem); - margin: 0 0.75rem; - font-size: 0.875rem; -`; - -const SubTitle = styled.div` - text-transform: uppercase; - font-weight: 700; - color: rgba(255, 255, 255, 0.5); - - padding-left: 1rem; - font-size: 0.875rem; -`; - -const Users = styled.div` - padding: 0.25rem 1rem; - padding-top: 0; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; -`; - -const ModeSelect = styled.div` - position: relative; - margin: 0.5rem 1rem; -`; - -const Mode = styled.button` - display: block; - text-align: left; - transition: 0.3s ease opacity; - padding: 0.5rem 1rem; - color: white; - border-radius: 4px; - width: 100%; - font-size: 1rem; - - font-weight: 600; - border: none; - outline: none; - background-color: transparent; - cursor: ${props => (props.onClick ? 'pointer' : 'inherit')}; - color: white; - opacity: ${props => (props.selected ? 1 : 0.6)}; - margin: 0.25rem 0; - - z-index: 3; - - ${props => - props.onClick && - ` - &:hover { - opacity: 1; - }`}; -`; - -const ModeDetails = styled.div` - font-size: 0.75rem; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)'}; - margin-top: 0.25rem; -`; - -const ModeSelector = styled.div` - transition: 0.3s ease transform; - position: absolute; - left: 0; - right: 0; - top: 0; - height: 48px; - - border: 2px solid rgba(253, 36, 57, 0.6); - background-color: rgba(253, 36, 57, 0.6); - border-radius: 4px; - z-index: -1; - - transform: translateY(${props => props.i * 55}px); -`; - -const PreferencesContainer = styled.div` - margin: 1rem; - display: flex; -`; - -const Preference = styled.div` - flex: 1; - font-weight: 400; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; - align-items: center; - justify-content: center; - font-size: 0.875rem; -`; - -const IconContainer = styled.div` - transition: 0.3s ease color; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; - cursor: pointer; - - &:hover { - color: white; - } -`; - -class LiveInfo extends React.Component { - select = e => { - e.target.select(); - }; - - render() { - const { - roomInfo, - isOwner, - isTeam, - ownerIds, - setMode, - addEditor, - removeEditor, - currentUserId, - reconnecting, - onSessionCloseClicked, - notificationsHidden, - toggleNotificationsHidden, - chatEnabled, - toggleChatEnabled, - setFollowing, - followingUserId, - } = this.props; - - const owners = roomInfo.users.filter(u => ownerIds.indexOf(u.id) > -1); - - const editors = sortBy( - roomInfo.users.filter( - u => - roomInfo.editorIds.indexOf(u.id) > -1 && ownerIds.indexOf(u.id) === -1 - ), - 'username' - ); - const otherUsers = sortBy( - roomInfo.users.filter( - u => - ownerIds.indexOf(u.id) === -1 && - roomInfo.editorIds.indexOf(u.id) === -1 - ), - 'username' - ); - - const liveMessage = (() => { - if (isTeam) { - return 'Your team is live!'; - } - - if (isOwner) { - return "You've gone live!"; - } - - return 'You are live!'; - })(); - - return ( - - - <div - style={{ - fontSize: '1rem', - flex: 1, - display: 'flex', - alignItems: 'center', - }} - > - {reconnecting ? ( - 'Reconnecting...' - ) : ( - <React.Fragment> - <RecordIcon /> {liveMessage} - </React.Fragment> - )} - </div> - <div> - {roomInfo.startTime != null && ( - <Countdown time={roomInfo.startTime} /> - )} - </div> - - - Share this link with others to invite them to the session: - - - - {isOwner && !isTeam && ( - - - - )} - - - Preferences - - {isOwner && ( - - Chat enabled - - - )} - - Hide notifications - - - - - - Live Mode - - - setMode({ mode: 'open' }) : undefined} - selected={roomInfo.mode === 'open'} - > -
Open
- Everyone can edit -
- setMode({ mode: 'classroom' }) : undefined - } - selected={roomInfo.mode === 'classroom'} - > -
Classroom
- Take control over who can edit -
-
-
- - {owners && ( - - Owners - - {owners.map(owner => ( - - {followingUserId === owner.id ? ( - - setFollowing({ liveUserId: null })} - /> - - ) : ( - - - setFollowing({ liveUserId: owner.id }) - } - /> - - )} - - ) - } - /> - ))} - - - )} - - {editors.length > 0 && roomInfo.mode === 'classroom' && ( - - Editors - - {editors.map(user => ( - - {user.id !== currentUserId && ( - - {followingUserId === user.id ? ( - - - setFollowing({ liveUserId: null }) - } - /> - - ) : ( - - - setFollowing({ liveUserId: user.id }) - } - /> - - )} - - )} - {isOwner && roomInfo.mode === 'classroom' && ( - - - - removeEditor({ liveUserId: user.id }) - } - /> - - - )} - - } - /> - ))} - - - )} - - - Users - - - {otherUsers.length ? ( - otherUsers.map(user => ( - - {roomInfo.mode !== 'classroom' && - user.id !== currentUserId && ( - - {followingUserId === user.id ? ( - - - setFollowing({ liveUserId: null }) - } - /> - - ) : ( - - - setFollowing({ liveUserId: user.id }) - } - /> - - )} - - )} - {isOwner && roomInfo.mode === 'classroom' && ( - - - addEditor({ liveUserId: user.id })} - /> - - - )} - - } - /> - )) - ) : ( -
- No other users in session, invite them! -
- )} -
-
-
- ); - } -} - -export default inject('store')(observer(LiveInfo)); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveInfo.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveInfo.tsx new file mode 100644 index 00000000000..81977bde6d4 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveInfo.tsx @@ -0,0 +1,86 @@ +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import RecordIcon from 'react-icons/lib/md/fiber-manual-record'; + +import { useSignals, useStore } from 'app/store'; + +import { Description, WorkspaceInputContainer } from '../../../elements'; + +import { LiveButton } from '../LiveButton'; + +import { ConnectionStatus, Container, StyledInput, Title } from './elements'; +import { LiveMode } from './LiveMode'; +import { Preferences } from './Preferences'; +import { SessionTimer } from './SessionTimer'; +import { Users } from './Users'; + +export const LiveInfo = observer(() => { + const { + live: { onSessionCloseClicked }, + } = useSignals(); + const { + live: { + isOwner, + isTeam, + reconnecting, + roomInfo: { roomId, startTime }, + }, + } = useStore(); + + const liveMessage = (() => { + if (isTeam) { + return 'Your team is live!'; + } + + if (isOwner) { + return "You've gone live!"; + } + + return 'You are live!'; + })(); + + return ( + + + <ConnectionStatus> + {reconnecting ? ( + 'Reconnecting...' + ) : ( + <> + <RecordIcon /> {liveMessage} + </> + )} + </ConnectionStatus> + + <div> + {startTime !== null && <SessionTimer startTime={startTime} />} + </div> + + + + Share this link with others to invite them to the session: + + + e.target.select()} + value={`https://codesandbox.io/live/${roomId}`} + /> + + {isOwner && !isTeam && ( + + + + )} + + + + + + + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/LiveMode.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/LiveMode.tsx new file mode 100644 index 00000000000..1473a79c9dc --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/LiveMode.tsx @@ -0,0 +1,50 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import noop from 'lodash/noop'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import { useSignals, useStore } from 'app/store'; + +import { SubTitle } from '../elements'; + +import { Mode, ModeDetails, ModeSelect, ModeSelector } from './elements'; + +export const LiveMode = observer(() => { + const { + live: { onModeChanged }, + } = useSignals(); + const { + live: { + isOwner, + roomInfo: { mode }, + }, + } = useStore(); + + return ( + + Live Mode + + + + + onModeChanged({ mode: 'open' }) : noop} + selected={mode === 'open'} + > +
Open
+ + Everyone can edit +
+ + onModeChanged({ mode: 'classroom' }) : noop} + selected={mode === 'classroom'} + > +
Classroom
+ + Take control over who can edit +
+
+
+ ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/elements.ts new file mode 100644 index 00000000000..f89b618e33b --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/elements.ts @@ -0,0 +1,66 @@ +import styled, { css } from 'styled-components'; + +import { Theme } from '../types'; + +export const Mode = styled.button<{ onClick: () => void; selected: boolean }>` + ${({ onClick, selected }) => css` + display: block; + text-align: left; + transition: 0.3s ease opacity; + padding: 0.5rem 1rem; + color: white; + border-radius: 4px; + width: 100%; + font-size: 1rem; + + font-weight: 600; + border: none; + outline: none; + background-color: transparent; + cursor: ${onClick ? 'pointer' : 'inherit'}; + opacity: ${selected ? 1 : 0.6}; + margin: 0.25rem 0; + + z-index: 3; + + ${onClick && + css` + &:hover { + opacity: 1; + } + `}; + `} +`; + +export const ModeDetails = styled.div` + ${({ theme }: Theme) => css` + color: ${theme.light + ? css`rgba(0, 0, 0, 0.7)` + : css`rgba(255, 255, 255, 0.7)`}; + font-size: 0.75rem; + margin-top: 0.25rem; + `} +`; + +export const ModeSelect = styled.div` + margin: 0.5rem 1rem; + position: relative; +`; + +export const ModeSelector = styled.div<{ i: number }>` + ${({ i }) => css` + transition: 0.3s ease transform; + position: absolute; + left: 0; + right: 0; + top: 0; + height: 48px; + + border: 2px solid rgba(253, 36, 57, 0.6); + background-color: rgba(253, 36, 57, 0.6); + border-radius: 4px; + z-index: -1; + + transform: translateY(${i * 55}px); + `} +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/index.ts new file mode 100644 index 00000000000..60c33ed3ab6 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/index.ts @@ -0,0 +1 @@ +export { LiveMode } from './LiveMode'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/ChatEnabled.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/ChatEnabled.tsx new file mode 100644 index 00000000000..7e82bcdb9be --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/ChatEnabled.tsx @@ -0,0 +1,35 @@ +import Switch from '@codesandbox/common/lib/components/Switch'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import { useSignals, useStore } from 'app/store'; + +import { Preference, PreferencesContainer } from './elements'; + +export const ChatEnabled = observer(() => { + const { + live: { onChatEnabledChange }, + } = useSignals(); + const { + live: { + roomInfo: { chatEnabled }, + }, + } = useStore(); + const toggleChatEnabled = () => { + onChatEnabledChange({ enabled: !chatEnabled }); + }; + + return ( + + Chat enabled + + + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/HideNotifications.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/HideNotifications.tsx new file mode 100644 index 00000000000..782e2cb7d18 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/HideNotifications.tsx @@ -0,0 +1,30 @@ +import Switch from '@codesandbox/common/lib/components/Switch'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import { useSignals, useStore } from 'app/store'; + +import { Preference, PreferencesContainer } from './elements'; + +export const HideNotifications = observer(() => { + const { + live: { onToggleNotificationsHidden }, + } = useSignals(); + const { + live: { notificationsHidden }, + } = useStore(); + + return ( + + Hide notifications + + + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/Preferences.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/Preferences.tsx new file mode 100644 index 00000000000..efb8f63278b --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/Preferences.tsx @@ -0,0 +1,26 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import { useStore } from 'app/store'; + +import { SubTitle } from '../elements'; + +import { ChatEnabled } from './ChatEnabled'; +import { HideNotifications } from './HideNotifications'; + +export const Preferences = observer(() => { + const { + live: { isOwner }, + } = useStore(); + + return ( + + Preferences + + {isOwner && } + + + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/elements.ts new file mode 100644 index 00000000000..c2a5b2d8452 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/elements.ts @@ -0,0 +1,21 @@ +import styled, { css } from 'styled-components'; + +import { Theme } from '../types'; + +export const Preference = styled.div` + ${({ theme }: Theme) => css` + align-items: center; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + flex: 1; + font-size: 0.875rem; + font-weight: 400; + justify-content: center; + `} +`; + +export const PreferencesContainer = styled.div` + display: flex; + margin: 1rem; +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/index.ts new file mode 100644 index 00000000000..e7f050692ca --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/index.ts @@ -0,0 +1 @@ +export { Preferences } from './Preferences'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/SessionTimer.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/SessionTimer.tsx new file mode 100644 index 00000000000..c28ba173da6 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/SessionTimer.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; + +import { useInterval } from 'app/hooks'; + +export const SessionTimer = ({ startTime }) => { + const [elapsed, setElapsed] = useState(`00:00`); + + const pad = (val: number) => (`${val}`.length === 1 ? `0${val}` : `${val}`); + + const getTimes = () => { + const delta = Date.now() - startTime; + + const hours = Math.floor(delta / 1000 / 60 / 60); + const minutes = Math.floor((delta - hours * 1000 * 60 * 60) / 1000 / 60); + const seconds = Math.floor( + (delta - hours * 1000 * 60 * 60 - minutes * 1000 * 60) / 1000 + ); + + return { hours: pad(hours), minutes: pad(minutes), seconds: pad(seconds) }; + }; + + const { hours, minutes, seconds } = getTimes(); + + useInterval(() => { + setElapsed(`${Number(hours) > 0 ? `${hours}:` : ``}${minutes}:${seconds}`); + }, 1000); + + // TODO: just return 'elapsed' after this one's fixed + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20544 + return {elapsed}; +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/Editors.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/Editors.tsx new file mode 100644 index 00000000000..55ca36fb7cb --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/Editors.tsx @@ -0,0 +1,26 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import React from 'react'; + +import { SubTitle } from '../../elements'; + +import { Users } from '../elements'; +import { User } from '../User'; + +import { SideView } from './SideView'; + +export const Editors = ({ editors }) => ( + + Editors + + + {editors.map(user => ( + } + type="Editor" + user={user} + /> + ))} + + +); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/SideView.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/SideView.tsx new file mode 100644 index 00000000000..8b11d029922 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/SideView.tsx @@ -0,0 +1,56 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import FollowIcon from 'react-icons/lib/io/eye'; +import UnFollowIcon from 'react-icons/lib/io/eye-disabled'; +import RemoveIcon from 'react-icons/lib/md/remove'; + +import { useSignals, useStore } from 'app/store'; + +import { IconContainer } from '../elements'; +import { User } from '../types'; + +type Props = { + userId: User['id']; +}; +export const SideView = observer(({ userId }) => { + const { + live: { onFollow, onRemoveEditorClicked }, + } = useSignals(); + const { + live: { + followingUserId, + isOwner, + liveUserId, + roomInfo: { mode }, + }, + } = useStore(); + + return ( + <> + {userId !== liveUserId && ( + + {followingUserId === userId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: userId })} /> + + )} + + )} + + {isOwner && mode === 'classroom' && ( + + + onRemoveEditorClicked({ liveUserId: userId })} + /> + + + )} + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/index.ts new file mode 100644 index 00000000000..3bdcb3ffc65 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/index.ts @@ -0,0 +1 @@ +export { Editors } from './Editors'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/OtherUsers.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/OtherUsers.tsx new file mode 100644 index 00000000000..50ddbcd625f --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/OtherUsers.tsx @@ -0,0 +1,30 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import React from 'react'; + +import { SubTitle } from '../../elements'; + +import { NoUsers, Users } from '../elements'; +import { User } from '../User'; + +import { SideView } from './SideView'; + +export const OtherUsers = ({ otherUsers }) => ( + + Users + + + {otherUsers.length ? ( + otherUsers.map(user => ( + } + type="Spectator" + user={user} + /> + )) + ) : ( + No other users in session, invite them! + )} + + +); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/SideView.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/SideView.tsx new file mode 100644 index 00000000000..b1db610858c --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/SideView.tsx @@ -0,0 +1,56 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import FollowIcon from 'react-icons/lib/io/eye'; +import UnFollowIcon from 'react-icons/lib/io/eye-disabled'; +import AddIcon from 'react-icons/lib/md/add'; + +import { useSignals, useStore } from 'app/store'; + +import { IconContainer } from '../elements'; +import { User } from '../types'; + +type Props = { + userId: User['id']; +}; +export const SideView = observer(({ userId }) => { + const { + live: { onAddEditorClicked, onFollow }, + } = useSignals(); + const { + live: { + followingUserId, + isOwner, + liveUserId, + roomInfo: { mode }, + }, + } = useStore(); + + return ( + <> + {mode !== 'classroom' && userId !== liveUserId && ( + + {followingUserId === userId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: userId })} /> + + )} + + )} + + {isOwner && mode === 'classroom' && ( + + + onAddEditorClicked({ liveUserId: userId })} + /> + + + )} + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/index.ts new file mode 100644 index 00000000000..03a50a81fd4 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/index.ts @@ -0,0 +1 @@ +export { OtherUsers } from './OtherUsers'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/Owners.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/Owners.tsx new file mode 100644 index 00000000000..88c22cdae27 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/Owners.tsx @@ -0,0 +1,26 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import React from 'react'; + +import { SubTitle } from '../../elements'; + +import { Users } from '../elements'; +import { User } from '../User'; + +import { SideView } from './SideView'; + +export const Owners = ({ owners }) => ( + + Owners + + + {owners.map(owner => ( + } + type="Owner" + user={owner} + /> + ))} + + +); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/SideView.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/SideView.tsx new file mode 100644 index 00000000000..14713aa0998 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/SideView.tsx @@ -0,0 +1,38 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; +import FollowIcon from 'react-icons/lib/io/eye'; +import UnFollowIcon from 'react-icons/lib/io/eye-disabled'; + +import { useSignals, useStore } from 'app/store'; + +import { IconContainer } from '../elements'; +import { User } from '../types'; + +type Props = { + userId: User['id']; +}; +export const SideView = observer(({ userId }) => { + const { + live: { onFollow }, + } = useSignals(); + const { + live: { followingUserId, liveUserId }, + } = useStore(); + + return ( + userId !== liveUserId && ( + + {followingUserId === userId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: userId })} /> + + )} + + ) + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/index.ts new file mode 100644 index 00000000000..8f9a448fa66 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/index.ts @@ -0,0 +1 @@ +export { Owners } from './Owners'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/User.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/User.tsx new file mode 100644 index 00000000000..67879ca9420 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/User.tsx @@ -0,0 +1,50 @@ +import { observer } from 'mobx-react-lite'; +import React, { ReactNode } from 'react'; + +import { useStore } from 'app/store'; + +import { User as UserType } from '../types'; + +import { UserContainer, ProfileImage, UserName, Status } from './elements'; + +type Props = { + sideView?: ReactNode; + type: string; + user: UserType; +}; +export const User = observer(({ sideView = null, type, user }) => { + const { + live: { + liveUserId, + roomInfo: { users }, + }, + } = useStore(); + + const metaData = users.find(({ id }) => id === user.id); + const [r, g, b] = metaData ? metaData.color : [0, 0, 0]; + const isCurrentUser = user.id === liveUserId; + + return ( + + + +
+ {user.username} + + {type && ( + + {type} + + {isCurrentUser && ' (you)'} + + )} +
+ + {sideView} +
+ ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/elements.ts new file mode 100644 index 00000000000..de9ab57fc78 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/elements.ts @@ -0,0 +1,43 @@ +import styled, { css } from 'styled-components'; +import delay from '@codesandbox/common/lib/utils/animation/delay-effect'; + +export const Status = styled.div` + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.6); +`; + +export const UserContainer = styled.div<{ isCurrentUser: boolean }>` + ${({ isCurrentUser, theme }) => css` + ${delay()}; + display: flex; + align-items: center; + margin: 0.5rem 0; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + ${isCurrentUser && + css` + color: white; + `}; + + &:first-child { + margin-top: 0; + } + `} +`; + +export const ProfileImage = styled.img<{ borderColor: string }>` + ${({ borderColor }) => css` + width: 26px; + height: 26px; + border-radius: 2px; + border-left: 2px solid ${borderColor}; + + margin-right: 0.5rem; + `} +`; + +export const UserName = styled.div` + font-weight: 600; + font-size: 0.875rem; +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/index.ts new file mode 100644 index 00000000000..403fc945c19 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/index.ts @@ -0,0 +1 @@ +export { User } from './User'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Users.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Users.tsx new file mode 100644 index 00000000000..35bba699398 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Users.tsx @@ -0,0 +1,39 @@ +import { sortBy } from 'lodash-es'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +import { useStore } from 'app/store'; + +import { Editors } from './Editors'; +import { OtherUsers } from './OtherUsers'; +import { Owners } from './Owners'; + +export const Users = observer(() => { + const { + live: { + roomInfo: { editorIds, mode, ownerIds, users }, + }, + } = useStore(); + + const owners = users.filter(({ id }) => ownerIds.includes(id)); + const editors = sortBy( + users.filter(({ id }) => editorIds.includes(id) && !ownerIds.includes(id)), + 'username' + ); + const otherUsers = sortBy( + users.filter(({ id }) => !ownerIds.includes(id) && !editorIds.includes(id)), + 'username' + ); + + return ( + <> + {owners && } + + {editors.length > 0 && mode === 'classroom' && ( + + )} + + + + ); +}); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/elements.ts new file mode 100644 index 00000000000..fe99ff38763 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/elements.ts @@ -0,0 +1,33 @@ +import styled, { css } from 'styled-components'; + +import { Theme } from '../types'; + +export const IconContainer = styled.div` + ${({ theme }: Theme) => css` + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + cursor: pointer; + transition: 0.3s ease color; + + &:hover { + color: white; + } + `} +`; + +export const NoUsers = styled.div` + color: rgba(255, 255, 255, 0.8); + font-size: 0.875rem; + font-weight: 600; + margin-top: 0.25rem; +`; + +export const Users = styled.div` + ${({ theme }: Theme) => css` + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + padding: 0 1rem 0.25rem; + `} +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/index.ts new file mode 100644 index 00000000000..a87a5f5b1f1 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/index.ts @@ -0,0 +1 @@ +export { Users } from './Users'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/types.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/types.ts new file mode 100644 index 00000000000..d368afd1870 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/types.ts @@ -0,0 +1,5 @@ +export type User = { + avatarUrl: string; + id: number; + username: string; +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts new file mode 100644 index 00000000000..0e00aab4c4d --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts @@ -0,0 +1,49 @@ +import Input from '@codesandbox/common/lib/components/Input'; +import delay from '@codesandbox/common/lib/utils/animation/delay-effect'; +import styled, { css } from 'styled-components'; + +import { Theme } from './types'; + +export const Container = styled.div` + ${({ theme }: Theme) => css` + ${delay()}; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.7)` + : css`rgba(255, 255, 255, 0.7)`}; + box-sizing: border-box; + `} +`; + +export const Title = styled.div` + color: #fd2439fa; + font-weight: 800; + display: flex; + align-items: center; + vertical-align: middle; + padding: 0 1rem 0.5rem; + + svg { + margin-right: 0.25rem; + } +`; + +export const ConnectionStatus = styled.div` + display: flex; + flex: 1; + align-items: center; + font-size: 1rem; +`; + +export const StyledInput = styled(Input)` + width: calc(100% - 1.5rem); + margin: 0 0.75rem; + font-size: 0.875rem; +`; + +export const SubTitle = styled.div` + text-transform: uppercase; + font-weight: 700; + color: rgba(255, 255, 255, 0.5); + padding-left: 1rem; + font-size: 0.875rem; +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/index.ts new file mode 100644 index 00000000000..4903353342c --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/index.ts @@ -0,0 +1 @@ +export { LiveInfo } from './LiveInfo'; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/types.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/types.ts new file mode 100644 index 00000000000..904dc7c650d --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/types.ts @@ -0,0 +1,5 @@ +export type Theme = { + theme: { + light: boolean; + }; +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/User.js b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/User.js deleted file mode 100644 index 2e20de7eedb..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/User.js +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { observer } from 'mobx-react'; - -import delay from '@codesandbox/common/lib/utils/animation/delay-effect'; - -const Status = styled.div` - font-size: 0.75rem; - color: rgba(255, 255, 255, 0.6); -`; - -const UserContainer = styled.div` - ${delay()}; - display: flex; - align-items: center; - margin: 0.5rem 0; - color: ${props => - props.theme.light ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; - ${props => - props.isCurrentUser && - ` - color: white; - `}; - - &:first-child { - margin-top: 0; - } -`; - -const ProfileImage = styled.img` - width: 26px; - height: 26px; - border-radius: 2px; - border-left: 2px solid ${({ borderColor }) => borderColor}; - - margin-right: 0.5rem; -`; - -const UserName = styled.div` - font-weight: 600; - font-size: 0.875rem; -`; - -// eslint-disable-next-line -class User extends React.Component { - render() { - const { user, type, sideView, roomInfo, currentUserId } = this.props; - - const metaData = roomInfo.users.find(u => u.id === user.id); - const [r, g, b] = metaData ? metaData.color : [0, 0, 0]; - - const isCurrentUser = user.id === currentUserId; - - return ( - - -
- {user.username} - {type && ( - - {type} - {isCurrentUser && ' (you)'} - - )} -
- {sideView} -
- ); - } -} - -export default observer(User); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/elements.ts new file mode 100644 index 00000000000..e1fa9a2ca0a --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/elements.ts @@ -0,0 +1,7 @@ +import styled from 'styled-components'; + +import { Description as DescriptionBase } from '../../elements'; + +export const Description = styled(DescriptionBase)` + margin-bottom: 1rem; +`; 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 deleted file mode 100644 index 66e46d84502..00000000000 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react'; -import { inject, observer } from 'mobx-react'; - -import LiveInfo from './LiveInfo'; -import LiveButton from './LiveButton'; - -import { - Description, - WorkspaceInputContainer, - 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 ? ( - { - signals.live.onChatEnabledChange({ - enabled: !store.live.roomInfo.chatEnabled, - }); - }} - setFollowing={signals.live.onFollow} - followingUserId={store.live.followingUserId} - /> - ) : ( - - - Invite others to live edit this sandbox with you. We - {"'"} - re doing it live! - - - - Create live room - - To invite others you need to generate a URL that others can join. - - - {hasUnsyncedModules && ( - - Save all your files before going live - - )} - - { - signals.live.createLiveClicked({ - sandboxId: store.editor.currentId, - }); - }} - isLoading={store.live.isLoading} - disable={hasUnsyncedModules} - /> - - - - )} -
- ); -}; - -export default inject('signals', 'store')(observer(Live)); diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.ts new file mode 100644 index 00000000000..c5cc09c5182 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.ts @@ -0,0 +1 @@ +export { Live } from './Live';