From 955262567f88bd7d83818cce7f08ba5672dc0103 Mon Sep 17 00:00:00 2001 From: Drake Costa Date: Tue, 21 May 2019 14:05:10 -0700 Subject: [PATCH 01/11] =?UTF-8?q?=F0=9F=94=A8=20Add=20useInterval=20Hook,?= =?UTF-8?q?=20Refactor=20Live=20to=20use=20useInterval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a useInterval Hook (demo here: https://codesandbox.io/s/sessiontimer-useinterval-hook-demo-0l6jq) Refactored Live workspace item and it's dependents to use hooks. --- packages/app/src/app/hooks/index.ts | 1 + packages/app/src/app/hooks/useInterval.ts | 24 + .../pages/Sandbox/Editor/Workspace/index.tsx | 2 +- .../Editor/Workspace/items/Live/Countdown.js | 48 -- .../Editor/Workspace/items/Live/Live.tsx | 78 +++ .../Editor/Workspace/items/Live/LiveButton.js | 120 ----- .../items/Live/LiveButton/LiveButton.tsx | 49 ++ .../items/Live/LiveButton/elements.ts | 55 +++ .../Workspace/items/Live/LiveButton/index.ts | 1 + .../Editor/Workspace/items/Live/LiveInfo.js | 459 ------------------ .../items/Live/LiveInfo/LiveInfo.tsx | 338 +++++++++++++ .../items/Live/LiveInfo/SessionTimer.tsx | 29 ++ .../items/Live/LiveInfo/User/User.tsx | 33 ++ .../items/Live/LiveInfo/User/elements.ts | 43 ++ .../items/Live/LiveInfo/User/index.ts | 1 + .../Workspace/items/Live/LiveInfo/elements.ts | 166 +++++++ .../Workspace/items/Live/LiveInfo/index.ts | 1 + .../Editor/Workspace/items/Live/User.js | 76 --- .../Editor/Workspace/items/Live/index.js | 98 ---- .../Editor/Workspace/items/Live/index.ts | 1 + 20 files changed, 821 insertions(+), 802 deletions(-) create mode 100644 packages/app/src/app/hooks/useInterval.ts delete mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Countdown.js create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Live.tsx delete mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton.js create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/LiveButton.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/elements.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/index.ts delete mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo.js create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveInfo.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/SessionTimer.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/User.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/elements.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/index.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/index.ts delete mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/User.js delete mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.ts 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..fc1b7eca781 --- /dev/null +++ b/packages/app/src/app/hooks/useInterval.ts @@ -0,0 +1,24 @@ +// Based on https://overreacted.io/making-setinterval-declarative-with-react-hooks/ +import { useEffect, useRef } from 'react'; + +export const useInterval = (callback = () => {}, delay: number) => { + const savedCallback = useRef(null); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + function tick() { + savedCallback.current(); + } + + if (delay !== null) { + const id = setInterval(tick, delay); + + return () => clearInterval(id); + } + + return undefined; + }, [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..483bc9a4c0d --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Live.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { useSignals, useStore } from 'app/store'; +import { LiveInfo } from './LiveInfo'; +import { LiveButton } from './LiveButton'; +import { + Description, + ErrorDescription, + WorkspaceInputContainer, + WorkspaceSubtitle, +} from '../../elements'; + +import { More } from '../More'; + +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 }); + }} + isLoading={isLoading} + disable={hasUnsyncedModules} + /> + + + )} +
+ ); +}); 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..e65c94bcb2d --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/LiveButton.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; + +import { useInterval } from 'app/hooks'; + +import { AnimatedRecordIcon, Button, LoadingDiv } from './elements'; + +export const LiveButton = ({ + disable, + icon = true, + isLoading, + message = 'Go Live', + onClick, +}) => { + const [hovering, setHovering] = useState(false); + const [showIcon, setShowIcon] = useState(icon); + + useInterval( + () => { + if (hovering) { + setShowIcon(!showIcon); + } + }, + hovering ? 1000 : null + ); + + if (!hovering && !showIcon) { + setShowIcon(true); + } + + const startHovering = () => setHovering(true); + const stopHovering = () => setHovering(false); + + 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..cb7e68a2acc --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveButton/elements.ts @@ -0,0 +1,55 @@ +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; + padding: 0.5rem; + background-color: #fd2439b8; + width: 100%; + color: white; + border-radius: 4px; + font-weight: 800; + border: 2px solid #fd2439b8; +`; + +interface ButtonProps extends HTMLButtonElement { + 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..425627e2fc8 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveInfo.tsx @@ -0,0 +1,338 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import Switch from '@codesandbox/common/lib/components/Switch'; +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +import { observer } from 'mobx-react-lite'; +import { sortBy } from 'lodash-es'; +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 RecordIcon from 'react-icons/lib/md/fiber-manual-record'; +import RemoveIcon from 'react-icons/lib/md/remove'; + +import { useSignals, useStore } from 'app/store'; + +import { Description, WorkspaceInputContainer } from '../../../elements'; + +import { LiveButton } from '../LiveButton'; + +import { + ConnectionStatus, + Container, + IconContainer, + Mode, + ModeDetails, + ModeSelect, + ModeSelector, + NoUsers, + Preference, + PreferencesContainer, + StyledInput, + SubTitle, + Title, + Users, +} from './elements'; +import { SessionTimer } from './SessionTimer'; +import { User } from './User'; + +export const LiveInfo = observer(() => { + const { + live: { + onAddEditorClicked, + onChatEnabledChange, + onFollow, + onModeChanged, + onRemoveEditorClicked, + onSessionCloseClicked, + onToggleNotificationsHidden, + }, + } = useSignals(); + const { + live: { + followingUserId, + isOwner, + isTeam, + liveUserId, + notificationsHidden, + reconnecting, + roomInfo: { + chatEnabled, + editorIds, + mode, + ownerIds, + roomId, + startTime, + users, + }, + }, + } = useStore(); + + const toggleChatEnabled = () => { + onChatEnabledChange({ enabled: !chatEnabled }); + }; + + const owners = users.filter(u => ownerIds.indexOf(u.id) > -1); + + const editors = sortBy( + users.filter( + u => editorIds.indexOf(u.id) > -1 && ownerIds.indexOf(u.id) === -1 + ), + 'username' + ); + const otherUsers = sortBy( + users.filter( + u => ownerIds.indexOf(u.id) === -1 && 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 ( + + + <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 && ( + + + + )} + + + Preferences + + {isOwner && ( + + Chat enabled + + + + )} + + + Hide notifications + + + + + + + Live Mode + + + + + onModeChanged({ mode: 'open' }) : undefined + } + selected={mode === 'open'} + > +
Open
+ + Everyone can edit +
+ + onModeChanged({ mode: 'classroom' }) : undefined + } + selected={mode === 'classroom'} + > +
Classroom
+ + Take control over who can edit +
+
+
+ + {owners && ( + + Owners + + + {owners.map(owner => ( + + {followingUserId === owner.id ? ( + + onFollow({ liveUserId: null })} + /> + + ) : ( + + onFollow({ liveUserId: owner.id })} + /> + + )} + + ) + } + type="Owner" + user={owner} + users={users} + /> + ))} + + + )} + + {editors.length > 0 && mode === 'classroom' && ( + + Editors + + + {editors.map(user => ( + + {user.id !== liveUserId && ( + + {followingUserId === user.id ? ( + + onFollow({ liveUserId: null })} + /> + + ) : ( + + onFollow({ liveUserId: user.id })} + /> + + )} + + )} + + {isOwner && mode === 'classroom' && ( + + + + onRemoveEditorClicked({ liveUserId: user.id }) + } + /> + + + )} + + } + type="Editor" + user={user} + users={users} + /> + ))} + + + )} + + + Users + + + {otherUsers.length ? ( + otherUsers.map(user => ( + + {mode !== 'classroom' && user.id !== liveUserId && ( + + {followingUserId === user.id ? ( + + onFollow({ liveUserId: null })} + /> + + ) : ( + + onFollow({ liveUserId: user.id })} + /> + + )} + + )} + + {isOwner && mode === 'classroom' && ( + + + + onAddEditorClicked({ liveUserId: user.id }) + } + /> + + + )} + + } + type="Spectator" + user={user} + users={users} + /> + )) + ) : ( + No other users in session, invite them! + )} + + +
+ ); +}); 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..bce41a0f84a --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/SessionTimer.tsx @@ -0,0 +1,29 @@ +import { 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); + + return elapsed; +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/User.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/User.tsx new file mode 100644 index 00000000000..40beb49d425 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/User.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { UserContainer, ProfileImage, UserName, Status } from './elements'; + +export const User = ({ currentUserId, sideView, type, user, users }) => { + const metaData = 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} +
+ ); +}; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/elements.ts new file mode 100644 index 00000000000..ae944734d5a --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/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, theme }: { isCurrentUser: boolean; theme: any }) => 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 }: { borderColor: string }) => 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/User/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/index.ts new file mode 100644 index 00000000000..403fc945c19 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/index.ts @@ -0,0 +1 @@ +export { User } from './User'; 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..1d788913f46 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts @@ -0,0 +1,166 @@ +import styled, { css } from 'styled-components'; +import Input from '@codesandbox/common/lib/components/Input'; +import delay from '@codesandbox/common/lib/utils/animation/delay-effect'; + +interface Theme { + theme: { + light: boolean; + }; +} + +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.5rem 1rem; + padding-top: 0; + + 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; +`; + +export const Users = styled.div` + ${({ theme }: Theme) => css` + padding: 0.25rem 1rem; + padding-top: 0; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + `} +`; + +export const ModeSelect = styled.div` + position: relative; + margin: 0.5rem 1rem; +`; + +export const Mode = styled.button` + ${({ onClick, selected }: { onClick: any; selected: boolean }) => 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'}; + color: white; + opacity: ${selected ? 1 : 0.6}; + margin: 0.25rem 0; + + z-index: 3; + + ${onClick && + ` + &:hover { + opacity: 1; + }`}; + `} +`; + +export const ModeDetails = styled.div` + ${({ theme }: Theme) => css` + font-size: 0.75rem; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.7)` + : css`rgba(255, 255, 255, 0.7)`}; + margin-top: 0.25rem; + `} +`; + +export const ModeSelector = styled.div` + ${({ i }: { i: number }) => 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); + `} +`; + +export const PreferencesContainer = styled.div` + margin: 1rem; + display: flex; +`; + +export const Preference = styled.div` + ${({ theme }: Theme) => css` + flex: 1; + font-weight: 400; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + align-items: center; + justify-content: center; + font-size: 0.875rem; + `} +`; + +export const IconContainer = styled.div` + ${({ theme }: Theme) => css` + transition: 0.3s ease color; + color: ${theme.light + ? css`rgba(0, 0, 0, 0.8)` + : css`rgba(255, 255, 255, 0.8)`}; + cursor: pointer; + + &:hover { + color: white; + } + `} +`; + +export const NoUsers = styled.div` + margin-top: 0.25rem; + color: rgba(255, 255, 255, 0.8); + font-size: 0.875rem; + font-weight: 600; +`; 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/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/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'; From 0374d00bb2853b02420c085a091446fd45669cb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 29 May 2019 01:05:07 +0200 Subject: [PATCH 02/11] Resolve discussions --- packages/app/src/app/hooks/useInterval.ts | 16 +++++----------- .../Sandbox/Editor/Workspace/items/Live/Live.tsx | 6 ++---- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/app/src/app/hooks/useInterval.ts b/packages/app/src/app/hooks/useInterval.ts index fc1b7eca781..0676f68ec66 100644 --- a/packages/app/src/app/hooks/useInterval.ts +++ b/packages/app/src/app/hooks/useInterval.ts @@ -1,7 +1,8 @@ // Based on https://overreacted.io/making-setinterval-declarative-with-react-hooks/ import { useEffect, useRef } from 'react'; -export const useInterval = (callback = () => {}, delay: number) => { +const noop = () => undefined; +export const useInterval = (callback: () => void = noop, delay: number) => { const savedCallback = useRef(null); useEffect(() => { @@ -9,16 +10,9 @@ export const useInterval = (callback = () => {}, delay: number) => { }, [callback]); useEffect(() => { - function tick() { - savedCallback.current(); - } + const id = + delay !== null ? setInterval(savedCallback.current, delay) : null; - if (delay !== null) { - const id = setInterval(tick, delay); - - return () => clearInterval(id); - } - - return undefined; + return () => clearInterval(id); }, [delay]); }; 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 index 483bc9a4c0d..c3b25fa27ce 100644 --- 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 @@ -64,11 +64,9 @@ export const Live = observer(() => { { - createLiveClicked({ sandboxId: currentId }); - }} - isLoading={isLoading} disable={hasUnsyncedModules} + isLoading={isLoading} + onClick={() => {createLiveClicked({ sandboxId: currentId }) }} /> From 0d0120d8a75fefaa01c92f3c17a10f1d6cae53ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Tue, 25 Jun 2019 23:26:07 +0200 Subject: [PATCH 03/11] Extract Preferences --- .../items/Live/LiveInfo/LiveInfo.tsx | 51 ++----------------- .../Live/LiveInfo/Preferences/ChatEnabled.tsx | 34 +++++++++++++ .../Preferences/HideNotifications.tsx | 29 +++++++++++ .../Live/LiveInfo/Preferences/Preferences.tsx | 25 +++++++++ .../Live/LiveInfo/Preferences/elements.ts | 21 ++++++++ .../items/Live/LiveInfo/Preferences/index.ts | 1 + .../Workspace/items/Live/LiveInfo/elements.ts | 26 +--------- .../Workspace/items/Live/LiveInfo/types.ts | 5 ++ 8 files changed, 120 insertions(+), 72 deletions(-) create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/ChatEnabled.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/HideNotifications.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/Preferences.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/elements.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/index.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/types.ts 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 index 425627e2fc8..4a164cff1a5 100644 --- 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 @@ -1,5 +1,4 @@ import Margin from '@codesandbox/common/lib/components/spacing/Margin'; -import Switch from '@codesandbox/common/lib/components/Switch'; import Tooltip from '@codesandbox/common/lib/components/Tooltip'; import { observer } from 'mobx-react-lite'; import { sortBy } from 'lodash-es'; @@ -25,13 +24,12 @@ import { ModeSelect, ModeSelector, NoUsers, - Preference, - PreferencesContainer, StyledInput, SubTitle, Title, Users, } from './elements'; +import { Preferences } from './Preferences'; import { SessionTimer } from './SessionTimer'; import { User } from './User'; @@ -39,12 +37,10 @@ export const LiveInfo = observer(() => { const { live: { onAddEditorClicked, - onChatEnabledChange, onFollow, onModeChanged, onRemoveEditorClicked, onSessionCloseClicked, - onToggleNotificationsHidden, }, } = useSignals(); const { @@ -53,24 +49,11 @@ export const LiveInfo = observer(() => { isOwner, isTeam, liveUserId, - notificationsHidden, reconnecting, - roomInfo: { - chatEnabled, - editorIds, - mode, - ownerIds, - roomId, - startTime, - users, - }, + roomInfo: { editorIds, mode, ownerIds, roomId, startTime, users }, }, } = useStore(); - const toggleChatEnabled = () => { - onChatEnabledChange({ enabled: !chatEnabled }); - }; - const owners = users.filter(u => ownerIds.indexOf(u.id) > -1); const editors = sortBy( @@ -133,35 +116,7 @@ export const LiveInfo = observer(() => { )} - - Preferences - - {isOwner && ( - - Chat enabled - - - - )} - - - Hide notifications - - - - + Live Mode 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..aebd25a7af3 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/ChatEnabled.tsx @@ -0,0 +1,34 @@ +import Switch from '@codesandbox/common/lib/components/Switch'; +import React from 'react'; + +import { useSignals, useStore } from 'app/store'; + +import { Preference, PreferencesContainer } from './elements'; + +export const ChatEnabled = () => { + 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..413b93ea01e --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/HideNotifications.tsx @@ -0,0 +1,29 @@ +import Switch from '@codesandbox/common/lib/components/Switch'; +import React from 'react'; + +import { useSignals, useStore } from 'app/store'; + +import { Preference, PreferencesContainer } from './elements'; + +export const HideNotifications = () => { + 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..fa3f4236235 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Preferences/Preferences.tsx @@ -0,0 +1,25 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import React from 'react'; + +import { useStore } from 'app/store'; + +import { SubTitle } from '../elements'; + +import { ChatEnabled } from './ChatEnabled'; +import { HideNotifications } from './HideNotifications'; + +export const Preferences = () => { + 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/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts index 1d788913f46..5002830ac37 100644 --- 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 @@ -1,12 +1,8 @@ -import styled, { css } from 'styled-components'; import Input from '@codesandbox/common/lib/components/Input'; import delay from '@codesandbox/common/lib/utils/animation/delay-effect'; +import styled, { css } from 'styled-components'; -interface Theme { - theme: { - light: boolean; - }; -} +import { Theme } from './types'; export const Container = styled.div` ${({ theme }: Theme) => css` @@ -126,24 +122,6 @@ export const ModeSelector = styled.div` `} `; -export const PreferencesContainer = styled.div` - margin: 1rem; - display: flex; -`; - -export const Preference = styled.div` - ${({ theme }: Theme) => css` - flex: 1; - font-weight: 400; - color: ${theme.light - ? css`rgba(0, 0, 0, 0.8)` - : css`rgba(255, 255, 255, 0.8)`}; - align-items: center; - justify-content: center; - font-size: 0.875rem; - `} -`; - export const IconContainer = styled.div` ${({ theme }: Theme) => css` transition: 0.3s ease color; 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; + }; +}; From 897bd8545973cd66c9388357b75dbff508f3671f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Tue, 25 Jun 2019 23:45:46 +0200 Subject: [PATCH 04/11] Extract LiveMode --- .../items/Live/LiveInfo/LiveInfo.tsx | 36 +--------- .../items/Live/LiveInfo/LiveMode/LiveMode.tsx | 49 ++++++++++++++ .../items/Live/LiveInfo/LiveMode/elements.ts | 66 +++++++++++++++++++ .../items/Live/LiveInfo/LiveMode/index.ts | 1 + .../Workspace/items/Live/LiveInfo/elements.ts | 63 ------------------ 5 files changed, 118 insertions(+), 97 deletions(-) create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/LiveMode.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/elements.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/index.ts 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 index 4a164cff1a5..2d5338d8b77 100644 --- 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 @@ -19,16 +19,13 @@ import { ConnectionStatus, Container, IconContainer, - Mode, - ModeDetails, - ModeSelect, - ModeSelector, NoUsers, StyledInput, SubTitle, Title, Users, } from './elements'; +import { LiveMode } from './LiveMode'; import { Preferences } from './Preferences'; import { SessionTimer } from './SessionTimer'; import { User } from './User'; @@ -38,7 +35,6 @@ export const LiveInfo = observer(() => { live: { onAddEditorClicked, onFollow, - onModeChanged, onRemoveEditorClicked, onSessionCloseClicked, }, @@ -118,35 +114,7 @@ export const LiveInfo = observer(() => { - - Live Mode - - - - - onModeChanged({ mode: 'open' }) : undefined - } - selected={mode === 'open'} - > -
Open
- - Everyone can edit -
- - onModeChanged({ mode: 'classroom' }) : undefined - } - selected={mode === 'classroom'} - > -
Classroom
- - Take control over who can edit -
-
-
+ {owners && ( 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..ce11aa5babf --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/LiveMode/LiveMode.tsx @@ -0,0 +1,49 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import noop from 'lodash/noop'; +import React from 'react'; + +import { useSignals, useStore } from 'app/store'; + +import { SubTitle } from '../elements'; + +import { Mode, ModeDetails, ModeSelect, ModeSelector } from './elements'; + +export const LiveMode = () => { + 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/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts index 5002830ac37..b0695a6ab25 100644 --- 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 @@ -59,69 +59,6 @@ export const Users = styled.div` `} `; -export const ModeSelect = styled.div` - position: relative; - margin: 0.5rem 1rem; -`; - -export const Mode = styled.button` - ${({ onClick, selected }: { onClick: any; selected: boolean }) => 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'}; - color: white; - opacity: ${selected ? 1 : 0.6}; - margin: 0.25rem 0; - - z-index: 3; - - ${onClick && - ` - &:hover { - opacity: 1; - }`}; - `} -`; - -export const ModeDetails = styled.div` - ${({ theme }: Theme) => css` - font-size: 0.75rem; - color: ${theme.light - ? css`rgba(0, 0, 0, 0.7)` - : css`rgba(255, 255, 255, 0.7)`}; - margin-top: 0.25rem; - `} -`; - -export const ModeSelector = styled.div` - ${({ i }: { i: number }) => 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); - `} -`; - export const IconContainer = styled.div` ${({ theme }: Theme) => css` transition: 0.3s ease color; From eeca4ee21cc73446100f1fa7db9e7ece6b8804c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 26 Jun 2019 00:51:02 +0200 Subject: [PATCH 05/11] Extract Users --- .../items/Live/LiveInfo/LiveInfo.tsx | 187 +----------------- .../Live/LiveInfo/Users/Editors/Editors.tsx | 71 +++++++ .../Live/LiveInfo/Users/Editors/index.ts | 1 + .../LiveInfo/Users/OtherUsers/OtherUsers.tsx | 75 +++++++ .../Live/LiveInfo/Users/OtherUsers/index.ts | 1 + .../Live/LiveInfo/Users/Owners/Owners.tsx | 55 ++++++ .../items/Live/LiveInfo/Users/Owners/index.ts | 1 + .../Live/LiveInfo/{ => Users}/User/User.tsx | 17 +- .../LiveInfo/{ => Users}/User/elements.ts | 0 .../Live/LiveInfo/{ => Users}/User/index.ts | 0 .../items/Live/LiveInfo/Users/Users.tsx | 38 ++++ .../items/Live/LiveInfo/Users/elements.ts | 33 ++++ .../items/Live/LiveInfo/Users/index.ts | 1 + .../Workspace/items/Live/LiveInfo/elements.ts | 31 --- 14 files changed, 294 insertions(+), 217 deletions(-) create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/Editors.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/index.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/OtherUsers.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/index.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/Owners.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/index.ts rename packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/{ => Users}/User/User.tsx (67%) rename packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/{ => Users}/User/elements.ts (100%) rename packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/{ => Users}/User/index.ts (100%) create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Users.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/elements.ts create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/index.ts 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 index 2d5338d8b77..8b94d558df4 100644 --- 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 @@ -1,13 +1,6 @@ -import Margin from '@codesandbox/common/lib/components/spacing/Margin'; -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; import { observer } from 'mobx-react-lite'; -import { sortBy } from 'lodash-es'; 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 RecordIcon from 'react-icons/lib/md/fiber-manual-record'; -import RemoveIcon from 'react-icons/lib/md/remove'; import { useSignals, useStore } from 'app/store'; @@ -15,56 +8,25 @@ import { Description, WorkspaceInputContainer } from '../../../elements'; import { LiveButton } from '../LiveButton'; -import { - ConnectionStatus, - Container, - IconContainer, - NoUsers, - StyledInput, - SubTitle, - Title, - Users, -} from './elements'; +import { ConnectionStatus, Container, StyledInput, Title } from './elements'; import { LiveMode } from './LiveMode'; import { Preferences } from './Preferences'; import { SessionTimer } from './SessionTimer'; -import { User } from './User'; +import { Users } from './Users'; export const LiveInfo = observer(() => { const { - live: { - onAddEditorClicked, - onFollow, - onRemoveEditorClicked, - onSessionCloseClicked, - }, + live: { onSessionCloseClicked }, } = useSignals(); const { live: { - followingUserId, isOwner, isTeam, - liveUserId, reconnecting, - roomInfo: { editorIds, mode, ownerIds, roomId, startTime, users }, + roomInfo: { roomId, startTime }, }, } = useStore(); - const owners = users.filter(u => ownerIds.indexOf(u.id) > -1); - - const editors = sortBy( - users.filter( - u => editorIds.indexOf(u.id) > -1 && ownerIds.indexOf(u.id) === -1 - ), - 'username' - ); - const otherUsers = sortBy( - users.filter( - u => ownerIds.indexOf(u.id) === -1 && editorIds.indexOf(u.id) === -1 - ), - 'username' - ); - const liveMessage = (() => { if (isTeam) { return 'Your team is live!'; @@ -116,146 +78,7 @@ export const LiveInfo = observer(() => { - {owners && ( - - Owners - - - {owners.map(owner => ( - - {followingUserId === owner.id ? ( - - onFollow({ liveUserId: null })} - /> - - ) : ( - - onFollow({ liveUserId: owner.id })} - /> - - )} - - ) - } - type="Owner" - user={owner} - users={users} - /> - ))} - - - )} - - {editors.length > 0 && mode === 'classroom' && ( - - Editors - - - {editors.map(user => ( - - {user.id !== liveUserId && ( - - {followingUserId === user.id ? ( - - onFollow({ liveUserId: null })} - /> - - ) : ( - - onFollow({ liveUserId: user.id })} - /> - - )} - - )} - - {isOwner && mode === 'classroom' && ( - - - - onRemoveEditorClicked({ liveUserId: user.id }) - } - /> - - - )} - - } - type="Editor" - user={user} - users={users} - /> - ))} - - - )} - - - Users - - - {otherUsers.length ? ( - otherUsers.map(user => ( - - {mode !== 'classroom' && user.id !== liveUserId && ( - - {followingUserId === user.id ? ( - - onFollow({ liveUserId: null })} - /> - - ) : ( - - onFollow({ liveUserId: user.id })} - /> - - )} - - )} - - {isOwner && mode === 'classroom' && ( - - - - onAddEditorClicked({ liveUserId: user.id }) - } - /> - - - )} - - } - type="Spectator" - user={user} - users={users} - /> - )) - ) : ( - No other users in session, invite them! - )} - - + ); }); 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..d031088b4b3 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/Editors.tsx @@ -0,0 +1,71 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +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 { SubTitle } from '../../elements'; + +import { IconContainer, Users } from '../elements'; +import { User } from '../User'; + +export const Editors = ({ editors }) => { + const { + live: { onFollow, onRemoveEditorClicked }, + } = useSignals(); + const { + live: { + followingUserId, + isOwner, + liveUserId, + roomInfo: { mode }, + }, + } = useStore(); + const getSideView = userId => ( + <> + {userId !== liveUserId && ( + + {followingUserId === userId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: userId })} /> + + )} + + )} + + {isOwner && mode === 'classroom' && ( + + + onRemoveEditorClicked({ liveUserId: userId })} + /> + + + )} + + ); + + return ( + + Editors + + + {editors.map(user => ( + + ))} + + + ); +}; 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..135b284d7c2 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/OtherUsers.tsx @@ -0,0 +1,75 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +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 { SubTitle } from '../../elements'; + +import { IconContainer, NoUsers, Users } from '../elements'; +import { User } from '../User'; + +export const OtherUsers = ({ otherUsers }) => { + const { + live: { onAddEditorClicked, onFollow }, + } = useSignals(); + const { + live: { + followingUserId, + isOwner, + liveUserId, + roomInfo: { mode }, + }, + } = useStore(); + const getSideView = userId => ( + <> + {mode !== 'classroom' && userId !== liveUserId && ( + + {followingUserId === userId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: userId })} /> + + )} + + )} + + {isOwner && mode === 'classroom' && ( + + + onAddEditorClicked({ liveUserId: userId })} + /> + + + )} + + ); + + return ( + + Users + + + {otherUsers.length ? ( + otherUsers.map(user => ( + + )) + ) : ( + No other users in session, invite them! + )} + + + ); +}; 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..eb3b2c75d7d --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/Owners.tsx @@ -0,0 +1,55 @@ +import Margin from '@codesandbox/common/lib/components/spacing/Margin'; +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +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 { SubTitle } from '../../elements'; + +import { IconContainer, Users } from '../elements'; +import { User } from '../User'; + +type Props = { + owners: { id: number }[]; +}; +export const Owners = ({ owners }: Props) => { + const { + live: { onFollow }, + } = useSignals(); + const { + live: { followingUserId, liveUserId }, + } = useStore(); + const getSideView = ownerId => + ownerId !== liveUserId && ( + + {followingUserId === ownerId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: ownerId })} /> + + )} + + ); + + return ( + + Owners + + + {owners.map(owner => ( + + ))} + + + ); +}; 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/User/User.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/User.tsx similarity index 67% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/User.tsx rename to packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/User.tsx index 40beb49d425..c3c909b509d 100644 --- a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/User.tsx +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/User.tsx @@ -1,11 +1,20 @@ import React from 'react'; + +import { useStore } from 'app/store'; + import { UserContainer, ProfileImage, UserName, Status } from './elements'; -export const User = ({ currentUserId, sideView, type, user, users }) => { - const metaData = users.find(u => u.id === user.id); - const [r, g, b] = metaData ? metaData.color : [0, 0, 0]; +export const User = ({ sideView = null, type, user }) => { + const { + live: { + liveUserId, + roomInfo: { users }, + }, + } = useStore(); - const isCurrentUser = user.id === currentUserId; + const metaData = users.find(({ id }) => id === user.id); + const [r, g, b] = metaData ? metaData.color : [0, 0, 0]; + const isCurrentUser = user.id === liveUserId; return ( diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/elements.ts similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/elements.ts rename to packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/elements.ts diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/index.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/index.ts similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/User/index.ts rename to packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/User/index.ts 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..34f8d244c59 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Users.tsx @@ -0,0 +1,38 @@ +import { sortBy } from 'lodash-es'; +import React from 'react'; + +import { useStore } from 'app/store'; + +import { Editors } from './Editors'; +import { OtherUsers } from './OtherUsers'; +import { Owners } from './Owners'; + +export const Users = () => { + 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/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/elements.ts index b0695a6ab25..a4e51b29033 100644 --- 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 @@ -48,34 +48,3 @@ export const SubTitle = styled.div` padding-left: 1rem; font-size: 0.875rem; `; - -export const Users = styled.div` - ${({ theme }: Theme) => css` - padding: 0.25rem 1rem; - padding-top: 0; - color: ${theme.light - ? css`rgba(0, 0, 0, 0.8)` - : css`rgba(255, 255, 255, 0.8)`}; - `} -`; - -export const IconContainer = styled.div` - ${({ theme }: Theme) => css` - transition: 0.3s ease color; - color: ${theme.light - ? css`rgba(0, 0, 0, 0.8)` - : css`rgba(255, 255, 255, 0.8)`}; - cursor: pointer; - - &:hover { - color: white; - } - `} -`; - -export const NoUsers = styled.div` - margin-top: 0.25rem; - color: rgba(255, 255, 255, 0.8); - font-size: 0.875rem; - font-weight: 600; -`; From a0b832a17f5aa7ce81c1c44dd6d828ff10413556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 26 Jun 2019 01:38:29 +0200 Subject: [PATCH 06/11] Extract SideView from Users --- .../Live/LiveInfo/Users/Editors/Editors.tsx | 79 ++++------------- .../Live/LiveInfo/Users/Editors/SideView.tsx | 51 +++++++++++ .../LiveInfo/Users/OtherUsers/OtherUsers.tsx | 87 +++++-------------- .../LiveInfo/Users/OtherUsers/SideView.tsx | 51 +++++++++++ .../Live/LiveInfo/Users/Owners/Owners.tsx | 63 ++++---------- .../Live/LiveInfo/Users/Owners/SideView.tsx | 33 +++++++ 6 files changed, 190 insertions(+), 174 deletions(-) create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/SideView.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/SideView.tsx create mode 100644 packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/SideView.tsx 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 index d031088b4b3..55ca36fb7cb 100644 --- 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 @@ -1,71 +1,26 @@ import Margin from '@codesandbox/common/lib/components/spacing/Margin'; -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; 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 { SubTitle } from '../../elements'; -import { IconContainer, Users } from '../elements'; +import { Users } from '../elements'; import { User } from '../User'; -export const Editors = ({ editors }) => { - const { - live: { onFollow, onRemoveEditorClicked }, - } = useSignals(); - const { - live: { - followingUserId, - isOwner, - liveUserId, - roomInfo: { mode }, - }, - } = useStore(); - const getSideView = userId => ( - <> - {userId !== liveUserId && ( - - {followingUserId === userId ? ( - - onFollow({ liveUserId: null })} /> - - ) : ( - - onFollow({ liveUserId: userId })} /> - - )} - - )} - - {isOwner && mode === 'classroom' && ( - - - onRemoveEditorClicked({ liveUserId: userId })} - /> - - - )} - - ); +import { SideView } from './SideView'; - return ( - - Editors +export const Editors = ({ editors }) => ( + + Editors - - {editors.map(user => ( - - ))} - - - ); -}; + + {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..56eb37e49cd --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Editors/SideView.tsx @@ -0,0 +1,51 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +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'; + +export const SideView = ({ 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/OtherUsers/OtherUsers.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/OtherUsers.tsx index 135b284d7c2..50ddbcd625f 100644 --- 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 @@ -1,75 +1,30 @@ import Margin from '@codesandbox/common/lib/components/spacing/Margin'; -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; 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 { SubTitle } from '../../elements'; -import { IconContainer, NoUsers, Users } from '../elements'; +import { NoUsers, Users } from '../elements'; import { User } from '../User'; -export const OtherUsers = ({ otherUsers }) => { - const { - live: { onAddEditorClicked, onFollow }, - } = useSignals(); - const { - live: { - followingUserId, - isOwner, - liveUserId, - roomInfo: { mode }, - }, - } = useStore(); - const getSideView = userId => ( - <> - {mode !== 'classroom' && userId !== liveUserId && ( - - {followingUserId === userId ? ( - - onFollow({ liveUserId: null })} /> - - ) : ( - - onFollow({ liveUserId: userId })} /> - - )} - - )} +import { SideView } from './SideView'; - {isOwner && mode === 'classroom' && ( - - - onAddEditorClicked({ liveUserId: userId })} - /> - - - )} - - ); +export const OtherUsers = ({ otherUsers }) => ( + + Users - return ( - - Users - - - {otherUsers.length ? ( - otherUsers.map(user => ( - - )) - ) : ( - No other users in session, invite them! - )} - - - ); -}; + + {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..b90f0669f39 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/OtherUsers/SideView.tsx @@ -0,0 +1,51 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +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'; + +export const SideView = ({ 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/Owners/Owners.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/Owners.tsx index eb3b2c75d7d..88c22cdae27 100644 --- 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 @@ -1,55 +1,26 @@ import Margin from '@codesandbox/common/lib/components/spacing/Margin'; -import Tooltip from '@codesandbox/common/lib/components/Tooltip'; 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 { SubTitle } from '../../elements'; -import { IconContainer, Users } from '../elements'; +import { Users } from '../elements'; import { User } from '../User'; -type Props = { - owners: { id: number }[]; -}; -export const Owners = ({ owners }: Props) => { - const { - live: { onFollow }, - } = useSignals(); - const { - live: { followingUserId, liveUserId }, - } = useStore(); - const getSideView = ownerId => - ownerId !== liveUserId && ( - - {followingUserId === ownerId ? ( - - onFollow({ liveUserId: null })} /> - - ) : ( - - onFollow({ liveUserId: ownerId })} /> - - )} - - ); +import { SideView } from './SideView'; - return ( - - Owners +export const Owners = ({ owners }) => ( + + Owners - - {owners.map(owner => ( - - ))} - - - ); -}; + + {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..320c64b1d21 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Users/Owners/SideView.tsx @@ -0,0 +1,33 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +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'; + +export const SideView = ({ userId }) => { + const { + live: { onFollow }, + } = useSignals(); + const { + live: { followingUserId, liveUserId }, + } = useStore(); + + return ( + userId !== liveUserId && ( + + {followingUserId === userId ? ( + + onFollow({ liveUserId: null })} /> + + ) : ( + + onFollow({ liveUserId: userId })} /> + + )} + + ) + ); +}; From b8e157db352dc5be8a91a52428b3b5bfeaf39759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Wed, 26 Jun 2019 01:51:14 +0200 Subject: [PATCH 07/11] Fix typecheck --- .../Workspace/items/Live/LiveButton/LiveButton.tsx | 14 ++++++-------- .../Workspace/items/Live/LiveInfo/LiveInfo.tsx | 6 ++++-- .../Workspace/items/Live/LiveInfo/SessionTimer.tsx | 6 ++++-- .../Workspace/items/Live/LiveInfo/elements.ts | 3 +-- 4 files changed, 15 insertions(+), 14 deletions(-) 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 index e65c94bcb2d..6c153164941 100644 --- 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 @@ -1,3 +1,4 @@ +import noop from 'lodash/noop'; import React, { useState } from 'react'; import { useInterval } from 'app/hooks'; @@ -5,11 +6,11 @@ import { useInterval } from 'app/hooks'; import { AnimatedRecordIcon, Button, LoadingDiv } from './elements'; export const LiveButton = ({ - disable, + disable = false, icon = true, - isLoading, + isLoading = false, message = 'Go Live', - onClick, + onClick = noop, }) => { const [hovering, setHovering] = useState(false); const [showIcon, setShowIcon] = useState(icon); @@ -27,9 +28,6 @@ export const LiveButton = ({ setShowIcon(true); } - const startHovering = () => setHovering(true); - const stopHovering = () => setHovering(false); - if (isLoading) { return Creating Session; } @@ -37,8 +35,8 @@ export const LiveButton = ({ return (