Skip to content

Collaborative Editing Feature #530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,060 changes: 4,804 additions & 256 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"redux": "^3.7.2",
"redux-mock-store": "^1.5.1",
"redux-saga": "^0.15.6",
"sharedb-ace": "^1.0.9",
"showdown": "^1.9.0",
"typesafe-actions": "^3.2.1",
"utility-types": "^2.0.0"
Expand Down
3 changes: 3 additions & 0 deletions src/actions/actionTypes.ts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ export const END_CLEAR_CONTEXT = 'END_CLEAR_CONTEXT';
export const ENSURE_LIBRARIES_LOADED = 'ENSURE_LIBRARIES_LOADED';
export const EVAL_EDITOR = 'EVAL_EDITOR';
export const EVAL_REPL = 'EVAL_REPL';
export const INVALID_EDITOR_SESSION_ID = 'INVALID_EDITOR_SESSION_ID';
export const PLAYGROUND_EXTERNAL_SELECT = 'PLAYGROUND_EXTERNAL_SELECT ';
export const RESET_WORKSPACE = 'RESET_WORKSPACE';
export const SEND_REPL_INPUT_TO_OUTPUT = 'SEND_REPL_INPUT_TO_OUTPUT';
export const SET_EDITOR_SESSION_ID = 'SET_EDITOR_SESSION_ID';
export const SET_WEBSOCKET_STATUS = 'SET_WEBSOCKET_STATUS';
export const TOGGLE_EDITOR_AUTORUN = 'TOGGLE_EDITOR_AUTORUN';
export const UPDATE_CURRENT_ASSESSMENT_ID = 'UPDATE_CURRENT_ASSESSMENT_ID';
export const UPDATE_CURRENT_SUBMISSION_ID = 'UPDATE_CURRENT_SUBMISSION_ID';
Expand Down
32 changes: 32 additions & 0 deletions src/actions/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ export const evalRepl = (workspaceLocation: WorkspaceLocation) => ({
payload: { workspaceLocation }
});

export const invalidEditorSessionId = () => ({
type: actionTypes.INVALID_EDITOR_SESSION_ID
});

export const updateEditorValue: ActionCreator<actionTypes.IAction> = (
newEditorValue: string,
workspaceLocation: WorkspaceLocation
Expand Down Expand Up @@ -209,6 +213,34 @@ export const resetWorkspace = (
}
});

export const setEditorSessionId: ActionCreator<actionTypes.IAction> = (
workspaceLocation: WorkspaceLocation,
editorSessionId: string
) => ({
type: actionTypes.SET_EDITOR_SESSION_ID,
payload: {
workspaceLocation,
editorSessionId
}
});

/**
* Sets sharedb websocket status.
*
* @param workspaceLocation the workspace to be reset
* @param websocketStatus 0: CLOSED 1: OPEN
*/
export const setWebsocketStatus: ActionCreator<actionTypes.IAction> = (
workspaceLocation: WorkspaceLocations,
websocketStatus: number
) => ({
type: actionTypes.SET_WEBSOCKET_STATUS,
payload: {
workspaceLocation,
websocketStatus
}
});

