diff --git a/packages/app/src/app/overmind/effects/live/index.ts b/packages/app/src/app/overmind/effects/live/index.ts index 1e0cebd7345..61c79f6373d 100755 --- a/packages/app/src/app/overmind/effects/live/index.ts +++ b/packages/app/src/app/overmind/effects/live/index.ts @@ -269,7 +269,7 @@ export default new (class Live { }); } - sendLiveMode(mode: string) { + sendLiveMode(mode: RoomInfo['mode']) { return this.send('live:mode', { mode, }); diff --git a/packages/app/src/app/overmind/namespaces/live/actions.ts b/packages/app/src/app/overmind/namespaces/live/actions.ts index e590b4b157f..c3249de340d 100755 --- a/packages/app/src/app/overmind/namespaces/live/actions.ts +++ b/packages/app/src/app/overmind/namespaces/live/actions.ts @@ -1,4 +1,8 @@ -import { LiveMessage, LiveMessageEvent } from '@codesandbox/common/lib/types'; +import { + LiveMessage, + LiveMessageEvent, + RoomInfo, +} from '@codesandbox/common/lib/types'; import { Action, AsyncAction, Operator } from 'app/overmind'; import { withLoadApp } from 'app/overmind/factories'; import getItems from 'app/overmind/utils/items'; @@ -176,9 +180,9 @@ export const onSelectionChanged: Action = ( } }; -export const onModeChanged: Action<{ mode: string }> = ( - { state, effects }, - { mode } +export const onModeChanged: Action = ( + { effects, state }, + mode ) => { if (state.live.isOwner && state.live.roomInfo) { state.live.roomInfo.mode = mode; @@ -186,31 +190,29 @@ export const onModeChanged: Action<{ mode: string }> = ( } }; -export const onAddEditorClicked: Action<{ - liveUserId: string; -}> = ({ state, effects }, { liveUserId }) => { +export const onAddEditorClicked: Action = ( + { effects, state }, + liveUserId +) => { if (!state.live.roomInfo) { return; } + state.live.roomInfo.editorIds.push(liveUserId); effects.live.sendEditorAdded(liveUserId); }; -export const onRemoveEditorClicked: Action = ( - { state, effects }, - { liveUserId, data } +export const onRemoveEditorClicked: Action = ( + { effects, state }, + liveUserId ) => { - const userId = liveUserId || data.editor_user_id; - if (!state.live.roomInfo) { return; } const editors = state.live.roomInfo.editorIds; - const newEditors = editors.filter(id => id !== userId); - - state.live.roomInfo.editorIds = newEditors; + state.live.roomInfo.editorIds = editors.filter(id => id !== liveUserId); effects.live.sendEditorRemoved(liveUserId); }; @@ -249,9 +251,10 @@ export const onChatEnabledChange: Action = ( } }; -export const onFollow: Action<{ - liveUserId: string; -}> = ({ state, effects, actions }, { liveUserId }) => { +export const onFollow: Action = ( + { actions, effects, state }, + liveUserId +) => { if (!state.live.roomInfo) { return; } @@ -259,11 +262,13 @@ export const onFollow: Action<{ effects.analytics.track('Follow Along in Live'); state.live.followingUserId = liveUserId; - const user = state.live.roomInfo.users.find(u => u.id === liveUserId); + const user = state.live.roomInfo.users.find(({ id }) => id === liveUserId); if (user && user.currentModuleShortid && state.editor.currentSandbox) { const { modules } = state.editor.currentSandbox; - const module = modules.find(m => m.shortid === user.currentModuleShortid); + const module = modules.find( + ({ shortid }) => shortid === user.currentModuleShortid + ); actions.editor.moduleSelected({ id: module ? module.id : undefined, diff --git a/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts b/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts index 4a28a5fdfac..a040fd1a591 100644 --- a/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts +++ b/packages/app/src/app/overmind/namespaces/live/liveMessageOperators.ts @@ -5,13 +5,15 @@ import { LiveMessage, LiveUser, Module, + RoomInfo, UserSelection, } from '@codesandbox/common/lib/types'; import { NotificationStatus } from '@codesandbox/notifications/lib/state'; -import { Operator } from 'app/overmind'; import { camelizeKeys } from 'humps'; import { json, mutate } from 'overmind'; +import { Operator } from 'app/overmind'; + export const onJoin: Operator> = mutate(({ state, actions }, { _isOwnMessage, data }) => { + mode: RoomInfo['mode']; +}>> = mutate(({ actions, state }, { _isOwnMessage, data }) => { if (!state.live.roomInfo) { return; } @@ -428,6 +430,7 @@ export const onLiveMode: Operator; - setMode: ({ mode: string }) => void; - addEditor: ({ liveUserId: string }) => void; - removeEditor: ({ liveUserId: string }) => void; - currentUserId: string; - reconnecting: boolean; - onSessionCloseClicked: () => void; - notificationsHidden: boolean; - toggleNotificationsHidden: () => void; - chatEnabled: boolean; - toggleChatEnabled: () => void; - setFollowing: ({ liveUserId: string }) => void; - followingUserId: string; -} - -const LiveInfo: React.FunctionComponent = ({ - roomInfo, - roomInfo: { users, editorIds, startTime, roomId, mode } = {}, - isOwner, - isTeam, - ownerIds, - setMode, - addEditor, - removeEditor, - currentUserId, - reconnecting, - onSessionCloseClicked, - notificationsHidden, - toggleNotificationsHidden, - chatEnabled, - toggleChatEnabled, - setFollowing, - followingUserId, -}) => { - const select: React.ChangeEventHandler = e => { - e.target.select(); - }; - - const owners = users.filter(u => ownerIds.includes(u.id)); - - const editors = sortBy( - users.filter(u => editorIds.includes(u.id) && !ownerIds.includes(u.id)), - 'username' - ); - - const otherUsers = sortBy( - users.filter(u => !ownerIds.includes(u.id) && !editorIds.includes(u.id)), - '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...' - ) : ( - <> - <RecordIcon /> {liveMessage} - </> - )} - </div> - <div>{startTime != null && <Countdown time={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={mode === 'open'} - > -
Open
- Everyone can edit -
- setMode({ mode: 'classroom' }) : undefined} - selected={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 && mode === 'classroom' && ( - - Editors - - {editors.map(user => ( - - {user.id !== currentUserId && ( - - {followingUserId === user.id ? ( - - setFollowing({ liveUserId: null })} - /> - - ) : ( - - - setFollowing({ liveUserId: user.id }) - } - /> - - )} - - )} - {isOwner && mode === 'classroom' && ( - - - - removeEditor({ liveUserId: user.id }) - } - /> - - - )} - - } - /> - ))} - - - )} - - - Users - - - {otherUsers.length ? ( - otherUsers.map(user => ( - - {mode !== 'classroom' && user.id !== currentUserId && ( - - {followingUserId === user.id ? ( - - setFollowing({ liveUserId: null })} - /> - - ) : ( - - - setFollowing({ liveUserId: user.id }) - } - /> - - )} - - )} - {isOwner && mode === 'classroom' && ( - - - addEditor({ liveUserId: user.id })} - /> - - - )} - - } - /> - )) - ) : ( -
- No other users in session, invite them! -
- )} -
-
-
- ); -}; - -export default LiveInfo; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Button/elements.ts b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Button/elements.ts new file mode 100644 index 00000000000..7d47e4cb2bb --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Button/elements.ts @@ -0,0 +1,18 @@ +import styled, { css } from 'styled-components'; + +export const IconContainer = styled.div<{ isSecond: boolean }>` + ${({ isSecond, theme }) => css` + transition: 0.3s ease color; + color: ${theme.light ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)'}; + cursor: pointer; + + ${isSecond && + css` + margin-left: 0.25rem; + `}; + + &:hover { + color: white; + } + `}; +`; diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Button/index.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Button/index.tsx new file mode 100644 index 00000000000..136eb30583e --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Button/index.tsx @@ -0,0 +1,20 @@ +import Tooltip from '@codesandbox/common/lib/components/Tooltip'; +import React, { ComponentProps, ComponentType, FunctionComponent } from 'react'; + +import { IconContainer } from './elements'; + +type Props = { + Icon: ComponentType; + tooltip: string; +} & Partial, 'isSecond'>>; +export const Button: FunctionComponent = ({ + Icon, + isSecond = false, + tooltip, +}) => ( + + + + + +); 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/LiveInfo/Countdown.js similarity index 100% rename from packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/Countdown.js rename to packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/Countdown.js diff --git a/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/FollowButton.tsx b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/FollowButton.tsx new file mode 100644 index 00000000000..f674552c893 --- /dev/null +++ b/packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo/FollowButton.tsx @@ -0,0 +1,38 @@ +import { LiveUser } from '@codesandbox/common/lib/types'; +import React, { FunctionComponent } from 'react'; +import UnFollowIcon from 'react-icons/lib/io/eye-disabled'; +import FollowIcon from 'react-icons/lib/io/eye'; + +import { useOvermind } from 'app/overmind'; + +import { Button } from './Button'; + +type Props = { + user: LiveUser; +}; +export const FollowButton: FunctionComponent = ({ user: { id } }) => { + const { + actions: { + live: { onFollow }, + }, + state: { + live: { followingUserId, liveUserId }, + }, + } = useOvermind(); + + if (id === liveUserId) { + return null; + } + + const buttonProps = + followingUserId === id + ? { + Icon: () => onFollow(null)} />, + tooltip: 'Stop following', + } + : { + Icon: () => onFollow(id)} />, + tooltip: 'Follow along', + }; + return