Skip to content

Commit 5b2a865

Browse files
ccyccyccymartin-henz
authored andcommitted
Collaborative Editing Feature (#530)
* Add collaborative-editing feature using sharedb-ace * Allow multiple sessions by implementing session id * Add auto-completion, and change info annotation to error * Add notifications for autorun and websocket connection * Allow detection on invalid session id * Update snapshot and pass test * Add working connection checks and remove autocompletion * Add leave button and color indicator, remove status button * Change server url from ip address to domain name * Get client to ping server instead of the other way round * Sync editorValue after inviting * Remove console-log, reset ts-config * Reset css back to original * Stored api URL in constant, changed invite icon * Fixed bug with invite not working
1 parent e3159bf commit 5b2a865

File tree

22 files changed

+5314
-399
lines changed

22 files changed

+5314
-399
lines changed

package-lock.json

Lines changed: 4804 additions & 256 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

100644100755
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"redux": "^3.7.2",
7070
"redux-mock-store": "^1.5.1",
7171
"redux-saga": "^0.15.6",
72+
"sharedb-ace": "^1.0.9",
7273
"showdown": "^1.9.0",
7374
"typesafe-actions": "^3.2.1",
7475
"utility-types": "^2.0.0"

src/actions/actionTypes.ts

100644100755
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ export const END_CLEAR_CONTEXT = 'END_CLEAR_CONTEXT';
3636
export const ENSURE_LIBRARIES_LOADED = 'ENSURE_LIBRARIES_LOADED';
3737
export const EVAL_EDITOR = 'EVAL_EDITOR';
3838
export const EVAL_REPL = 'EVAL_REPL';
39+
export const INVALID_EDITOR_SESSION_ID = 'INVALID_EDITOR_SESSION_ID';
3940
export const PLAYGROUND_EXTERNAL_SELECT = 'PLAYGROUND_EXTERNAL_SELECT ';
4041
export const RESET_WORKSPACE = 'RESET_WORKSPACE';
4142
export const SEND_REPL_INPUT_TO_OUTPUT = 'SEND_REPL_INPUT_TO_OUTPUT';
43+
export const SET_EDITOR_SESSION_ID = 'SET_EDITOR_SESSION_ID';
44+
export const SET_WEBSOCKET_STATUS = 'SET_WEBSOCKET_STATUS';
4245
export const TOGGLE_EDITOR_AUTORUN = 'TOGGLE_EDITOR_AUTORUN';
4346
export const UPDATE_CURRENT_ASSESSMENT_ID = 'UPDATE_CURRENT_ASSESSMENT_ID';
4447
export const UPDATE_CURRENT_SUBMISSION_ID = 'UPDATE_CURRENT_SUBMISSION_ID';

src/actions/workspaces.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ export const evalRepl = (workspaceLocation: WorkspaceLocation) => ({
161161
payload: { workspaceLocation }
162162
});
163163

164+
export const invalidEditorSessionId = () => ({
165+
type: actionTypes.INVALID_EDITOR_SESSION_ID
166+
});
167+
164168
export const updateEditorValue: ActionCreator<actionTypes.IAction> = (
165169
newEditorValue: string,
166170
workspaceLocation: WorkspaceLocation
@@ -209,6 +213,34 @@ export const resetWorkspace = (
209213
}
210214
});
211215

