Skip to content

Commit b3541d1

Browse files
author
Ives van Hoorne
committed
Add 'follow' functionality for live
1 parent 5b92c04 commit b3541d1

File tree

9 files changed

+180
-47
lines changed

9 files changed

+180
-47
lines changed

packages/app/.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"webpack": {
55
"config": "config/webpack.common.js"
66
}
7-
}
7+
},
8+
"import/external-module-folders": ["src", "node_modules"]
89
}
910
}

packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/LiveInfo.js

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ import Margin from 'common/components/spacing/Margin';
99
import delay from 'common/utils/animation/delay-effect';
1010
import Switch from 'common/components/Switch';
1111

12+
import Tooltip from 'common/components/Tooltip';
13+
14+
import AddIcon from 'react-icons/lib/md/add';
15+
import RemoveIcon from 'react-icons/lib/md/remove';
16+
import FollowIcon from 'react-icons/lib/ti/user-add';
17+
import UnFollowIcon from 'react-icons/lib/ti/user-delete';
18+
1219
import User from './User';
1320
import Countdown from './Countdown';
1421
import LiveButton from './LiveButton';
@@ -126,6 +133,16 @@ const Preference = styled.div`
126133
font-size: 0.875rem;
127134
`;
128135

136+
const IconContainer = styled.div`
137+
transition: 0.3s ease color;
138+
color: rgba(255, 255, 255, 0.8);
139+
cursor: pointer;
140+
141+
&:hover {
142+
color: white;
143+
}
144+
`;
145+
129146
class LiveInfo extends React.Component {
130147
select = e => {
131148
e.target.select();
@@ -146,6 +163,8 @@ class LiveInfo extends React.Component {
146163
toggleNotificationsHidden,
147164
chatEnabled,
148165
toggleChatEnabled,
166+
setFollowing,
167+
followingUserId,
149168
} = this.props;
150169

151170
const owner = roomInfo.users.find(u => u.id === ownerId);
@@ -258,6 +277,25 @@ class LiveInfo extends React.Component {
258277
user={owner}
259278
roomInfo={roomInfo}
260279
type="Owner"
280+
sideView={
281+
owner.id !== currentUserId && (
282+
<IconContainer>
283+
{followingUserId === owner.id ? (
284+
<Tooltip title="Stop following">
285+
<UnFollowIcon
286+
onClick={() => setFollowing({ userId: null })}
287+
/>
288+
</Tooltip>
289+
) : (
290+
<Tooltip title="Follow along">
291+
<FollowIcon
292+
onClick={() => setFollowing({ userId: owner.id })}
293+
/>
294+
</Tooltip>
295+
)}
296+
</IconContainer>
297+
)
298+
}
261299
/>
262300
</Users>
263301
</Margin>
@@ -272,11 +310,44 @@ class LiveInfo extends React.Component {
272310
<User
273311
currentUserId={currentUserId}
274312
key={user.id}
275-
showSwitch={isOwner && roomInfo.mode === 'classroom'}
276313
user={user}
277314
roomInfo={roomInfo}
278-
onClick={() => removeEditor({ userId: user.id })}
279315
type="Editor"
316+
sideView={
317+
<React.Fragment>
318+
{user.id !== currentUserId && (
319+
<IconContainer>
320+
{followingUserId === user.id ? (
321+
<Tooltip title="Stop following">
322+
<UnFollowIcon
323+
onClick={() => setFollowing({ userId: null })}
324+
/>
325+
</Tooltip>
326+
) : (
327+
<Tooltip title="Follow along">
328+
<FollowIcon
329+
onClick={() =>
330+
setFollowing({ userId: user.id })
331+
}
332+
/>
333+
</Tooltip>
334+
)}
335+
</IconContainer>
336+
)}
337+
{isOwner &&
338+
roomInfo.mode === 'classroom' && (
339+
<IconContainer style={{ marginLeft: '0.25rem' }}>
340+
<Tooltip title={'Make spectator'}>
341+
<RemoveIcon
342+
onClick={() =>
343+
removeEditor({ userId: user.id })
344+
}
345+
/>
346+
</Tooltip>
347+
</IconContainer>
348+
)}
349+
</React.Fragment>
350+
}
280351
/>
281352
))}
282353
</Users>
@@ -292,12 +363,43 @@ class LiveInfo extends React.Component {
292363
<User
293364
currentUserId={currentUserId}
294365
key={user.id}
295-
showSwitch={isOwner && roomInfo.mode === 'classroom'}
296366
user={user}
297367
roomInfo={roomInfo}
298-
onClick={() => addEditor({ userId: user.id })}
299368
type="Spectator"
300-
showPlusIcon
369+
sideView={
370+
<React.Fragment>
371+
{roomInfo.mode !== 'classroom' &&
372+
user.id !== currentUserId && (
373+
<IconContainer>
374+
{followingUserId === user.id ? (
375+
<Tooltip title="Stop following">
376+
<UnFollowIcon
377+
onClick={() => setFollowing({ userId: null })}
378+
/>
379+
</Tooltip>
380+
) : (
381+
<Tooltip title="Follow along">
382+
<FollowIcon
383+
onClick={() =>
384+
setFollowing({ userId: user.id })
385+
}
386+
/>
387+
</Tooltip>
388+
)}
389+
</IconContainer>
390+
)}
391+
{isOwner &&
392+
roomInfo.mode === 'classroom' && (
393+
<IconContainer style={{ marginLeft: '0.25rem' }}>
394+
<Tooltip title={'Make editor'}>
395+
<AddIcon
396+
onClick={() => addEditor({ userId: user.id })}
397+
/>
398+
</Tooltip>
399+
</IconContainer>
400+
)}
401+
</React.Fragment>
402+
}
301403
/>
302404
))
303405
) : (

packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/User.js

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import { observer } from 'mobx-react';
44

55
import delay from 'common/utils/animation/delay-effect';
66

7-
import Tooltip from 'common/components/Tooltip';
8-
9-
import AddIcon from 'react-icons/lib/md/add';
10-
import RemoveIcon from 'react-icons/lib/md/remove';
11-
127
const Status = styled.div`
138
font-size: 0.75rem;
149
color: rgba(255, 255, 255, 0.6);
@@ -45,28 +40,10 @@ const UserName = styled.div`
4540
font-size: 0.875rem;
4641
`;
4742

48-
const IconContainer = styled.div`
49-
transition: 0.3s ease color;
50-
color: rgba(255, 255, 255, 0.8);
51-
cursor: pointer;
52-
53-
&:hover {
54-
color: white;
55-
}
56-
`;
57-
5843
// eslint-disable-next-line
5944
class User extends React.Component {
6045
render() {
61-
const {
62-
user,
63-
type,
64-
onClick,
65-
showPlusIcon,
66-
showSwitch,
67-
roomInfo,
68-
currentUserId,
69-
} = this.props;
46+
const { user, type, sideView, roomInfo, currentUserId } = this.props;
7047

7148
const metaData = roomInfo.usersMetadata.get(user.id);
7249
const [r, g, b] = metaData
@@ -91,17 +68,7 @@ class User extends React.Component {
9168
</Status>
9269
)}
9370
</div>
94-
{showSwitch && (
95-
<IconContainer>
96-
<Tooltip title={showPlusIcon ? 'Make editor' : 'Make spectator'}>
97-
{showPlusIcon ? (
98-
<AddIcon onClick={onClick} />
99-
) : (
100-
<RemoveIcon onClick={onClick} />
101-
)}
102-
</Tooltip>
103-
</IconContainer>
104-
)}
71+
{sideView}
10572
</UserContainer>
10673
);
10774
}

packages/app/src/app/pages/Sandbox/Editor/Workspace/items/Live/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const Live = ({ signals, store }) => (
3333
enabled: !store.live.roomInfo.chatEnabled,
3434
});
3535
}}
36+
setFollowing={signals.live.onFollow}
37+
followingUserId={store.live.followingUserId}
3638
/>
3739
) : (
3840
<React.Fragment>

packages/app/src/app/store/modules/editor/sequences.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,15 @@ export const changeCurrentModule = [
5555
equals(state`live.isLive`),
5656
{
5757
true: [
58-
getSelectionsForCurrentModule,
59-
set(state`editor.pendingUserSelections`, props`selections`),
60-
sendChangeCurrentModule,
58+
equals(state`live.isCurrentEditor`),
59+
{
60+
true: [
61+
getSelectionsForCurrentModule,
62+
set(state`editor.pendingUserSelections`, props`selections`),
63+
sendChangeCurrentModule,
64+
],
65+
false: [],
66+
},
6167
],
6268
false: [],
6369
},

packages/app/src/app/store/modules/live/actions.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,16 @@ export function sendChangeCurrentModule({ props, state, live }) {
316316
module.shortid
317317
);
318318

319+
const followingUserId = state.get('live.followingUserId');
320+
if (followingUserId) {
321+
const user = state.get('live.roomInfo.usersMetadata').get(followingUserId);
322+
323+
if (user && user.currentModuleShortid !== module.shortid) {
324+
// Reset following as this is a user change module action
325+
state.set('live.followingUserId', null);
326+
}
327+
}
328+
319329
live.send('user:current-module', {
320330
moduleShortid: module.shortid,
321331
});
@@ -494,3 +504,16 @@ export function sendChat({ live, props }) {
494504
export function sendChatEnabled({ live, props }) {
495505
live.send('live:chat_enabled', { enabled: props.enabled });
496506
}
507+
508+
export function getModuleIdFromShortid({ props, state }) {
509+
const moduleShortid = props.moduleShortid;
510+
const modules = state.get('editor.currentSandbox.modules');
511+
512+
const module = modules.find(m => m.shortid === moduleShortid);
513+
514+
if (module) {
515+
return { id: module.id };
516+
}
517+
518+
return {};
519+
}

packages/app/src/app/store/modules/live/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default Module({
1414
receivingCode: false,
1515
reconnecting: false,
1616
notificationsHidden: false,
17+
followingUserId: null,
1718
},
1819
computed: {
1920
isEditor,
@@ -39,5 +40,6 @@ export default Module({
3940
onToggleNotificationsHidden: sequences.toggleNotificationsHidden,
4041
onSendChat: sequences.sendChat,
4142
onChatEnabledChange: sequences.setChatEnabled,
43+
onFollow: sequences.setFollowing,
4244
},
4345
});

packages/app/src/app/store/modules/live/model.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export default {
2424
isOwner: types.boolean,
2525
reconnecting: types.boolean,
2626
notificationsHidden: types.boolean,
27+
followingUserId: types.maybe(types.string),
2728
roomInfo: types.maybe(
2829
types.model({
2930
startTime: types.maybe(types.number),

packages/app/src/app/store/modules/live/sequences.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { state, props } from 'cerebral/tags';
1212
import * as factories from '../../factories';
1313
import { setSandbox, openModal, resetLive } from '../../sequences';
1414

15-
import { changeCode } from '../editor/sequences';
15+
import { changeCode, changeCurrentModule } from '../editor/sequences';
1616
import { setModuleSaved } from '../editor/actions';
1717
import { removeModule, removeDirectory } from '../files/sequences';
1818
import * as actions from './actions';
@@ -233,6 +233,23 @@ export const handleMessage = [
233233
props`data.moduleShortid`
234234
),
235235
actions.clearUserSelections,
236+
237+
when(
238+
state`live.followingUserId`,
239+
props`data.user_id`,
240+
props`data.moduleShortid`,
241+
state`editor.currentModuleShortid`,
242+
(followingId, userId, moduleShortid, currentModuleShortid) =>
243+
followingId === userId && moduleShortid !== currentModuleShortid
244+
),
245+
{
246+
true: [
247+
set(props`moduleShortid`, props`data.moduleShortid`),
248+
actions.getModuleIdFromShortid,
249+
changeCurrentModule,
250+
],
251+
false: [],
252+
},
236253
],
237254
},
238255
],
@@ -295,7 +312,11 @@ export const handleMessage = [
295312
actions.disconnect,
296313
set(props`modal`, 'liveSessionEnded'),
297314
openModal,
298-
when(state`live.roomInfo.ownerId`, `live.user.id`, (i1, i2) => i1 === i2),
315+
when(
316+
state`live.roomInfo.ownerId`,
317+
state`live.user.id`,
318+
(i1, i2) => i1 === i2
319+
),
299320
{
300321
true: [],
301322
false: [set(state`editor.currentSandbox.owned`, false)],
@@ -310,7 +331,13 @@ export const handleMessage = [
310331
},
311332
];
312333

313-
export const sendSelection = [actions.sendSelection];
334+
export const sendSelection = [
335+
equals(state`live.isCurrentEditor`),
336+
{
337+
true: [actions.sendSelection],
338+
false: [],
339+
},
340+
];
314341

315342
export const createLive = [
316343
set(state`live.isOwner`, true),
@@ -368,3 +395,5 @@ export const setChatEnabled = [
368395
false: [],
369396
},
370397
];
398+
399+
export const setFollowing = [set(state`live.followingUserId`, props`userId`)];

0 commit comments

Comments
 (0)