export const updateCurrentAssessmentId = (assessmentId: number, questionId: number) => ({
type: actionTypes.UPDATE_CURRENT_ASSESSMENT_ID,
payload: {
Expand Down
16 changes: 15 additions & 1 deletion src/components/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface IPlaygroundProps extends IDispatchProps, IStateProps, RouteComp

export interface IStateProps {
activeTab: number;
editorSessionId: string;
editorValue: string;
editorWidth: string;
isEditorAutorun: boolean;
Expand All @@ -43,6 +44,7 @@ export interface IStateProps {
replValue: string;
sideContentHeight?: number;
sourceChapter: number;
websocketStatus: number;
externalLibraryName: string;
}

Expand All @@ -56,10 +58,13 @@ export interface IDispatchProps {
handleEditorWidthChange: (widthChange: number) => void;
handleGenerateLz: () => void;
handleInterruptEval: () => void;
handleInvalidEditorSessionId: () => void;
handleExternalSelect: (externalLibraryName: ExternalLibraryName) => void;
handleReplEval: () => void;
handleReplOutputClear: () => void;
handleReplValueChange: (newValue: string) => void;
handleSetEditorSessionId: (editorSessionId: string) => void;
handleSetWebsocketStatus: (websocketStatus: number) => void;
handleSideContentHeightChange: (heightChange: number) => void;
handleToggleEditorAutorun: () => void;
}
Expand All @@ -82,31 +87,40 @@ class Playground extends React.Component<IPlaygroundProps, PlaygroundState> {
public render() {
const workspaceProps: WorkspaceProps = {
controlBarProps: {
editorValue: this.props.editorValue,
editorSessionId: this.props.editorSessionId,
externalLibraryName: this.props.externalLibraryName,
handleChapterSelect: ({ chapter }: { chapter: number }, e: any) =>
this.props.handleChapterSelect(chapter),
handleExternalSelect: ({ name }: { name: ExternalLibraryName }, e: any) =>
this.props.handleExternalSelect(name),
handleEditorEval: this.props.handleEditorEval,
handleEditorValueChange: this.props.handleEditorValueChange,
handleGenerateLz: this.props.handleGenerateLz,
handleInterruptEval: this.props.handleInterruptEval,
handleInvalidEditorSessionId: this.props.handleInvalidEditorSessionId,
handleReplEval: this.props.handleReplEval,
handleReplOutputClear: this.props.handleReplOutputClear,
handleSetEditorSessionId: this.props.handleSetEditorSessionId,
handleToggleEditorAutorun: this.props.handleToggleEditorAutorun,
hasChapterSelect: true,
hasCollabEditing: true,
hasEditorAutorunButton: true,
hasSaveButton: false,
hasShareButton: true,
isEditorAutorun: this.props.isEditorAutorun,
isRunning: this.props.isRunning,
queryString: this.props.queryString,
questionProgress: null,
sourceChapter: this.props.sourceChapter
sourceChapter: this.props.sourceChapter,
websocketStatus: this.props.websocketStatus
},
editorProps: {
editorValue: this.props.editorValue,
editorSessionId: this.props.editorSessionId,
handleEditorEval: this.props.handleEditorEval,
handleEditorValueChange: this.props.handleEditorValueChange,
handleSetWebsocketStatus: this.props.handleSetWebsocketStatus,
isEditorAutorun: this.props.isEditorAutorun
},
editorWidth: this.props.editorWidth,
Expand Down
5 changes: 5 additions & 0 deletions src/components/__tests__/Playground.tsx
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ const baseProps = {
editorValue: '',
isRunning: false,
activeTab: 0,
editorSessionId: '',
editorWidth: '50%',
isEditorAutorun: false,
sideContentHeight: 40,
sourceChapter: 2,
externalLibraryName: ExternalLibraryNames.NONE,
output: [],
replValue: '',
websocketStatus: 0,
handleBrowseHistoryDown: () => {},
handleBrowseHistoryUp: () => {},
handleChangeActiveTab: (n: number) => {},
Expand All @@ -26,9 +28,12 @@ const baseProps = {
handleExternalSelect: (externalLibraryName: ExternalLibraryName) => {},
handleGenerateLz: () => {},
handleInterruptEval: () => {},
handleInvalidEditorSessionId: () => {},
handleReplEval: () => {},
handleReplOutputClear: () => {},
handleReplValueChange: (code: string) => {},
handleSetEditorSessionId: (editorSessionId: string) => {},
handleSetWebsocketStatus: (websocketStatus: number) => {},
handleSideContentHeightChange: (h: number) => {},
handleToggleEditorAutorun: () => {}
};
Expand Down
5 changes: 4 additions & 1 deletion src/components/academy/grading/GradingWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,11 @@ class GradingWorkspace extends React.Component<GradingWorkspaceProps> {
editorProps:
question.type === QuestionTypes.programming
? {
editorSessionId: '',
editorValue: editorValue!,
handleEditorEval: this.props.handleEditorEval,
handleEditorValueChange: this.props.handleEditorValueChange
handleEditorValueChange: this.props.handleEditorValueChange,
isEditorAutorun: false
}
: undefined,
editorWidth: this.props.editorWidth,
Expand Down Expand Up @@ -220,6 +222,7 @@ class GradingWorkspace extends React.Component<GradingWorkspaceProps> {
handleReplEval: this.props.handleReplEval,
handleReplOutputClear: this.props.handleReplOutputClear,
hasChapterSelect: false,
hasCollabEditing: false,
hasEditorAutorunButton: false,
hasSaveButton: false,
hasShareButton: false,
Expand Down
5 changes: 4 additions & 1 deletion src/components/assessment/AssessmentWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,12 @@ class AssessmentWorkspace extends React.Component<
editorProps:
question.type === QuestionTypes.programming
? {
editorSessionId: '',
editorValue: this.props.editorValue!,
handleEditorEval: this.props.handleEditorEval,
handleEditorValueChange: this.props.handleEditorValueChange,
handleUpdateHasUnsavedChanges: this.props.handleUpdateHasUnsavedChanges
handleUpdateHasUnsavedChanges: this.props.handleUpdateHasUnsavedChanges,
isEditorAutorun: false
}
: undefined,
editorWidth: this.props.editorWidth,
Expand Down Expand Up @@ -327,6 +329,7 @@ class AssessmentWorkspace extends React.Component<
handleReplOutputClear: this.props.handleReplOutputClear,
handleReplValueChange: this.props.handleReplValueChange,
hasChapterSelect: false,
hasCollabEditing: false,
hasEditorAutorunButton: false,
hasSaveButton:
!beforeNow(this.props.closeDate) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ exports[`AssessmentWorkspace page with MCQ question renders correctly 1`] = `
</div>
<div className=\\"pt-dialog-footer\\">
<Blueprint2.ButtonGroup>
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" onClick={[Function]}>
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
Cancel
</Blueprint2.Button>
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" onClick={[Function]}>
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
Confirm
</Blueprint2.Button>
</Blueprint2.ButtonGroup>
Expand All @@ -45,10 +45,10 @@ exports[`AssessmentWorkspace page with overdue assessment renders correctly 1`]
</div>
<div className=\\"pt-dialog-footer\\">
<Blueprint2.ButtonGroup>
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" onClick={[Function]}>
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
Cancel
</Blueprint2.Button>
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" onClick={[Function]}>
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
Confirm
</Blueprint2.Button>
</Blueprint2.ButtonGroup>
Expand All @@ -73,10 +73,10 @@ exports[`AssessmentWorkspace page with programming question renders correctly 1`
</div>
<div className=\\"pt-dialog-footer\\">
<Blueprint2.ButtonGroup>
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" onClick={[Function]}>
<Blueprint2.Button disabled={false} fill={false} intent=\\"none\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
Cancel
</Blueprint2.Button>
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" onClick={[Function]}>
<Blueprint2.Button disabled={false} fill={false} intent=\\"danger\\" minimal={false} className=\\"\\" type=\\"\\" onClick={[Function]}>
Confirm
</Blueprint2.Button>
</Blueprint2.ButtonGroup>
Expand Down
Loading