Skip to content

Commit 7a6e590

Browse files
author
Ives van Hoorne
committed
More user info
1 parent 4c5b9bb commit 7a6e590

File tree

19 files changed

+344
-69
lines changed

19 files changed

+344
-69
lines changed

packages/app/src/app/components/CodeEditor/Monaco/index.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,13 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
208208

209209
requestAnimationFrame(() => {
210210
this.setupWorkers();
211-
editor.onDidChangeModelContent(() => {
211+
editor.onDidChangeModelContent(({ changes }) => {
212+
const { isLive, sendTransforms } = this.props;
213+
214+
if (isLive && sendTransforms && !this.receivingCode) {
215+
this.addChangesOperation(changes);
216+
}
217+
212218
this.handleChange();
213219
});
214220
});
@@ -279,14 +285,6 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
279285
},
280286
});
281287

282-
editor.onDidChangeModelContent(({ changes }) => {
283-
const { isLive, sendTransforms } = this.props;
284-
285-
if (isLive && sendTransforms && !this.receivingCode) {
286-
this.addChangesOperation(changes);
287-
}
288-
});
289-
290288
editor.onDidChangeCursorSelection(selectionChange => {
291289
// TODO: add another debounced action to send the current data. So we can
292290
// have the correct cursor pos no matter what
@@ -307,6 +305,7 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
307305
/* click inside a selection */ selectionChange.source === 'api') &&
308306
onSelectionChanged
309307
) {
308+
this.onSelectionChangedDebounced.cancel();
310309
onSelectionChanged({
311310
selection: data,
312311
moduleShortid: this.currentModule.shortid,
@@ -415,7 +414,7 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
415414
}
416415
};
417416

418-
changes = { code: '', changes: [] };
417+
changes = { moduleShortid: null, code: '', changes: [] };
419418
changeTimeout: ?TimeoutID;
420419
/**
421420
* Throttle the changes and handle them after a desired amount of time as one array of changes
@@ -424,6 +423,9 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
424423
if (!this.changes.code) {
425424
this.changes.code = this.currentModule.code || '';
426425
}
426+
if (!this.changes.moduleShortid) {
427+
this.changes.moduleShortid = this.currentModule.shortid;
428+
}
427429

428430
changes.forEach(change => {
429431
this.changes.changes.push(change);
@@ -440,7 +442,11 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
440442
sendChangeOperations = () => {
441443
const { sendTransforms, isLive, onCodeReceived } = this.props;
442444

443-
if (sendTransforms && this.changes.changes) {
445+
if (
446+
sendTransforms &&
447+
this.changes.changes &&
448+
this.changes.moduleShortid === this.currentModule.shortid
449+
) {
444450
let code = this.changes.code;
445451
const t = this.changes.changes
446452
.map(change => {
@@ -483,7 +489,7 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
483489
} else if (!isLive && onCodeReceived) {
484490
onCodeReceived();
485491
}
486-
this.changes = { code: '', changes: [] };
492+
this.changes = { moduleShortid: null, code: '', changes: [] };
487493
};
488494

489495
userClassesGenerated = {};
@@ -580,6 +586,7 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
580586
fontWeight: 600,
581587
userSelect: 'none',
582588
pointerEvents: 'none',
589+
width: 'max-content',
583590
};
584591
this.userClassesGenerated[cursorClassName] = `${css({
585592
backgroundColor: `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.8)`,

packages/app/src/app/components/Preview/index.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,12 @@ class BasePreview extends React.Component<Props, State> {
225225

226226
sandbox.modules.forEach(m => {
227227
const path = getModulePath(sandbox.modules, sandbox.directories, m.id);
228-
modulesObject[path] = {
229-
path,
230-
code: m.code,
231-
};
228+
if (path) {
229+
modulesObject[path] = {
230+
path,
231+
code: m.code,
232+
};
233+
}
232234
});
233235

234236
const extraModules = this.props.extraModules || {};

packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/DirectoryChildren/ModuleEntry.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@ class ModuleEntry extends React.Component {
3131
error => error.moduleId === module.id
3232
).length;
3333

34+
const liveUsers = store.live.liveUsersByModule[module.shortid] || [];
35+
3436
return (
3537
<Entry
3638
id={module.id}
3739
shortid={module.shortid}
3840
title={module.title}
41+
rightColors={liveUsers.map(([a, b, c]) => `rgb(${a}, ${b}, ${c})`)}
3942
depth={depth + 1}
4043
active={isActive}
4144
type={type || 'function'}

packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/Entry/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class Entry extends React.PureComponent {
8787
isMainModule,
8888
moduleHasError,
8989
root,
90+
rightColors,
9091
} = this.props;
9192
const { state, error, selected, hovering } = this.state;
9293

@@ -127,6 +128,7 @@ class Entry extends React.PureComponent {
127128
onMouseEnter={this.onMouseEnter}
128129
onMouseLeave={this.onMouseLeave}
129130
alternative={isMainModule}
131+
rightColors={rightColors}
130132
noTransition
131133
>
132134
<EntryIcons

packages/app/src/app/pages/Sandbox/Editor/Workspace/elements.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ export const getContainerStyles = props => {
6767
`;
6868
}
6969

70+
if (props.rightColors) {
71+
styles += `
72+
border-right: 2px solid ${props.rightColors[0]};
73+
`;
74+
}
75+
7076
return styles;
7177
};
7278

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

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import React from 'react';
22
import styled from 'styled-components';
3+
import { observer } from 'mobx-react';
34

45
import RecordIcon from 'react-icons/lib/md/fiber-manual-record';
56
import Input from 'common/components/Input';
7+
import Margin from 'common/components/spacing/Margin';
68
import delay from 'common/utils/animation/delay-effect';
79

10+
import User from './User';
11+
812
import {
913
Description,
1014
WorkspaceInputContainer,
@@ -56,13 +60,74 @@ const SubTitle = styled.div`
5660
font-size: 0.875rem;
5761
`;
5862

59-
export default class LiveInfo extends React.PureComponent {
63+
const Users = styled.div`
64+
padding: 0.25rem 1rem;
65+
color: rgba(255, 255, 255, 0.8);
66+
`;
67+
68+
const ModeSelect = styled.div`
69+
position: relative;
70+
margin: 0.5rem 1rem;
71+
`;
72+
73+
const Mode = styled.button`
74+
display: block;
75+
text-align: left;
76+
transition: 0.3s ease opacity;
77+
padding: 0.5rem 1rem;
78+
color: white;
79+
border-radius: 4px;
80+
width: 100%;
81+
82+
font-weight: 600;
83+
border: none;
84+
outline: none;
85+
background-color: transparent;
86+
cursor: pointer;
87+
color: white;
88+
opacity: ${props => (props.selected ? 1 : 0.6)};
89+
margin: 0.25rem 0;
90+
91+
z-index: 3;
92+
93+
&:hover {
94+
opacity: 1;
95+
}
96+
`;
97+
98+
const ModeDetails = styled.div`
99+
font-size: 0.75rem;
100+
color: rgba(255, 255, 255, 0.7);
101+
margin-top: 0.25rem;
102+
`;
103+
104+
const ModeSelector = styled.div`
105+
transition: 0.3s ease transform;
106+
position: absolute;
107+
left: 0;
108+
right: 0;
109+
top: 0;
110+
height: 48px;
111+
112+
border: 2px solid rgba(253, 36, 57, 0.6);
113+
background-color: rgba(253, 36, 57, 0.6);
114+
border-radius: 4px;
115+
z-index: -1;
116+
117+
transform: translateY(${props => props.i * 55}px);
118+
`;
119+
120+
class LiveInfo extends React.Component {
60121
select = e => {
61122
e.target.select();
62123
};
63124

64125
render() {
65-
const { roomInfo, isOwner } = this.props;
126+
const { roomInfo, isOwner, ownerId, setMode } = this.props;
127+
128+
const owner = roomInfo.users.find(u => u.id === ownerId);
129+
const otherUsers = roomInfo.users.filter(x => x.id !== ownerId);
130+
66131
return (
67132
<Container>
68133
<Title>
@@ -75,14 +140,71 @@ export default class LiveInfo extends React.PureComponent {
75140
onFocus={this.select}
76141
value={`https://codesandbox.io/live/${roomInfo.roomId}`}
77142
/>
78-
<Separator />
79143

80144
{isOwner && (
81-
<div>
82-
<SubTitle>Session Settings</SubTitle>
83-
</div>
145+
<Margin top={1}>
146+
<SubTitle>Live Mode</SubTitle>
147+
<ModeSelect>
148+
<ModeSelector i={roomInfo.mode === 'open' ? 0 : 1} />
149+
<Mode
150+
onClick={() => setMode({ mode: 'open' })}
151+
selected={roomInfo.mode === 'open'}
152+
>
153+
<div>Open</div>
154+
<ModeDetails>Everyone can edit</ModeDetails>
155+
</Mode>
156+
<Mode
157+
onClick={() => setMode({ mode: 'classroom' })}
158+
selected={roomInfo.mode === 'classroom'}
159+
>
160+
<div>Classroom</div>
161+
<ModeDetails>Fine grained control over editors</ModeDetails>
162+
</Mode>
163+
</ModeSelect>
164+
</Margin>
165+
)}
166+
167+
{owner && (
168+
<Margin top={1}>
169+
<SubTitle>Owner</SubTitle>
170+
<Users>
171+
<User user={owner} roomInfo={roomInfo} type="Sandbox Owner" />
172+
</Users>
173+
</Margin>
84174
)}
175+
176+
<Margin top={1}>
177+
<SubTitle>Users</SubTitle>
178+
179+
<Users>
180+
{otherUsers.length ? (
181+
otherUsers.map(user => (
182+
<User
183+
key={user.id}
184+
showSwitch={roomInfo.mode === 'classroom'}
185+
switchOn={false}
186+
user={user}
187+
roomInfo={roomInfo}
188+
type="Spectator"
189+
/>
190+
))
191+
) : (
192+
<div
193+
style={{
194+
color: 'rgba(255, 255, 255, 0.8)',
195+
fontWeight: 600,
196+
fontSize: '.875rem',
197+
marginTop: '0.25rem',
198+
}}
199+
>
200+
No other users in session, invite them!
201+
</div>
202+
)}
203+
</Users>
204+
</Margin>
85205
</Container>
86206
);
87207
}
88208
}
209+
210+
export default observer(LiveInfo);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
import { observer } from 'mobx-react';
4+
5+
import Switch from 'common/components/Switch';
6+
import delay from 'common/utils/animation/delay-effect';
7+
8+
const Status = styled.div`
9+
font-size: 0.75rem;
10+
color: rgba(255, 255, 255, 0.6);
11+
`;
12+
13+
const UserContainer = styled.div`
14+
display: flex;
15+
align-items: center;
16+
margin: 0.5rem 0;
17+
`;
18+
19+
const ProfileImage = styled.img`
20+
width: 26px;
21+
height: 26px;
22+
border-radius: 2px;
23+
border-left: 2px solid ${({ borderColor }) => borderColor};
24+
25+
margin-right: 0.5rem;
26+
`;
27+
28+
const UserName = styled.div`
29+
font-weight: 600;
30+
font-size: 0.875rem;
31+
`;
32+
33+
const StyledSwitch = styled(Switch)`
34+
${delay()};
35+
`;
36+
37+
// eslint-disable-next-line
38+
class User extends React.Component {
39+
render() {
40+
const { user, type, showSwitch, switchOn, roomInfo } = this.props;
41+
42+
const metaData = roomInfo.usersMetadata.get(user.id);
43+
const [r, g, b] = metaData
44+
? roomInfo.usersMetadata.get(user.id).color
45+
: [0, 0, 0];
46+
47+
return (
48+
<UserContainer>
49+
<ProfileImage
50+
src={user.avatarUrl}
51+
alt={user.username}
52+
borderColor={`rgba(${r}, ${g}, ${b}, 0.7)`}
53+
/>
54+
<div style={{ flex: 1 }}>
55+
<UserName>{user.username}</UserName>
56+
{type && <Status>{type}</Status>}
57+
</div>
58+
{showSwitch && (
59+
<div>
60+
<Switch offMode right={switchOn} small />
61+
</div>
62+
)}
63+
</UserContainer>
64+
);
65+
}
66+
}
67+
68+
export default observer(User);

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ const Live = ({ signals, store }) => {
1515
return (
1616
<div>
1717
{store.live.isLive ? (
18-
<LiveInfo isOwner={store.live.isOwner} roomInfo={store.live.roomInfo} />
18+
<LiveInfo
19+
setMode={signals.live.onModeChanged}
20+
isOwner={store.live.isOwner}
21+
roomInfo={store.live.roomInfo}
22+
ownerId={store.editor.currentSandbox.author.id}
23+
/>
1924
) : (
2025
<React.Fragment>
2126
<Description style={{ marginBottom: '1rem' }}>

0 commit comments

Comments
 (0)