216+
export const setEditorSessionId: ActionCreator<actionTypes.IAction> = (
217+
workspaceLocation: WorkspaceLocation,
218+
editorSessionId: string
219+
) => ({
220+
type: actionTypes.SET_EDITOR_SESSION_ID,
221+
payload: {
222+
workspaceLocation,
223+
editorSessionId
224+
}
225+
});
226+
227+
/**
228+
* Sets sharedb websocket status.
229+
*
230+
* @param workspaceLocation the workspace to be reset
231+
* @param websocketStatus 0: CLOSED 1: OPEN
232+
*/
233+
export const setWebsocketStatus: ActionCreator<actionTypes.IAction> = (
234+
workspaceLocation: WorkspaceLocations,
235+
websocketStatus: number
236+
) => ({
237+
type: actionTypes.SET_WEBSOCKET_STATUS,
238+
payload: {
239+
workspaceLocation,
240+
websocketStatus
241+
}
242+
});
243+
212244
export const updateCurrentAssessmentId = (assessmentId: number, questionId: number) => ({
213245
type: actionTypes.UPDATE_CURRENT_ASSESSMENT_ID,
214246
payload: {

src/components/Playground.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface IPlaygroundProps extends IDispatchProps, IStateProps, RouteComp
3434

3535
export interface IStateProps {
3636
activeTab: number;
37+
editorSessionId: string;
3738
editorValue: string;
3839
editorWidth: string;
3940
isEditorAutorun: boolean;
@@ -43,6 +44,7 @@ export interface IStateProps {
4344
replValue: string;
4445
sideContentHeight?: number;
4546
sourceChapter: number;
47+
websocketStatus: number;
4648
externalLibraryName: string;
4749
}
4850

@@ -56,10 +58,13 @@ export interface IDispatchProps {
5658
handleEditorWidthChange: (widthChange: number) => void;
5759
handleGenerateLz: () => void;
5860
handleInterruptEval: () => void;
61+
handleInvalidEditorSessionId: () => void;
5962
handleExternalSelect: (externalLibraryName: ExternalLibraryName) => void;
6063
handleReplEval: () => void;
6164
handleReplOutputClear: () => void;
6265
handleReplValueChange: (newValue: string) => void;
66+
handleSetEditorSessionId: (editorSessionId: string) => void;
67+
handleSetWebsocketStatus: (websocketStatus: number) => void;
6368
handleSideContentHeightChange: (heightChange: number) => void;
6469
handleToggleEditorAutorun: () => void;
6570
}
@@ -82,31 +87,40 @@ class Playground extends React.Component<IPlaygroundProps, PlaygroundState> {
8287
public render() {
8388
const workspaceProps: WorkspaceProps = {
8489
controlBarProps: {
90+
editorValue: this.props.editorValue,
91+
editorSessionId: this.props.editorSessionId,
8592
externalLibraryName: this.props.externalLibraryName,
8693
handleChapterSelect: ({ chapter }: { chapter: number }, e: any) =>
8794
this.props.handleChapterSelect(chapter),
8895
handleExternalSelect: ({ name }: { name: ExternalLibraryName }, e: any) =>
8996
this.props.handleExternalSelect(name),
9097
handleEditorEval: this.props.handleEditorEval,
98+
handleEditorValueChange: this.props.handleEditorValueChange,
9199
handleGenerateLz: this.props.handleGenerateLz,
92100
handleInterruptEval: this.props.handleInterruptEval,
101+
handleInvalidEditorSessionId: this.props.handleInvalidEditorSessionId,
93102
handleReplEval: this.props.handleReplEval,
94103
handleReplOutputClear: this.props.handleReplOutputClear,
104+
handleSetEditorSessionId: this.props.handleSetEditorSessionId,
95105
handleToggleEditorAutorun: this.props.handleToggleEditorAutorun,
96106
hasChapterSelect: true,
107+
hasCollabEditing: true,
97108
hasEditorAutorunButton: true,
98109
hasSaveButton: false,
99110
hasShareButton: true,
100111
isEditorAutorun: this.props.isEditorAutorun,
101112
isRunning: this.props.isRunning,
102113
queryString: this.props.queryString,
103114
questionProgress: null,
104-
sourceChapter: this.props.sourceChapter
115+
sourceChapter: this.props.sourceChapter,
116+
websocketStatus: this.props.websocketStatus
105117
},
106118
editorProps: {
107119
editorValue: this.props.editorValue,
120+
editorSessionId: this.props.editorSessionId,
108121
handleEditorEval: this.props.handleEditorEval,
109122
handleEditorValueChange: this.props.handleEditorValueChange,
123+
handleSetWebsocketStatus: this.props.handleSetWebsocketStatus,
110124
isEditorAutorun: this.props.isEditorAutorun
111125
},
112126
editorWidth: this.props.editorWidth,

src/components/__tests__/Playground.tsx

100644100755
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ const baseProps = {
99
editorValue: '',
1010
isRunning: false,
1111
activeTab: 0,
12+
editorSessionId: '',
1213
editorWidth: '50%',
1314
isEditorAutorun: false,
1415
sideContentHeight: 40,
1516
sourceChapter: 2,
1617
externalLibraryName: ExternalLibraryNames.NONE,
1718
output: [],
1819
replValue: '',
20+
websocketStatus: 0,
1921
handleBrowseHistoryDown: () => {},
2022
handleBrowseHistoryUp: () => {},
2123
handleChangeActiveTab: (n: number) => {},
@@ -26,9 +28,12 @@ const baseProps = {
2628
handleExternalSelect: (externalLibraryName: ExternalLibraryName) => {},
2729
handleGenerateLz: () => {},
2830
handleInterruptEval: () => {},
31+
handleInvalidEditorSessionId: () => {},
2932
handleReplEval: () => {},
3033
handleReplOutputClear: () => {},
3134
handleReplValueChange: (code: string) => {},
35+
handleSetEditorSessionId: (editorSessionId: string) => {},
36+
handleSetWebsocketStatus: (websocketStatus: number) => {},
3237
handleSideContentHeightChange: (h: number) => {},
3338
handleToggleEditorAutorun: () => {}
3439
};

src/components/academy/grading/GradingWorkspace.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,11 @@ class GradingWorkspace extends React.Component<GradingWorkspaceProps> {
105105
editorProps:
106106
question.type === QuestionTypes.programming
107107
? {
108+
editorSessionId: '',
108109
editorValue: editorValue!,
109110
handleEditorEval: this.props.handleEditorEval,
110-
handleEditorValueChange: this.props.handleEditorValueChange
111+
handleEditorValueChange: this.props.handleEditorValueChange,
112+
isEditorAutorun: false
111113
}
112114
: undefined,
113115
editorWidth: this.props.editorWidth,
@@ -220,6 +222,7 @@ class GradingWorkspace extends React.Component<GradingWorkspaceProps> {
220222
handleReplEval: this.props.handleReplEval,
221223
handleReplOutputClear: this.props.handleReplOutputClear,
222224
hasChapterSelect: false,
225+
hasCollabEditing: false,
223226
hasEditorAutorunButton: false,
224227
hasSaveButton: false,
225228
hasShareButton: false,

src/components/assessment/AssessmentWorkspace.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,12 @@ class AssessmentWorkspace extends React.Component<
191191
editorProps:
192192
question.type === QuestionTypes.programming
193193
? {
194+
editorSessionId: '',
194195
editorValue: this.props.editorValue!,
195196
handleEditorEval: this.props.handleEditorEval,
196197
handleEditorValueChange: this.props.handleEditorValueChange,
197-
handleUpdateHasUnsavedChanges: this.props.handleUpdateHasUnsavedChanges
198+
handleUpdateHasUnsavedChanges: this.props.handleUpdateHasUnsavedChanges,
199+
isEditorAutorun: false
198200
}
199201
: undefined,
200202
editorWidth: this.props.editorWidth,
@@ -327,6 +329,7 @@ class AssessmentWorkspace extends React.Component<
327329
handleReplOutputClear: this.props.handleReplOutputClear,
328330
handleReplValueChange: this.props.handleReplValueChange,
329331
hasChapterSelect: false,
332+
hasCollabEditing: false,
330333
hasEditorAutorunButton: false,
331334
hasSaveButton:
332335
!beforeNow(this.props.closeDate) &&

src/components/assessment/__tests__/__snapshots__/AssessmentWorkspace.tsx.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ exports[`AssessmentWorkspace page with MCQ question renders correctly 1`] = `
1717
</div>
1818
<div className=\\"pt-dialog-footer\\">
1919
<Blueprint2.ButtonGroup>
20-
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" onClick={[Function]}>
20+
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
2121
Cancel
2222
</Blueprint2.Button>
23-
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" onClick={[Function]}>
23+
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
2424
Confirm
2525
</Blueprint2.Button>
2626
</Blueprint2.ButtonGroup>
@@ -45,10 +45,10 @@ exports[`AssessmentWorkspace page with overdue assessment renders correctly 1`]
4545
</div>
4646
<div className=\\"pt-dialog-footer\\">
4747
<Blueprint2.ButtonGroup>
48-
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" onClick={[Function]}>
48+
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
4949
Cancel
5050
</Blueprint2.Button>
51-
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" onClick={[Function]}>
51+
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
5252
Confirm
5353
</Blueprint2.Button>
5454
</Blueprint2.ButtonGroup>
@@ -73,10 +73,10 @@ exports[`AssessmentWorkspace page with programming question renders correctly 1`
7373
</div>
7474
<div className=\\"pt-dialog-footer\\">
7575
<Blueprint2.ButtonGroup>
76-
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" onClick={[Function]}>
76+
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
7777
Cancel
7878
</Blueprint2.Button>
79-
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" onClick={[Function]}>
79+
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
8080
Confirm
8181
</Blueprint2.Button>
8282
</Blueprint2.ButtonGroup>

0 commit comments

Comments
 (0)