From 86f4c7d748a12b1cf450c57ea9f011d6f41a85ae Mon Sep 17 00:00:00 2001 From: ning Date: Fri, 13 Jul 2018 18:15:16 +0800 Subject: [PATCH 01/11] Add property replHistory to workspace state --- src/reducers/states.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/reducers/states.ts b/src/reducers/states.ts index f4c13fdbae..bd60b34475 100644 --- a/src/reducers/states.ts +++ b/src/reducers/states.ts @@ -45,6 +45,7 @@ interface IWorkspaceState { readonly editorValue: string readonly editorWidth: string readonly output: InterpreterOutput[] + readonly replHistory: ReplHistory readonly replValue: string readonly sideContentActiveTab: number readonly sideContentHeight?: number @@ -65,6 +66,11 @@ export interface ISessionState { readonly username?: string } +type ReplHistory = { + isBrowsingIndex: null | number // [0, 49] if browsing, else null + records: string[] +} + /** * An output while the program is still being run in the interpreter. As a * result, there are no return values or SourceErrors yet. However, there could @@ -189,6 +195,10 @@ export const createDefaultWorkspace = (location: WorkspaceLocation): IWorkspaceS editorValue: defaultEditorValue, editorWidth: '50%', output: [], + replHistory: { + isBrowsingIndex: null, + records: [] + }, replValue: '', sideContentActiveTab: 0, externals: [] From 7ee45e074b26ec7d7709dc5b174b1f21dd892401 Mon Sep 17 00:00:00 2001 From: ning Date: Fri, 13 Jul 2018 18:23:09 +0800 Subject: [PATCH 02/11] Define actions, ActionCreators for ReplHistory --- src/actions/actionTypes.ts | 2 ++ src/actions/workspaces.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index bda0b9c966..14e1dca8b2 100644 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -19,6 +19,8 @@ export const EVAL_INTERPRETER_SUCCESS = 'EVAL_INTERPRETER_SUCCESS' export const HANDLE_CONSOLE_LOG = 'HANDLE_CONSOLE_LOG' /** Workspace */ +export const BROWSE_REPL_HISTORY_DOWN = 'BROWSE_REPL_HISTORY_DOWN' +export const BROWSE_REPL_HISTORY_UP = 'BROWSE_REPL_HISTORY_UP' export const CHANGE_ACTIVE_TAB = 'CHANGE_ACTIVE_TAB' export const CHANGE_EDITOR_WIDTH = 'CHANGE_EDITOR_WIDTH' export const CHANGE_PLAYGROUND_EXTERNAL = 'CHANGE_PLAYGROUND_EXTERNAL' diff --git a/src/actions/workspaces.ts b/src/actions/workspaces.ts index 4b5403efba..d0f5f7054f 100644 --- a/src/actions/workspaces.ts +++ b/src/actions/workspaces.ts @@ -15,8 +15,23 @@ export enum WorkspaceLocations { assessment = 'assessment', playground = 'playground' } + export type WorkspaceLocation = keyof typeof WorkspaceLocations +export const browseReplHistoryDown: ActionCreator = ( + workspaceLocation: WorkspaceLocation +) => ({ + type: actionTypes.BROWSE_REPL_HISTORY_DOWN, + payload: { workspaceLocation } +}) + +export const browseReplHistoryUp: ActionCreator = ( + workspaceLocation: WorkspaceLocation +) => ({ + type: actionTypes.BROWSE_REPL_HISTORY_UP, + payload: { workspaceLocation } +}) + export const changeActiveTab: ActionCreator = ( activeTab: number, workspaceLocation: WorkspaceLocation From 7d6eec4555a6448e78f5f3ccea1207a77ed6ac57 Mon Sep 17 00:00:00 2001 From: ning Date: Fri, 13 Jul 2018 18:34:18 +0800 Subject: [PATCH 03/11] Remove reducer cases for EVAL_[REPL|EDITOR] --- src/reducers/workspaces.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index 41a4d012d0..fabd7df87d 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -9,10 +9,8 @@ import { CLEAR_REPL_INPUT, CLEAR_REPL_OUTPUT, END_INTERRUPT_EXECUTION, - EVAL_EDITOR, EVAL_INTERPRETER_ERROR, EVAL_INTERPRETER_SUCCESS, - EVAL_REPL, HANDLE_CONSOLE_LOG, IAction, RESET_ASSESSMENT_WORKSPACE, @@ -149,20 +147,6 @@ export const reducer: Reducer = ( output: newOutput } } - case EVAL_EDITOR: - return { - ...state, - [location]: { - ...state[location] - } - } - case EVAL_REPL: - return { - ...state, - [location]: { - ...state[location] - } - } case EVAL_INTERPRETER_SUCCESS: lastOutput = state[location].output.slice(-1)[0] if (lastOutput !== undefined && lastOutput.type === 'running') { From f0d469df6c650c72f141a4739c6f61295cf3b564 Mon Sep 17 00:00:00 2001 From: ning Date: Fri, 13 Jul 2018 19:01:25 +0800 Subject: [PATCH 04/11] Push codeOutput to replHistory in workspace reducer --- src/reducers/workspaces.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index fabd7df87d..94252b8ec4 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -140,11 +140,21 @@ export const reducer: Reducer = ( case SEND_REPL_INPUT_TO_OUTPUT: // CodeOutput properties exist in parallel with workspaceLocation newOutput = state[location].output.concat(action.payload as CodeOutput) + const newReplHistoryRecords = [action.payload.value].concat( + state[location].replHistory.records + ) + if (newReplHistoryRecords.length > 50) { + newReplHistoryRecords.pop() + } return { ...state, [location]: { ...state[location], - output: newOutput + output: newOutput, + replHistory: { + ...state[location].replHistory, + records: newReplHistoryRecords + } } } case EVAL_INTERPRETER_SUCCESS: From a83260d203d32caf90c8cf58f2ebc5bcd7b3e3e6 Mon Sep 17 00:00:00 2001 From: ning Date: Fri, 13 Jul 2018 19:14:28 +0800 Subject: [PATCH 05/11] Add keyboard shortcuts in ReplInput --- src/components/workspace/ReplInput.tsx | 34 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/workspace/ReplInput.tsx b/src/components/workspace/ReplInput.tsx index 3fd89c683c..fc9bc5031c 100644 --- a/src/components/workspace/ReplInput.tsx +++ b/src/components/workspace/ReplInput.tsx @@ -12,6 +12,19 @@ export interface IReplInputProps { class ReplInput extends React.PureComponent { private replInputBottom: HTMLDivElement + private execBrowseHistoryDown: () => void + private execBrowseHistoryUp: () => void + private execEvaluate: () => void + + constructor(props: IReplInputProps) { + super(props) + this.execBrowseHistoryDown = () => {} + this.execBrowseHistoryUp = () => {} + this.execEvaluate = () => { + this.replInputBottom.scrollIntoView() + this.props.handleReplEval() + } + } public componentDidUpdate() { if (this.replInputBottom.clientWidth >= window.innerWidth - 50) { @@ -41,16 +54,29 @@ class ReplInput extends React.PureComponent { value={this.props.replValue} onChange={this.props.handleReplValueChange} commands={[ + { + name: 'browseHistoryDown', + bindKey: { + win: 'Down', + mac: 'Down' + }, + exec: this.execBrowseHistoryDown + }, + { + name: 'browseHistoryUp', + bindKey: { + win: 'Up', + mac: 'Up' + }, + exec: this.execBrowseHistoryUp + }, { name: 'evaluate', bindKey: { win: 'Shift-Enter', mac: 'Shift-Enter' }, - exec: () => { - this.replInputBottom.scrollIntoView() - this.props.handleReplEval() - } + exec: this.execEvaluate } ]} minLines={1} From 70649bf5cf9fa412785699aedb77f8593f8ba11c Mon Sep 17 00:00:00 2001 From: ning Date: Fri, 13 Jul 2018 19:36:10 +0800 Subject: [PATCH 06/11] Connect dispatches for browse repl to workspaces --- src/components/Playground.tsx | 4 ++++ src/components/__tests__/Playground.tsx | 4 +++- src/components/academy/grading/GradingWorkspace.tsx | 12 ++++++++---- src/components/assessment/AssessmentWorkspace.tsx | 11 ++++++++--- .../assessment/__tests__/AssessmentWorkspace.tsx | 2 ++ src/components/workspace/Repl.tsx | 2 ++ src/components/workspace/ReplInput.tsx | 2 ++ src/components/workspace/__tests__/Repl.tsx | 8 +++++--- .../workspace/__tests__/__snapshots__/Repl.tsx.snap | 2 +- src/containers/PlaygroundContainer.ts | 4 ++++ .../academy/grading/GradingWorkspaceContainer.ts | 6 +++++- .../assessment/AssessmentWorkspaceContainer.ts | 4 ++++ 12 files changed, 48 insertions(+), 13 deletions(-) diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 27f93bb428..f691b21159 100644 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -24,6 +24,8 @@ export interface IStateProps { } export interface IDispatchProps { + handleBrowseHistoryDown: () => void + handleBrowseHistoryUp: () => void handleChangeActiveTab: (activeTab: number) => void handleChapterSelect: (chapter: any, changeEvent: any) => void handleEditorEval: () => void @@ -85,6 +87,8 @@ class Playground extends React.Component { replProps: { output: this.props.output, replValue: this.props.replValue, + handleBrowseHistoryDown: this.props.handleBrowseHistoryDown, + handleBrowseHistoryUp: this.props.handleBrowseHistoryUp, handleReplEval: this.props.handleReplEval, handleReplValueChange: this.props.handleReplValueChange }, diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index c3250b9037..1a2d41b08e 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -16,11 +16,13 @@ const baseProps = { output: [], replValue: '', handleChapterSelect: (chapter: any, e: any) => {}, - handleExternalSelect: (external: any, e: any) => {}, + handleBrowseHistoryDown: () => {}, + handleBrowseHistoryUp: () => {}, handleChangeActiveTab: (n: number) => {}, handleEditorEval: () => {}, handleEditorValueChange: () => {}, handleEditorWidthChange: (widthChange: number) => {}, + handleExternalSelect: (external: any, e: any) => {}, handleGenerateLz: () => {}, handleInterruptEval: () => {}, handleReplEval: () => {}, diff --git a/src/components/academy/grading/GradingWorkspace.tsx b/src/components/academy/grading/GradingWorkspace.tsx index 30f134ed4f..9458b48bdc 100644 --- a/src/components/academy/grading/GradingWorkspace.tsx +++ b/src/components/academy/grading/GradingWorkspace.tsx @@ -37,13 +37,15 @@ export type OwnProps = { } export type DispatchProps = { - handleGradingFetch: (submissionId: number) => void + handleBrowseHistoryDown: () => void + handleBrowseHistoryUp: () => void handleChangeActiveTab: (activeTab: number) => void handleChapterSelect: (chapter: any, changeEvent: any) => void handleClearContext: (chapter: number, externals: string[]) => void handleEditorEval: () => void handleEditorValueChange: (val: string) => void handleEditorWidthChange: (widthChange: number) => void + handleGradingFetch: (submissionId: number) => void handleInterruptEval: () => void handleReplEval: () => void handleReplOutputClear: () => void @@ -101,10 +103,12 @@ class GradingWorkspace extends React.Component { sideContentHeight: this.props.sideContentHeight, sideContentProps: this.sideContentProps(this.props, questionId), replProps: { - output: this.props.output, - replValue: this.props.replValue, + handleBrowseHistoryDown: this.props.handleBrowseHistoryDown, + handleBrowseHistoryUp: this.props.handleBrowseHistoryUp, handleReplEval: this.props.handleReplEval, - handleReplValueChange: this.props.handleReplValueChange + handleReplValueChange: this.props.handleReplValueChange, + output: this.props.output, + replValue: this.props.replValue } } return ( diff --git a/src/components/assessment/AssessmentWorkspace.tsx b/src/components/assessment/AssessmentWorkspace.tsx index 0df905e15b..1859769228 100644 --- a/src/components/assessment/AssessmentWorkspace.tsx +++ b/src/components/assessment/AssessmentWorkspace.tsx @@ -40,6 +40,8 @@ export type OwnProps = { export type DispatchProps = { handleAssessmentFetch: (assessmentId: number) => void + handleBrowseHistoryDown: () => void + handleBrowseHistoryUp: () => void handleChangeActiveTab: (activeTab: number) => void handleChapterSelect: (chapter: any, changeEvent: any) => void handleClearContext: (chapter: number, externals: string[]) => void @@ -123,10 +125,12 @@ class AssessmentWorkspace extends React.Component< sideContentHeight: this.props.sideContentHeight, sideContentProps: this.sideContentProps(this.props, questionId), replProps: { - output: this.props.output, - replValue: this.props.replValue, + handleBrowseHistoryDown: this.props.handleBrowseHistoryDown, + handleBrowseHistoryUp: this.props.handleBrowseHistoryUp, handleReplEval: this.props.handleReplEval, - handleReplValueChange: this.props.handleReplValueChange + handleReplValueChange: this.props.handleReplValueChange, + output: this.props.output, + replValue: this.props.replValue } } return ( @@ -199,6 +203,7 @@ class AssessmentWorkspace extends React.Component< handleInterruptEval: this.props.handleInterruptEval, handleReplEval: this.props.handleReplEval, handleReplOutputClear: this.props.handleReplOutputClear, + handleReplValueChange: this.props.handleReplValueChange, hasChapterSelect: false, hasDoneButton: questionId === this.props.assessment!.questions.length - 1, hasNextButton: questionId < this.props.assessment!.questions.length - 1, diff --git a/src/components/assessment/__tests__/AssessmentWorkspace.tsx b/src/components/assessment/__tests__/AssessmentWorkspace.tsx index ec29808140..17bb568f9f 100644 --- a/src/components/assessment/__tests__/AssessmentWorkspace.tsx +++ b/src/components/assessment/__tests__/AssessmentWorkspace.tsx @@ -10,6 +10,8 @@ const defaultProps: AssessmentWorkspaceProps = { closeDate: '2048-06-18T05:24:26.026Z', editorWidth: '50%', handleAssessmentFetch: (assessmentId: number) => {}, + handleBrowseHistoryDown: () => {}, + handleBrowseHistoryUp: () => {}, handleChangeActiveTab: (activeTab: number) => {}, handleChapterSelect: (chapter: any, changeEvent: any) => {}, handleClearContext: (chapter: number, externals: string[]) => {}, diff --git a/src/components/workspace/Repl.tsx b/src/components/workspace/Repl.tsx index bf574a228b..b56731b28b 100644 --- a/src/components/workspace/Repl.tsx +++ b/src/components/workspace/Repl.tsx @@ -9,6 +9,8 @@ import ReplInput, { IReplInputProps } from './ReplInput' export interface IReplProps { output: InterpreterOutput[] replValue: string + handleBrowseHistoryDown: () => void + handleBrowseHistoryUp: () => void handleReplEval: () => void handleReplValueChange: (newCode: string) => void } diff --git a/src/components/workspace/ReplInput.tsx b/src/components/workspace/ReplInput.tsx index fc9bc5031c..744ff183f8 100644 --- a/src/components/workspace/ReplInput.tsx +++ b/src/components/workspace/ReplInput.tsx @@ -6,6 +6,8 @@ import 'brace/theme/terminal' export interface IReplInputProps { replValue: string + handleBrowseHistoryDown: () => void + handleBrowseHistoryUp: () => void handleReplValueChange: (newCode: string) => void handleReplEval: () => void } diff --git a/src/components/workspace/__tests__/Repl.tsx b/src/components/workspace/__tests__/Repl.tsx index c197b945de..316dc6c70e 100644 --- a/src/components/workspace/__tests__/Repl.tsx +++ b/src/components/workspace/__tests__/Repl.tsx @@ -35,11 +35,13 @@ const mockErrorOutput: ErrorOutput = { test('Repl renders correctly', () => { const props = { - output: [mockResultOutput, mockCodeOutput, mockErrorOutput, mockRunningOutput], - replValue: '', + handleBrowseHistoryDown: () => {}, + handleBrowseHistoryUp: () => {}, handleReplValueChange: (newCode: string) => {}, handleReplEval: () => {}, - handleReplOutputClear: () => {} + handleReplOutputClear: () => {}, + output: [mockResultOutput, mockCodeOutput, mockErrorOutput, mockRunningOutput], + replValue: '' } const app = const tree = shallow(app) diff --git a/src/components/workspace/__tests__/__snapshots__/Repl.tsx.snap b/src/components/workspace/__tests__/__snapshots__/Repl.tsx.snap index bbba57a68f..a168dcf08f 100644 --- a/src/components/workspace/__tests__/__snapshots__/Repl.tsx.snap +++ b/src/components/workspace/__tests__/__snapshots__/Repl.tsx.snap @@ -45,7 +45,7 @@ exports[`Repl renders correctly 1`] = ` - + " diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 0b73250803..c097a5a9b9 100644 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -4,6 +4,8 @@ import { bindActionCreators, Dispatch } from 'redux' import { beginInterruptExecution, + browseReplHistoryDown, + browseReplHistoryUp, changeActiveTab, changeEditorWidth, changeSideContentHeight, @@ -38,6 +40,8 @@ const location: WorkspaceLocation = 'playground' const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( { + handleBrowseHistoryDown: () => browseReplHistoryDown(location), + handleBrowseHistoryUp: () => browseReplHistoryUp(location), handleChangeActiveTab: (activeTab: number) => changeActiveTab(activeTab, location), handleChapterSelect: (chapter: any, changeEvent: any) => chapterSelect(chapter, changeEvent, location), diff --git a/src/containers/academy/grading/GradingWorkspaceContainer.ts b/src/containers/academy/grading/GradingWorkspaceContainer.ts index c768b8dcb4..44a914812a 100644 --- a/src/containers/academy/grading/GradingWorkspaceContainer.ts +++ b/src/containers/academy/grading/GradingWorkspaceContainer.ts @@ -3,6 +3,8 @@ import { bindActionCreators, Dispatch } from 'redux' import { beginInterruptExecution, + browseReplHistoryDown, + browseReplHistoryUp, changeActiveTab, changeEditorWidth, changeSideContentHeight, @@ -52,7 +54,8 @@ const mapStateToProps: MapStateToProps = (state, p const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => bindActionCreators( { - handleGradingFetch: fetchGrading, + handleBrowseHistoryDown: () => browseReplHistoryDown(location), + handleBrowseHistoryUp: () => browseReplHistoryUp(location), handleChangeActiveTab: (activeTab: number) => changeActiveTab(activeTab, location), handleChapterSelect: (chapter: any, changeEvent: any) => chapterSelect(chapter, changeEvent, location), @@ -61,6 +64,7 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleEditorEval: () => evalEditor(location), handleEditorValueChange: (val: string) => updateEditorValue(val, location), handleEditorWidthChange: (widthChange: number) => changeEditorWidth(widthChange, location), + handleGradingFetch: fetchGrading, handleInterruptEval: () => beginInterruptExecution(location), handleReplEval: () => evalRepl(location), handleReplOutputClear: () => clearReplOutput(location), diff --git a/src/containers/assessment/AssessmentWorkspaceContainer.ts b/src/containers/assessment/AssessmentWorkspaceContainer.ts index b0ee635183..0b0468c892 100644 --- a/src/containers/assessment/AssessmentWorkspaceContainer.ts +++ b/src/containers/assessment/AssessmentWorkspaceContainer.ts @@ -3,6 +3,8 @@ import { bindActionCreators, Dispatch } from 'redux' import { beginInterruptExecution, + browseReplHistoryDown, + browseReplHistoryUp, changeActiveTab, changeEditorWidth, changeSideContentHeight, @@ -48,6 +50,8 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis bindActionCreators( { handleAssessmentFetch: fetchAssessment, + handleBrowseHistoryDown: () => browseReplHistoryDown(location), + handleBrowseHistoryUp: () => browseReplHistoryUp(location), handleChangeActiveTab: (activeTab: number) => changeActiveTab(activeTab, location), handleChapterSelect: (chapter: any, changeEvent: any) => chapterSelect(chapter, changeEvent, location), From c4993356c122398ca6e5f2f488c3f485a314316f Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 16 Jul 2018 12:40:20 +0800 Subject: [PATCH 07/11] Implement up/down arrow behaviours --- src/components/workspace/ReplInput.tsx | 4 +- src/reducers/workspaces.ts | 92 +++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/components/workspace/ReplInput.tsx b/src/components/workspace/ReplInput.tsx index 744ff183f8..6b4c1b1680 100644 --- a/src/components/workspace/ReplInput.tsx +++ b/src/components/workspace/ReplInput.tsx @@ -20,8 +20,8 @@ class ReplInput extends React.PureComponent { constructor(props: IReplInputProps) { super(props) - this.execBrowseHistoryDown = () => {} - this.execBrowseHistoryUp = () => {} + this.execBrowseHistoryDown = props.handleBrowseHistoryDown + this.execBrowseHistoryUp = props.handleBrowseHistoryUp this.execEvaluate = () => { this.replInputBottom.scrollIntoView() this.props.handleReplEval() diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index 94252b8ec4..83e0f223b2 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -1,6 +1,8 @@ import { Reducer } from 'redux' import { + BROWSE_REPL_HISTORY_DOWN, + BROWSE_REPL_HISTORY_UP, CHANGE_ACTIVE_TAB, CHANGE_EDITOR_WIDTH, CHANGE_PLAYGROUND_EXTERNAL, @@ -34,9 +36,12 @@ import { } from './states' /** - * Takes in a IWorkspaceManagerState and maps it to a new state. The pre-conditions are that - * - There exists an IWorkspaceState in the IWorkspaceManagerState of the key `location`. - * - `location` is defined (and exists) as a property 'workspaceLocation' in the action's payload. + * Takes in a IWorkspaceManagerState and maps it to a new state. The + * pre-conditions are that + * - There exists an IWorkspaceState in the IWorkspaceManagerState of the key + * `location`. + * - `location` is defined (and exists) as a property 'workspaceLocation' in + * the action's payload. */ export const reducer: Reducer = ( state = defaultWorkspaceManager, @@ -48,6 +53,87 @@ export const reducer: Reducer = ( let lastOutput: InterpreterOutput switch (action.type) { + case BROWSE_REPL_HISTORY_DOWN: + if (state[location].replHistory.isBrowsingIndex === null) { + // Not yet started browsing history, nothing to do + return state + } else if (state[location].replHistory.isBrowsingIndex !== 0) { + // Browsing history, and still have earlier records to show + const newIndex = state[location].replHistory.isBrowsingIndex! - 1 + const newReplValue = state[location].replHistory.records[newIndex] + return { + ...state, + [location]: { + ...state[location], + replValue: newReplValue, + replHistory: { + ...state[location].replHistory, + isBrowsingIndex: newIndex + } + } + } + } else { + // Browsing history, no earlier records to show; return replValue to + // the last value when user started browsing + const newIndex = null + const newReplValue = state[location].replHistory.records[-1] + const newRecords = state[location].replHistory.records.slice() + delete newRecords[-1] + return { + ...state, + [location]: { + ...state[location], + replValue: newReplValue, + replHistory: { + isBrowsingIndex: newIndex, + records: newRecords + } + } + } + } + case BROWSE_REPL_HISTORY_UP: + const lastRecords = state[location].replHistory.records + const lastIndex = state[location].replHistory.isBrowsingIndex + if ( + lastRecords.length === 0 || + (lastIndex !== null && lastRecords[lastIndex + 1] === undefined) + ) { + // There is no more later history to show + return state + } else if (lastIndex === null) { + // Not yet started browsing, initialise the index & array + const newIndex = 0 + const newRecords = lastRecords.slice() + newRecords[-1] = state[location].replValue + const newReplValue = newRecords[newIndex] + return { + ...state, + [location]: { + ...state[location], + replValue: newReplValue, + replHistory: { + ...state[location].replHistory, + isBrowsingIndex: newIndex, + records: newRecords + } + } + } + } else { + // Browsing history, and still have later history to show + const newIndex = lastIndex + 1 + const newReplValue = lastRecords[newIndex] + return { + ...state, + [location]: { + ...state[location], + replValue: newReplValue, + replHistory: { + ...state[location].replHistory, + isBrowsingIndex: newIndex + } + } + } + } case CHANGE_ACTIVE_TAB: return { ...state, From 82901b89425cdcc93a86159c27321f025f7d9f92 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 16 Jul 2018 12:51:59 +0800 Subject: [PATCH 08/11] Rename property isBrowsingIndex -> browseIndex --- src/reducers/states.ts | 4 ++-- src/reducers/workspaces.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/reducers/states.ts b/src/reducers/states.ts index bd60b34475..3e3116f206 100644 --- a/src/reducers/states.ts +++ b/src/reducers/states.ts @@ -67,7 +67,7 @@ export interface ISessionState { } type ReplHistory = { - isBrowsingIndex: null | number // [0, 49] if browsing, else null + browseIndex: null | number // [0, 49] if browsing, else null records: string[] } @@ -196,7 +196,7 @@ export const createDefaultWorkspace = (location: WorkspaceLocation): IWorkspaceS editorWidth: '50%', output: [], replHistory: { - isBrowsingIndex: null, + browseIndex: null, records: [] }, replValue: '', diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index 83e0f223b2..b387cd2bd6 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -54,12 +54,12 @@ export const reducer: Reducer = ( switch (action.type) { case BROWSE_REPL_HISTORY_DOWN: - if (state[location].replHistory.isBrowsingIndex === null) { + if (state[location].replHistory.browseIndex === null) { // Not yet started browsing history, nothing to do return state - } else if (state[location].replHistory.isBrowsingIndex !== 0) { + } else if (state[location].replHistory.browseIndex !== 0) { // Browsing history, and still have earlier records to show - const newIndex = state[location].replHistory.isBrowsingIndex! - 1 + const newIndex = state[location].replHistory.browseIndex! - 1 const newReplValue = state[location].replHistory.records[newIndex] return { ...state, @@ -68,7 +68,7 @@ export const reducer: Reducer = ( replValue: newReplValue, replHistory: { ...state[location].replHistory, - isBrowsingIndex: newIndex + browseIndex: newIndex } } } @@ -85,7 +85,7 @@ export const reducer: Reducer = ( ...state[location], replValue: newReplValue, replHistory: { - isBrowsingIndex: newIndex, + browseIndex: newIndex, records: newRecords } } @@ -93,7 +93,7 @@ export const reducer: Reducer = ( } case BROWSE_REPL_HISTORY_UP: const lastRecords = state[location].replHistory.records - const lastIndex = state[location].replHistory.isBrowsingIndex + const lastIndex = state[location].replHistory.browseIndex if ( lastRecords.length === 0 || (lastIndex !== null && lastRecords[lastIndex + 1] === undefined) @@ -113,7 +113,7 @@ export const reducer: Reducer = ( replValue: newReplValue, replHistory: { ...state[location].replHistory, - isBrowsingIndex: newIndex, + browseIndex: newIndex, records: newRecords } } @@ -129,7 +129,7 @@ export const reducer: Reducer = ( replValue: newReplValue, replHistory: { ...state[location].replHistory, - isBrowsingIndex: newIndex + browseIndex: newIndex } } } From a00fd95eee9f40625a900d14a271f06954533d4a Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 16 Jul 2018 12:55:33 +0800 Subject: [PATCH 09/11] Prevent saving of empty replValues into replHistory --- src/components/__tests__/Playground.tsx | 3 ++- src/reducers/workspaces.ts | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 1a2d41b08e..9ff1c648bf 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -15,16 +15,17 @@ const baseProps = { externalLibrary: 'none', output: [], replValue: '', - handleChapterSelect: (chapter: any, e: any) => {}, handleBrowseHistoryDown: () => {}, handleBrowseHistoryUp: () => {}, handleChangeActiveTab: (n: number) => {}, + handleChapterSelect: (chapter: any, e: any) => {}, handleEditorEval: () => {}, handleEditorValueChange: () => {}, handleEditorWidthChange: (widthChange: number) => {}, handleExternalSelect: (external: any, e: any) => {}, handleGenerateLz: () => {}, handleInterruptEval: () => {}, + handleLibrarySelect: (library: any, e: any) => {}, handleReplEval: () => {}, handleReplOutputClear: () => {}, handleReplValueChange: (code: string) => {}, diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index b387cd2bd6..5ef5e2b32a 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -226,9 +226,12 @@ export const reducer: Reducer = ( case SEND_REPL_INPUT_TO_OUTPUT: // CodeOutput properties exist in parallel with workspaceLocation newOutput = state[location].output.concat(action.payload as CodeOutput) - const newReplHistoryRecords = [action.payload.value].concat( - state[location].replHistory.records - ) + let newReplHistoryRecords: string[] + if (action.payload.value !== '') { + newReplHistoryRecords = [action.payload.value].concat(state[location].replHistory.records) + } else { + newReplHistoryRecords = state[location].replHistory.records + } if (newReplHistoryRecords.length > 50) { newReplHistoryRecords.pop() } From c6b0fdc9c71e82949bc6bfdf118b2457862ecbf2 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 16 Jul 2018 18:32:22 +0800 Subject: [PATCH 10/11] Revert removal of EVAL_EDITOR and EVAL_REPL reducers --- src/reducers/workspaces.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index 5ef5e2b32a..d9f0335e08 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -246,6 +246,22 @@ export const reducer: Reducer = ( } } } + case EVAL_EDITOR: + // Forces re-render of workspace on editor eval + return { + ...state, + [location]: { + ...state[location] + } + } + case EVAL_REPL: + // Forces re-render of workspace on repl eval + return { + ...state, + [location]: { + ...state[location] + } + } case EVAL_INTERPRETER_SUCCESS: lastOutput = state[location].output.slice(-1)[0] if (lastOutput !== undefined && lastOutput.type === 'running') { From d4eb7aeaddc35a869d74051e53635eec2bb5c200 Mon Sep 17 00:00:00 2001 From: ning Date: Mon, 16 Jul 2018 18:37:44 +0800 Subject: [PATCH 11/11] Use variable for max repl history length --- src/components/__tests__/Playground.tsx | 1 - src/reducers/states.ts | 2 ++ src/reducers/workspaces.ts | 23 +++++++++++++---------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 9ff1c648bf..9101c86aa8 100644 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -25,7 +25,6 @@ const baseProps = { handleExternalSelect: (external: any, e: any) => {}, handleGenerateLz: () => {}, handleInterruptEval: () => {}, - handleLibrarySelect: (library: any, e: any) => {}, handleReplEval: () => {}, handleReplOutputClear: () => {}, handleReplValueChange: (code: string) => {}, diff --git a/src/reducers/states.ts b/src/reducers/states.ts index 3e3116f206..5b2a45c694 100644 --- a/src/reducers/states.ts +++ b/src/reducers/states.ts @@ -71,6 +71,8 @@ type ReplHistory = { records: string[] } +export const maxBrowseIndex = 50 + /** * An output while the program is still being run in the interpreter. As a * result, there are no return values or SourceErrors yet. However, there could diff --git a/src/reducers/workspaces.ts b/src/reducers/workspaces.ts index d9f0335e08..50702b38d2 100644 --- a/src/reducers/workspaces.ts +++ b/src/reducers/workspaces.ts @@ -11,8 +11,10 @@ import { CLEAR_REPL_INPUT, CLEAR_REPL_OUTPUT, END_INTERRUPT_EXECUTION, + EVAL_EDITOR, EVAL_INTERPRETER_ERROR, EVAL_INTERPRETER_SUCCESS, + EVAL_REPL, HANDLE_CONSOLE_LOG, IAction, RESET_ASSESSMENT_WORKSPACE, @@ -32,7 +34,8 @@ import { defaultComments, defaultWorkspaceManager, InterpreterOutput, - IWorkspaceManagerState + IWorkspaceManagerState, + maxBrowseIndex } from './states' /** @@ -232,7 +235,7 @@ export const reducer: Reducer = ( } else { newReplHistoryRecords = state[location].replHistory.records } - if (newReplHistoryRecords.length > 50) { + if (newReplHistoryRecords.length > maxBrowseIndex) { newReplHistoryRecords.pop() } return { @@ -254,14 +257,6 @@ export const reducer: Reducer = ( ...state[location] } } - case EVAL_REPL: - // Forces re-render of workspace on repl eval - return { - ...state, - [location]: { - ...state[location] - } - } case EVAL_INTERPRETER_SUCCESS: lastOutput = state[location].output.slice(-1)[0] if (lastOutput !== undefined && lastOutput.type === 'running') { @@ -306,6 +301,14 @@ export const reducer: Reducer = ( output: newOutput } } + case EVAL_REPL: + // Forces re-render of workspace on repl eval + return { + ...state, + [location]: { + ...state[location] + } + } /** * Called to signal the end of an interruption, * i.e called after the interpreter is told to stop interruption,