From d69b8342a25fb6a0838566caac1ff30d21ee5fa7 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Mon, 13 May 2024 00:33:24 +0800 Subject: [PATCH] Migrate to Redux Toolkit part 10 (#2995) * Migrate SourcereelActions to use helper * Update tests * Replace old types with new types * Use qualified action creator names from default export * Update BackendSaga to use new action type * Migrate remaining SessionActions to use helper * Update files to reflect SessionActions changes * Migrate more saga handlers to use helper * Use new action types in SideContentReducer * Migrate SideContentActions to use helper * Update files post SideContentActions refactor * Migrate SideContentSaga to use helper * Use qualified action creator names in workspaces * Update workspace reducers and tests to use default import * Update sagas to use default import for WorkspaceActions * Migrate AchievementActions to use helper * Migrate AchievementSaga to use helper * Migrate GroundControlActions to use helper * Update BackendSaga to match GroundControlActions changes * Update achievement tests * Migrate RemoteExecutionActions to use helper * Migrate RemoteExecutionSaga to use helper * Fix error --- .../application/actions/SessionActions.ts | 51 +-- .../actions/__tests__/SessionActions.ts | 16 +- .../reducers/__tests__/SessionReducer.ts | 8 +- src/commons/application/types/SessionTypes.ts | 6 - .../AssessmentWorkspace.tsx | 77 ++-- .../editingWorkspace/EditingWorkspace.tsx | 65 ++-- src/commons/missionCreator/MissionCreator.tsx | 4 +- .../ResearchAgreementPrompt.tsx | 6 +- .../__tests__/ResearchAgreementPrompt.tsx | 9 +- src/commons/sagas/AchievementSaga.ts | 190 ++++----- src/commons/sagas/BackendSaga.ts | 250 +++++------- src/commons/sagas/PlaygroundSaga.ts | 41 +- src/commons/sagas/RemoteExecutionSaga.ts | 367 +++++++++--------- src/commons/sagas/SideContentSaga.ts | 83 ++-- .../WorkspaceSaga/helpers/clearContext.ts | 4 +- .../sagas/WorkspaceSaga/helpers/evalCode.ts | 28 +- .../sagas/WorkspaceSaga/helpers/evalEditor.ts | 4 +- src/commons/sagas/WorkspaceSaga/index.ts | 4 +- src/commons/sagas/__tests__/BackendSaga.ts | 34 +- .../sagas/__tests__/PersistenceSaga.ts | 60 ++- src/commons/sagas/__tests__/WorkspaceSaga.ts | 152 ++++---- src/commons/sideContent/SideContentActions.ts | 90 ++--- src/commons/sideContent/SideContentReducer.ts | 30 +- src/commons/sideContent/SideContentTypes.ts | 8 - src/commons/utils/ActionsHelper.ts | 10 +- .../workspace/__tests__/WorkspaceActions.ts | 241 ++++++------ src/commons/workspace/reducers/cseReducer.ts | 27 +- .../workspace/reducers/editorReducer.ts | 48 +-- src/commons/workspace/reducers/replReducer.ts | 27 +- .../achievement/AchievementActions.ts | 166 +++----- src/features/achievement/AchievementTypes.ts | 18 - .../__tests__/AchievementActions.ts | 7 +- .../__tests__/AchievementReducer.ts | 5 +- .../groundControl/GroundControlActions.ts | 98 ++--- .../groundControl/GroundControlTypes.ts | 9 - .../remoteExecution/RemoteExecutionActions.ts | 73 ++-- .../remoteExecution/RemoteExecutionTypes.ts | 10 - .../sourcereel/SourcereelActions.ts | 110 +++--- .../sourcereel/SourcereelTypes.ts | 10 - .../sourcereel/__tests__/SourcereelActions.ts | 47 +-- .../sourcereel/__tests__/SourcereelReducer.ts | 41 +- .../subcomponents/GradingWorkspace.tsx | 66 ++-- src/pages/academy/sourcereel/Sourcereel.tsx | 56 ++- src/pages/playground/Playground.tsx | 107 +++-- src/pages/sourcecast/Sourcecast.tsx | 56 ++- 45 files changed, 1183 insertions(+), 1636 deletions(-) delete mode 100644 src/features/groundControl/GroundControlTypes.ts diff --git a/src/commons/application/actions/SessionActions.ts b/src/commons/application/actions/SessionActions.ts index 73f1b41a2a..f8137b1483 100644 --- a/src/commons/application/actions/SessionActions.ts +++ b/src/commons/application/actions/SessionActions.ts @@ -1,4 +1,3 @@ -import { createAction } from '@reduxjs/toolkit'; import { createActions } from 'src/commons/redux/utils'; import { paginationToBackendParams, @@ -23,20 +22,15 @@ import { Role, StoriesRole } from '../ApplicationTypes'; import { AdminPanelCourseRegistration, CourseRegistration, - DELETE_STORIES_USER_USER_GROUPS, NotificationConfiguration, NotificationPreference, TimeOption, Tokens, - UPDATE_ASSESSMENT, - UPDATE_COURSE_RESEARCH_AGREEMENT, - UPDATE_STORIES_USER_ROLE, - UPDATE_TOTAL_XP, UpdateCourseConfiguration, User } from '../types/SessionTypes'; -const newActions = createActions('session', { +const SessionActions = createActions('session', { fetchAuth: (code: string, providerId?: string) => ({ code, providerId }), fetchUserAndCourse: () => ({}), fetchCourseConfig: () => ({}), @@ -109,18 +103,9 @@ const newActions = createActions('session', { ) => ({ submissionId, questionId, xpAdjustment, comments }), reautogradeSubmission: (submissionId: number) => submissionId, reautogradeAnswer: (submissionId: number, questionId: number) => ({ submissionId, questionId }), - updateAssessmentOverviews: (overviews: AssessmentOverview[]) => overviews -}); - -export const updateTotalXp = createAction(UPDATE_TOTAL_XP, (totalXp: number) => ({ - payload: totalXp -})); - -export const updateAssessment = createAction(UPDATE_ASSESSMENT, (assessment: Assessment) => ({ - payload: assessment -})); - -const newActions2 = createActions('session', { + updateAssessmentOverviews: (overviews: AssessmentOverview[]) => overviews, + updateTotalXp: (totalXp: number) => totalXp, + updateAssessment: (assessment: Assessment) => assessment, updateGradingOverviews: (overviews: GradingOverviews) => overviews, fetchTeamFormationOverview: (assessmentId: number) => ({ assessmentId }), createTeam: (assessment: AssessmentOverview, teams: OptionType[][]) => ({ assessment, teams }), @@ -168,31 +153,13 @@ const newActions2 = createActions('session', { updateTimeOptions: (timeOptions: TimeOption[]) => timeOptions, deleteTimeOptions: (timeOptionIds: number[]) => timeOptionIds, updateUserRole: (courseRegId: number, role: Role) => ({ courseRegId, role }), - deleteUserCourseRegistration: (courseRegId: number) => ({ courseRegId }) + deleteUserCourseRegistration: (courseRegId: number) => ({ courseRegId }), + updateCourseResearchAgreement: (agreedToResearch: boolean) => ({ agreedToResearch }), + updateStoriesUserRole: (userId: number, role: StoriesRole) => ({ userId, role }), + deleteStoriesUserUserGroups: (userId: number) => ({ userId }) }); -export const updateCourseResearchAgreement = createAction( - UPDATE_COURSE_RESEARCH_AGREEMENT, - (agreedToResearch: boolean) => ({ payload: { agreedToResearch } }) -); - -export const updateStoriesUserRole = createAction( - UPDATE_STORIES_USER_ROLE, - (userId: number, role: StoriesRole) => ({ payload: { userId, role } }) -); - -export const deleteStoriesUserUserGroups = createAction( - DELETE_STORIES_USER_USER_GROUPS, - (userId: number) => ({ payload: { userId } }) -); - // For compatibility with existing code (actions helper) export default { - ...newActions, - updateTotalXp, - updateAssessment, - ...newActions2, - updateCourseResearchAgreement, - updateStoriesUserRole, - deleteStoriesUserUserGroups + ...SessionActions }; diff --git a/src/commons/application/actions/__tests__/SessionActions.ts b/src/commons/application/actions/__tests__/SessionActions.ts index 3c0fcebe98..7afe96e44d 100644 --- a/src/commons/application/actions/__tests__/SessionActions.ts +++ b/src/commons/application/actions/__tests__/SessionActions.ts @@ -16,12 +16,8 @@ import { } from '../../../assessment/AssessmentTypes'; import { Notification } from '../../../notificationBadge/NotificationBadgeTypes'; import { GameState, Role, Story } from '../../ApplicationTypes'; -import { - UPDATE_ASSESSMENT, - UPDATE_COURSE_RESEARCH_AGREEMENT, - User -} from '../../types/SessionTypes'; -import SessionActions, { updateAssessment, updateCourseResearchAgreement } from '../SessionActions'; +import { User } from '../../types/SessionTypes'; +import SessionActions from '../SessionActions'; test('acknowledgeNotifications generates correct action object', () => { const action = SessionActions.acknowledgeNotifications(); @@ -501,9 +497,9 @@ test('updateAssessment generates correct action object', () => { title: 'first assessment' }; - const action = updateAssessment(assessment); + const action = SessionActions.updateAssessment(assessment); expect(action).toEqual({ - type: UPDATE_ASSESSMENT, + type: SessionActions.updateAssessment.type, payload: assessment }); }); @@ -815,9 +811,9 @@ test('updateUserRole generates correct action object', () => { test('updateCourseResearchAgreement generates correct action object', () => { const agreedToResearch = true; - const action = updateCourseResearchAgreement(agreedToResearch); + const action = SessionActions.updateCourseResearchAgreement(agreedToResearch); expect(action).toEqual({ - type: UPDATE_COURSE_RESEARCH_AGREEMENT, + type: SessionActions.updateCourseResearchAgreement.type, payload: { agreedToResearch } }); }); diff --git a/src/commons/application/reducers/__tests__/SessionReducer.ts b/src/commons/application/reducers/__tests__/SessionReducer.ts index c884cbf2ab..ef3dc97883 100644 --- a/src/commons/application/reducers/__tests__/SessionReducer.ts +++ b/src/commons/application/reducers/__tests__/SessionReducer.ts @@ -11,7 +11,7 @@ import { Notification } from '../../../notificationBadge/NotificationBadgeTypes' import SessionActions from '../../actions/SessionActions'; import { defaultSession, GameState, Role, Story } from '../../ApplicationTypes'; import { LOG_OUT } from '../../types/CommonsTypes'; -import { SessionState, UPDATE_ASSESSMENT } from '../../types/SessionTypes'; +import { SessionState } from '../../types/SessionTypes'; import { SessionsReducer } from '../SessionsReducer'; test('LOG_OUT works correctly on default session', () => { @@ -272,7 +272,7 @@ const assessmentTest3: Assessment = { test('UPDATE_ASSESSMENT works correctly in inserting assessment', () => { const action = { - type: UPDATE_ASSESSMENT, + type: SessionActions.updateAssessment.type, payload: assessmentTest1 } as const; const resultMap = SessionsReducer(defaultSession, action).assessments; @@ -290,7 +290,7 @@ test('UPDATE_ASSESSMENT works correctly in inserting assessment and retains old }; const action = { - type: UPDATE_ASSESSMENT, + type: SessionActions.updateAssessment.type, payload: assessmentTest2 } as const; const resultMap = SessionsReducer(newDefaultSession, action).assessments; @@ -308,7 +308,7 @@ test('UPDATE_ASSESSMENT works correctly in updating assessment', () => { assessments }; const action = { - type: UPDATE_ASSESSMENT, + type: SessionActions.updateAssessment.type, payload: assessmentTest2 } as const; const resultMap = SessionsReducer(newDefaultSession, action).assessments; diff --git a/src/commons/application/types/SessionTypes.ts b/src/commons/application/types/SessionTypes.ts index 3cdb7bec7e..abe41d4422 100644 --- a/src/commons/application/types/SessionTypes.ts +++ b/src/commons/application/types/SessionTypes.ts @@ -12,12 +12,6 @@ import { import { Notification } from '../../notificationBadge/NotificationBadgeTypes'; import { GameState, Role, Story } from '../ApplicationTypes'; -export const UPDATE_TOTAL_XP = 'UPDATE_TOTAL_XP'; -export const UPDATE_ASSESSMENT = 'UPDATE_ASSESSMENT'; -export const UPDATE_COURSE_RESEARCH_AGREEMENT = 'UPDATE_COURSE_RESEARCH_AGREEMENT'; -export const UPDATE_STORIES_USER_ROLE = 'UPDATE_STORIES_USER_ROLE'; -export const DELETE_STORIES_USER_USER_GROUPS = 'DELETE_STORIES_USER_USER_GROUPS'; - export type SessionState = { // Tokens readonly accessToken?: string; diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index 68a6c3f325..bb5b9b79a8 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -73,29 +73,7 @@ import { useResponsive, useTypedSelector } from '../utils/Hooks'; import { assessmentTypeLink } from '../utils/ParamParseHelper'; import { assertType } from '../utils/TypeHelper'; import Workspace, { WorkspaceProps } from '../workspace/Workspace'; -import { - beginClearContext, - browseReplHistoryDown, - browseReplHistoryUp, - changeExecTime, - clearReplOutput, - disableTokenCounter, - enableTokenCounter, - evalEditor, - evalRepl, - evalTestcase, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - resetWorkspace, - runAllTestcases, - setEditorBreakpoint, - updateActiveEditorTabIndex, - updateCurrentAssessmentId, - updateEditorValue, - updateHasUnsavedChanges, - updateReplValue -} from '../workspace/WorkspaceActions'; +import WorkspaceActions from '../workspace/WorkspaceActions'; import { WorkspaceLocation, WorkspaceState } from '../workspace/WorkspaceTypes'; import AssessmentWorkspaceGradingResult from './AssessmentWorkspaceGradingResult'; @@ -170,32 +148,39 @@ const AssessmentWorkspace: React.FC = props => { return { handleTeamOverviewFetch: (assessmentId: number) => dispatch(SessionActions.fetchTeamFormationOverview(assessmentId)), - handleTestcaseEval: (id: number) => dispatch(evalTestcase(workspaceLocation, id)), + handleTestcaseEval: (id: number) => + dispatch(WorkspaceActions.evalTestcase(workspaceLocation, id)), handleClearContext: (library: Library, shouldInitLibrary: boolean) => - dispatch(beginClearContext(workspaceLocation, library, shouldInitLibrary)), + dispatch(WorkspaceActions.beginClearContext(workspaceLocation, library, shouldInitLibrary)), handleChangeExecTime: (execTimeMs: number) => - dispatch(changeExecTime(execTimeMs, workspaceLocation)), + dispatch(WorkspaceActions.changeExecTime(execTimeMs, workspaceLocation)), handleUpdateCurrentAssessmentId: (assessmentId: number, questionId: number) => - dispatch(updateCurrentAssessmentId(assessmentId, questionId)), + dispatch(WorkspaceActions.updateCurrentAssessmentId(assessmentId, questionId)), handleResetWorkspace: (options: Partial) => - dispatch(resetWorkspace(workspaceLocation, options)), - handleRunAllTestcases: () => dispatch(runAllTestcases(workspaceLocation)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + dispatch(WorkspaceActions.resetWorkspace(workspaceLocation, options)), + handleRunAllTestcases: () => dispatch(WorkspaceActions.runAllTestcases(workspaceLocation)), + handleEditorEval: () => dispatch(WorkspaceActions.evalEditor(workspaceLocation)), handleAssessmentFetch: (assessmentId: number, assessmentPassword?: string) => dispatch(SessionActions.fetchAssessment(assessmentId, assessmentPassword)), handleEditorValueChange: (editorTabIndex: number, newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue)), + dispatch( + WorkspaceActions.updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue) + ), handleEditorUpdateBreakpoints: (editorTabIndex: number, newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), + dispatch( + WorkspaceActions.setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints) + ), + handleReplEval: () => dispatch(WorkspaceActions.evalRepl(workspaceLocation)), handleCheckLastModifiedAt: (id: number, lastModifiedAt: string, saveAnswer: Function) => dispatch(SessionActions.checkAnswerLastModifiedAt(id, lastModifiedAt, saveAnswer)), handleSave: (id: number, answer: number | string | ContestEntry[]) => dispatch(SessionActions.submitAnswer(id, answer)), handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => - dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)), - handleEnableTokenCounter: () => dispatch(enableTokenCounter(workspaceLocation)), - handleDisableTokenCounter: () => dispatch(disableTokenCounter(workspaceLocation)) + dispatch(WorkspaceActions.updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)), + handleEnableTokenCounter: () => + dispatch(WorkspaceActions.enableTokenCounter(workspaceLocation)), + handleDisableTokenCounter: () => + dispatch(WorkspaceActions.disableTokenCounter(workspaceLocation)) }; }, [dispatch]); @@ -800,7 +785,7 @@ const AssessmentWorkspace: React.FC = props => { const replButtons = useMemo(() => { const clearButton = ( dispatch(clearReplOutput(workspaceLocation))} + handleReplOutputClear={() => dispatch(WorkspaceActions.clearReplOutput(workspaceLocation))} key="clear_repl" /> ); @@ -814,22 +799,26 @@ const AssessmentWorkspace: React.FC = props => { const editorContainerHandlers = useMemo(() => { return { setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), + dispatch( + WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex) + ), removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)), + dispatch(WorkspaceActions.removeEditorTab(workspaceLocation, editorTabIndex)), handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), + dispatch(WorkspaceActions.navigateToDeclaration(workspaceLocation, cursorPosition)), handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)) + dispatch(WorkspaceActions.promptAutocomplete(workspaceLocation, row, col, callback)) }; }, [dispatch]); const replHandlers = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), + handleBrowseHistoryDown: () => + dispatch(WorkspaceActions.browseReplHistoryDown(workspaceLocation)), + handleBrowseHistoryUp: () => + dispatch(WorkspaceActions.browseReplHistoryUp(workspaceLocation)), handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)) + dispatch(WorkspaceActions.updateReplValue(newValue, workspaceLocation)) }; }, [dispatch]); diff --git a/src/commons/editingWorkspace/EditingWorkspace.tsx b/src/commons/editingWorkspace/EditingWorkspace.tsx index 70f3750436..e92fb1bb68 100644 --- a/src/commons/editingWorkspace/EditingWorkspace.tsx +++ b/src/commons/editingWorkspace/EditingWorkspace.tsx @@ -52,26 +52,7 @@ import { changeSideContentHeight } from '../sideContent/SideContentActions'; import { SideContentTab, SideContentType } from '../sideContent/SideContentTypes'; import { useTypedSelector } from '../utils/Hooks'; import Workspace, { WorkspaceProps } from '../workspace/Workspace'; -import { - beginClearContext, - browseReplHistoryDown, - browseReplHistoryUp, - clearReplOutput, - evalEditor, - evalRepl, - evalTestcase, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - resetWorkspace, - setEditorBreakpoint, - updateActiveEditorTabIndex, - updateCurrentAssessmentId, - updateEditorValue, - updateHasUnsavedChanges, - updateReplValue, - updateWorkspace -} from '../workspace/WorkspaceActions'; +import WorkspaceActions from '../workspace/WorkspaceActions'; import { WorkspaceLocation, WorkspaceState } from '../workspace/WorkspaceTypes'; import { retrieveLocalAssessment, @@ -151,39 +132,47 @@ const EditingWorkspace: React.FC = props => { removeEditorTabByIndex } = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), + handleBrowseHistoryDown: () => + dispatch(WorkspaceActions.browseReplHistoryDown(workspaceLocation)), + handleBrowseHistoryUp: () => + dispatch(WorkspaceActions.browseReplHistoryUp(workspaceLocation)), handleClearContext: (library: Library, shouldInitLibrary: boolean) => - dispatch(beginClearContext(workspaceLocation, library, shouldInitLibrary)), + dispatch(WorkspaceActions.beginClearContext(workspaceLocation, library, shouldInitLibrary)), handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + dispatch(WorkspaceActions.navigateToDeclaration(workspaceLocation, cursorPosition)), + handleEditorEval: () => dispatch(WorkspaceActions.evalEditor(workspaceLocation)), handleEditorValueChange: (editorTabIndex: number, newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue)), + dispatch( + WorkspaceActions.updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue) + ), handleEditorUpdateBreakpoints: (editorTabIndex: number, newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), - handleReplOutputClear: () => dispatch(clearReplOutput(workspaceLocation)), + dispatch( + WorkspaceActions.setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints) + ), + handleReplEval: () => dispatch(WorkspaceActions.evalRepl(workspaceLocation)), + handleReplOutputClear: () => dispatch(WorkspaceActions.clearReplOutput(workspaceLocation)), handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)), + dispatch(WorkspaceActions.updateReplValue(newValue, workspaceLocation)), handleResetWorkspace: (options: Partial) => - dispatch(resetWorkspace(workspaceLocation, options)), + dispatch(WorkspaceActions.resetWorkspace(workspaceLocation, options)), handleUpdateWorkspace: (options: Partial) => - dispatch(updateWorkspace(workspaceLocation, options)), + dispatch(WorkspaceActions.updateWorkspace(workspaceLocation, options)), handleSubmitAnswer: (id: number, answer: string | number) => dispatch(SessionActions.submitAnswer(id, answer)), handleSideContentHeightChange: (heightChange: number) => dispatch(changeSideContentHeight(heightChange, workspaceLocation)), handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => - dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)), + dispatch(WorkspaceActions.updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)), handleUpdateCurrentAssessmentId: (assessmentId: number, questionId: number) => - dispatch(updateCurrentAssessmentId(assessmentId, questionId)), + dispatch(WorkspaceActions.updateCurrentAssessmentId(assessmentId, questionId)), handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)), + dispatch(WorkspaceActions.promptAutocomplete(workspaceLocation, row, col, callback)), setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), + dispatch( + WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex) + ), removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)) + dispatch(WorkspaceActions.removeEditorTab(workspaceLocation, editorTabIndex)) }; }, [dispatch]); @@ -334,7 +323,7 @@ const EditingWorkspace: React.FC = props => { const handleTestcaseEval = (testcase: Testcase) => { const editorTestcases = [testcase]; handleUpdateWorkspace({ editorTestcases }); - dispatch(evalTestcase(workspaceLocation, 0)); + dispatch(WorkspaceActions.evalTestcase(workspaceLocation, 0)); }; const handleSave = () => { diff --git a/src/commons/missionCreator/MissionCreator.tsx b/src/commons/missionCreator/MissionCreator.tsx index 3d6da510bb..d7da58875b 100644 --- a/src/commons/missionCreator/MissionCreator.tsx +++ b/src/commons/missionCreator/MissionCreator.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { parseString } from 'xml2js'; -import { updateAssessment } from '../application/actions/SessionActions'; +import SessionActions from '../application/actions/SessionActions'; import { Assessment, AssessmentOverview, @@ -29,7 +29,7 @@ const MissionCreator: React.FC = props => { const dispatch = useDispatch(); const newAssessment = useCallback( - (assessment: Assessment) => dispatch(updateAssessment(assessment)), + (assessment: Assessment) => dispatch(SessionActions.updateAssessment(assessment)), [dispatch] ); diff --git a/src/commons/researchAgreementPrompt/ResearchAgreementPrompt.tsx b/src/commons/researchAgreementPrompt/ResearchAgreementPrompt.tsx index 9ed531d41e..7621b12a40 100644 --- a/src/commons/researchAgreementPrompt/ResearchAgreementPrompt.tsx +++ b/src/commons/researchAgreementPrompt/ResearchAgreementPrompt.tsx @@ -1,7 +1,7 @@ import { Button, Classes, Dialog, H4, Intent } from '@blueprintjs/core'; import { useDispatch } from 'react-redux'; -import { updateCourseResearchAgreement } from '../application/actions/SessionActions'; +import SessionActions from '../application/actions/SessionActions'; import Constants from '../utils/Constants'; const ResearchAgreementPrompt: React.FC = () => { @@ -36,12 +36,12 @@ const ResearchAgreementPrompt: React.FC = () => {
diff --git a/src/commons/researchAgreementPrompt/__tests__/ResearchAgreementPrompt.tsx b/src/commons/researchAgreementPrompt/__tests__/ResearchAgreementPrompt.tsx index e4fdc1aa29..c52c9a999e 100644 --- a/src/commons/researchAgreementPrompt/__tests__/ResearchAgreementPrompt.tsx +++ b/src/commons/researchAgreementPrompt/__tests__/ResearchAgreementPrompt.tsx @@ -1,9 +1,8 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; -import { Provider } from 'react-redux'; -import { useDispatch } from 'react-redux'; -import { updateCourseResearchAgreement } from 'src/commons/application/actions/SessionActions'; +import { Provider, useDispatch } from 'react-redux'; +import SessionActions from 'src/commons/application/actions/SessionActions'; import { mockInitialStore } from 'src/commons/mocks/StoreMocks'; import ResearchAgreementPrompt from '../ResearchAgreementPrompt'; @@ -41,7 +40,7 @@ describe('ResearchAgreementPrompt', () => { await user.click(button); expect(dispatchMock).toBeCalledTimes(1); - expect(dispatchMock).toBeCalledWith(updateCourseResearchAgreement(false)); + expect(dispatchMock).toBeCalledWith(SessionActions.updateCourseResearchAgreement(false)); }); test('"I consent!" dispatches correctly', async () => { @@ -50,6 +49,6 @@ describe('ResearchAgreementPrompt', () => { await user.click(button); expect(dispatchMock).toBeCalledTimes(1); - expect(dispatchMock).toBeCalledWith(updateCourseResearchAgreement(true)); + expect(dispatchMock).toBeCalledWith(SessionActions.updateCourseResearchAgreement(true)); }); }); diff --git a/src/commons/sagas/AchievementSaga.ts b/src/commons/sagas/AchievementSaga.ts index f455545116..84b84733ed 100644 --- a/src/commons/sagas/AchievementSaga.ts +++ b/src/commons/sagas/AchievementSaga.ts @@ -1,31 +1,17 @@ -import { SagaIterator } from 'redux-saga'; import { call, delay, put, select } from 'redux-saga/effects'; +import AchievementActions from 'src/features/achievement/AchievementActions'; -import { - AchievementGoal, - ADD_EVENT, - BULK_UPDATE_ACHIEVEMENTS, - BULK_UPDATE_GOALS, - EventType, - GET_ACHIEVEMENTS, - GET_GOALS, - GET_OWN_GOALS, - GET_USER_ASSESSMENT_OVERVIEWS, - GET_USERS, - HANDLE_EVENT, - REMOVE_ACHIEVEMENT, - REMOVE_GOAL, - UPDATE_GOAL_PROGRESS, - UPDATE_OWN_GOAL_PROGRESS -} from '../../features/achievement/AchievementTypes'; +import { AchievementGoal, EventType } from '../../features/achievement/AchievementTypes'; import { updateGoalProcessed } from '../achievement/AchievementManualEditor'; import AchievementInferencer from '../achievement/utils/AchievementInferencer'; import { goalIncludesEvents, incrementCount } from '../achievement/utils/EventHandler'; import { OverallState } from '../application/ApplicationTypes'; import { Tokens } from '../application/types/SessionTypes'; +import { combineSagaHandlers } from '../redux/utils'; import { SideContentType } from '../sideContent/SideContentTypes'; import { actions } from '../utils/ActionsHelper'; import Constants from '../utils/Constants'; +import { selectTokens } from './BackendSaga'; import { bulkUpdateAchievements, bulkUpdateGoals, @@ -39,47 +25,31 @@ import { updateGoalProgress, updateOwnGoalProgress } from './RequestsSaga'; -import { safeTakeEvery as takeEvery } from './SafeEffects'; - -function selectTokens() { - return select((state: OverallState) => ({ - accessToken: state.session.accessToken, - refreshToken: state.session.refreshToken - })); -} -export default function* AchievementSaga(): SagaIterator { - yield takeEvery( - BULK_UPDATE_ACHIEVEMENTS, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); +const AchievementSaga = combineSagaHandlers(AchievementActions, { + bulkUpdateAchievements: function* (action) { + const tokens: Tokens = yield selectTokens(); - const achievements = action.payload; + const achievements = action.payload; - const resp = yield call(bulkUpdateAchievements, achievements, tokens); + const resp = yield call(bulkUpdateAchievements, achievements, tokens); - if (!resp) { - return; - } + if (!resp) { + return; } - ); - - yield takeEvery( - BULK_UPDATE_GOALS, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); + }, + bulkUpdateGoals: function* (action) { + const tokens: Tokens = yield selectTokens(); - const goals = action.payload; + const goals = action.payload; - const resp = yield call(bulkUpdateGoals, goals, tokens); + const resp = yield call(bulkUpdateGoals, goals, tokens); - if (!resp) { - return; - } + if (!resp) { + return; } - ); - - yield takeEvery(GET_ACHIEVEMENTS, function* (): any { + }, + getAchievements: function* () { const tokens: Tokens = yield selectTokens(); const achievements = yield call(getAchievements, tokens); @@ -87,9 +57,8 @@ export default function* AchievementSaga(): SagaIterator { if (achievements) { yield put(actions.saveAchievements(achievements)); } - }); - - yield takeEvery(GET_GOALS, function* (action: ReturnType): any { + }, + getGoals: function* (action) { const tokens: Tokens = yield selectTokens(); const studentCourseRegId = action.payload; @@ -99,9 +68,8 @@ export default function* AchievementSaga(): SagaIterator { if (goals) { yield put(actions.saveGoals(goals)); } - }); - - yield takeEvery(GET_OWN_GOALS, function* (action: ReturnType): any { + }, + getOwnGoals: function* (action) { const tokens: Tokens = yield selectTokens(); const goals = yield call(getOwnGoals, tokens); @@ -109,9 +77,8 @@ export default function* AchievementSaga(): SagaIterator { if (goals) { yield put(actions.saveGoals(goals)); } - }); - - yield takeEvery(GET_USERS, function* (action: ReturnType): any { + }, + getUsers: function* (action) { const tokens: Tokens = yield selectTokens(); const users = yield call(getAllUsers, tokens); @@ -119,24 +86,19 @@ export default function* AchievementSaga(): SagaIterator { if (users) { yield put(actions.saveUsers(users)); } - }); - - yield takeEvery( - REMOVE_ACHIEVEMENT, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); + }, + removeAchievement: function* (action) { + const tokens: Tokens = yield selectTokens(); - const achievement = action.payload; + const achievement = action.payload; - const resp = yield call(removeAchievement, achievement, tokens); + const resp = yield call(removeAchievement, achievement, tokens); - if (!resp) { - return; - } + if (!resp) { + return; } - ); - - yield takeEvery(REMOVE_GOAL, function* (action: ReturnType): any { + }, + removeGoal: function* (action) { const tokens: Tokens = yield selectTokens(); const definition = action.payload; @@ -146,47 +108,38 @@ export default function* AchievementSaga(): SagaIterator { if (!resp) { return; } - }); - - yield takeEvery( - UPDATE_OWN_GOAL_PROGRESS, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); + }, + updateOwnGoalProgress: function* (action) { + const tokens: Tokens = yield selectTokens(); - const progress = action.payload; + const progress = action.payload; - const resp = yield call(updateOwnGoalProgress, progress, tokens); + const resp = yield call(updateOwnGoalProgress, progress, tokens); - if (!resp) { - return; - } + if (!resp) { + return; } - ); - - yield takeEvery( - UPDATE_GOAL_PROGRESS, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); + }, + updateGoalProgress: function* (action) { + const tokens: Tokens = yield selectTokens(); - const { studentCourseRegId, progress } = action.payload; + const { studentCourseRegId, progress } = action.payload; - const resp = yield call(updateGoalProgress, studentCourseRegId, progress, tokens); + const resp = yield call(updateGoalProgress, studentCourseRegId, progress, tokens); - if (!resp) { - return; - } - if (resp.ok) { - yield put(actions.getGoals(studentCourseRegId)); - updateGoalProcessed(); - } + if (!resp) { + return; } - ); - - let loggedEvents: EventType[][] = []; - let timeoutSet: boolean = false; - const updateInterval = 3000; + if (resp.ok) { + yield put(actions.getGoals(studentCourseRegId)); + updateGoalProcessed(); + } + }, + addEvent: function* (action) { + let loggedEvents: EventType[][] = []; + let timeoutSet: boolean = false; + const updateInterval = 3000; - yield takeEvery(ADD_EVENT, function* (action: ReturnType): any { const role = yield select((state: OverallState) => state.session.role); const enableAchievements = yield select( (state: OverallState) => state.session.enableAchievements @@ -221,9 +174,8 @@ export default function* AchievementSaga(): SagaIterator { loggedEvents = []; } } - }); - - yield takeEvery(HANDLE_EVENT, function* (action: ReturnType): any { + }, + handleEvent: function* (action) { const tokens: Tokens = yield selectTokens(); // get the most recent list of achievements @@ -266,18 +218,16 @@ export default function* AchievementSaga(): SagaIterator { return; } } - }); - - yield takeEvery( - GET_USER_ASSESSMENT_OVERVIEWS, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); + }, + getUserAssessmentOverviews: function* (action) { + const tokens: Tokens = yield selectTokens(); - const assessmentOverviews = yield call(getUserAssessmentOverviews, action.payload, tokens); + const assessmentOverviews = yield call(getUserAssessmentOverviews, action.payload, tokens); - if (assessmentOverviews) { - yield put(actions.saveUserAssessmentOverviews(assessmentOverviews)); - } + if (assessmentOverviews) { + yield put(actions.saveUserAssessmentOverviews(assessmentOverviews)); } - ); -} + } +}); + +export default AchievementSaga; diff --git a/src/commons/sagas/BackendSaga.ts b/src/commons/sagas/BackendSaga.ts index 528ec97b54..321e7db12b 100644 --- a/src/commons/sagas/BackendSaga.ts +++ b/src/commons/sagas/BackendSaga.ts @@ -2,11 +2,8 @@ /*eslint-env browser*/ import { SagaIterator } from 'redux-saga'; import { all, call, fork, put, select } from 'redux-saga/effects'; -import { - addNewStoriesUsersToCourse, - addNewUsersToCourse, - createCourse -} from 'src/features/academy/AcademyActions'; +import AcademyActions from 'src/features/academy/AcademyActions'; +import GroundControlActions from 'src/features/groundControl/GroundControlActions'; import { postNewStoriesUsers } from 'src/features/stories/storiesComponents/BackendAccess'; import { UsernameRoleGroup } from 'src/pages/academy/adminPanel/subcomponents/AddUserPanel'; @@ -20,23 +17,14 @@ import { GradingQuery, GradingQuestion } from '../../features/grading/GradingTypes'; -import { - ASSIGN_ENTRIES_FOR_VOTING, - CHANGE_DATE_ASSESSMENT, - CHANGE_TEAM_SIZE_ASSESSMENT, - CONFIGURE_ASSESSMENT, - DELETE_ASSESSMENT, - PUBLISH_ASSESSMENT, - PUBLISH_GRADING_ALL, - UNPUBLISH_GRADING_ALL, - UPLOAD_ASSESSMENT -} from '../../features/groundControl/GroundControlTypes'; import { FETCH_SOURCECAST_INDEX } from '../../features/sourceRecorder/sourcecast/SourcecastTypes'; import { SAVE_SOURCECAST_DATA, SourcecastData } from '../../features/sourceRecorder/SourceRecorderTypes'; -import { DELETE_SOURCECAST_ENTRY } from '../../features/sourceRecorder/sourcereel/SourcereelTypes'; +import SourcereelActions, { + deleteSourcecastEntry as deleteSourcecastEntryAction +} from '../../features/sourceRecorder/sourcereel/SourcereelActions'; import { TeamFormationOverview } from '../../features/teamFormation/TeamFormationTypes'; import SessionActions from '../application/actions/SessionActions'; import { OverallState, Role } from '../application/ApplicationTypes'; @@ -48,7 +36,6 @@ import { NotificationConfiguration, TimeOption, Tokens, - UPDATE_COURSE_RESEARCH_AGREEMENT, UpdateCourseConfiguration, User } from '../application/types/SessionTypes'; @@ -68,7 +55,7 @@ import { combineSagaHandlers } from '../redux/utils'; import { actions } from '../utils/ActionsHelper'; import { computeRedirectUri, getClientId, getDefaultProvider } from '../utils/AuthHelper'; import { showSuccessMessage, showWarningMessage } from '../utils/notifications/NotificationsHelper'; -import { changeSublanguage } from '../workspace/WorkspaceActions'; +import WorkspaceActions from '../workspace/WorkspaceActions'; import { WorkspaceLocation } from '../workspace/WorkspaceTypes'; import { checkAnswerLastModifiedAt, @@ -143,7 +130,9 @@ export function* routerNavigate(path: string) { return router?.navigate(path); } -const newBackendSagaOne = combineSagaHandlers(SessionActions, { +// TODO: Refactor and combine in a future commit +const sagaActions = { ...SessionActions, ...SourcereelActions, ...AcademyActions }; +const newBackendSagaOne = combineSagaHandlers(sagaActions, { fetchAuth: function* (action): any { const { code, providerId: payloadProviderId } = action.payload; @@ -699,7 +688,7 @@ function* sendGradeAndContinue(action: ReturnType): any { const role: Role = yield select((state: OverallState) => state.session.role!); if (role === Role.Student) { @@ -826,7 +815,7 @@ function* oldBackendSagaOne(): SagaIterator { ); yield takeEvery( - changeSublanguage.type, + WorkspaceActions.changeSublanguage.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const { sublang } = action.payload; @@ -850,7 +839,7 @@ function* oldBackendSagaOne(): SagaIterator { ); } -const newBackendSagaThree = combineSagaHandlers(SessionActions, { +const newBackendSagaThree = combineSagaHandlers(sagaActions, { updateLatestViewedCourse: function* (action) { const tokens: Tokens = yield selectTokens(); const { courseId } = action.payload; @@ -1035,133 +1024,108 @@ const newBackendSagaThree = combineSagaHandlers(SessionActions, { if (courseRegistrations) { yield put(actions.setAdminPanelCourseRegistrations(courseRegistrations)); } - } -}); - -function* oldBackendSagaTwo(): SagaIterator { - yield takeEvery( - createCourse.type, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); - const courseConfig: UpdateCourseConfiguration = action.payload; - - const resp: Response | null = yield call(postCreateCourse, tokens, courseConfig); - if (!resp || !resp.ok) { - return yield handleResponseError(resp); - } + }, + createCourse: function* (action) { + const tokens: Tokens = yield selectTokens(); + const courseConfig: UpdateCourseConfiguration = action.payload; - const { - user, - courseRegistration, - courseConfiguration - }: { - user: User | null; - courseRegistration: CourseRegistration | null; - courseConfiguration: CourseConfiguration | null; - assessmentConfigurations: AssessmentConfiguration[] | null; - } = yield call(getUser, tokens); - - if (!user || !courseRegistration || !courseConfiguration) { - return yield showWarningMessage('An error occurred. Please try again.'); - } + const resp: Response | null = yield call(postCreateCourse, tokens, courseConfig); + if (!resp || !resp.ok) { + return yield handleResponseError(resp); + } - /** - * setUser updates the Dropdown course selection menu with the updated courses - * - * Setting the role here handles an edge case where a user creates his first ever course. - * Without it, the history.push below would not work as the /courses routes will not be rendered - * (see Application.tsx) - */ - yield put(actions.setUser(user)); - yield put(actions.setCourseRegistration({ role: Role.Student })); + const { + user, + courseRegistration, + courseConfiguration + }: { + user: User | null; + courseRegistration: CourseRegistration | null; + courseConfiguration: CourseConfiguration | null; + assessmentConfigurations: AssessmentConfiguration[] | null; + } = yield call(getUser, tokens); - if (courseConfiguration.enableStories) { - yield put(actions.getStoriesUser()); - // TODO: Fetch associated stories group ID - } else { - yield put(actions.clearStoriesUserAndGroup()); - } + if (!user || !courseRegistration || !courseConfiguration) { + return yield showWarningMessage('An error occurred. Please try again.'); + } - const placeholderAssessmentConfig: AssessmentConfiguration[] = [ - { - type: 'Missions', - assessmentConfigId: -1, - isManuallyGraded: true, - isGradingAutoPublished: false, - displayInDashboard: true, - hoursBeforeEarlyXpDecay: 0, - hasTokenCounter: false, - hasVotingFeatures: false, - earlySubmissionXp: 0 - } - ]; - - const resp1: Response | null = yield call( - putAssessmentConfigs, - tokens, - placeholderAssessmentConfig, - courseRegistration.courseId - ); - if (!resp1 || !resp1.ok) { - return yield handleResponseError(resp); - } + /** + * setUser updates the Dropdown course selection menu with the updated courses + * + * Setting the role here handles an edge case where a user creates his first ever course. + * Without it, the history.push below would not work as the /courses routes will not be rendered + * (see Application.tsx) + */ + yield put(actions.setUser(user)); + yield put(actions.setCourseRegistration({ role: Role.Student })); - yield call(showSuccessMessage, 'Successfully created your new course!'); - yield routerNavigate(`/courses/${courseRegistration.courseId}`); + if (courseConfiguration.enableStories) { + yield put(actions.getStoriesUser()); + // TODO: Fetch associated stories group ID + } else { + yield put(actions.clearStoriesUserAndGroup()); } - ); - yield takeEvery( - addNewUsersToCourse.type, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); - const { users, provider }: { users: UsernameRoleGroup[]; provider: string } = action.payload; - - const resp: Response | null = yield call(putNewUsers, tokens, users, provider); - if (!resp || !resp.ok) { - return yield handleResponseError(resp); + const placeholderAssessmentConfig: AssessmentConfiguration[] = [ + { + type: 'Missions', + assessmentConfigId: -1, + isManuallyGraded: true, + isGradingAutoPublished: false, + displayInDashboard: true, + hoursBeforeEarlyXpDecay: 0, + hasTokenCounter: false, + hasVotingFeatures: false, + earlySubmissionXp: 0 } + ]; - yield put(actions.fetchAdminPanelCourseRegistrations()); - yield call(showSuccessMessage, 'Users added!'); + const resp1: Response | null = yield call( + putAssessmentConfigs, + tokens, + placeholderAssessmentConfig, + courseRegistration.courseId + ); + if (!resp1 || !resp1.ok) { + return yield handleResponseError(resp); } - ); - - yield takeEvery( - addNewStoriesUsersToCourse.type, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); - const { users, provider } = action.payload; - yield call(postNewStoriesUsers, tokens, users, provider); + yield call(showSuccessMessage, 'Successfully created your new course!'); + yield routerNavigate(`/courses/${courseRegistration.courseId}`); + }, + addNewUsersToCourse: function* (action) { + const tokens: Tokens = yield selectTokens(); + const { users, provider }: { users: UsernameRoleGroup[]; provider: string } = action.payload; - // TODO: Refresh the list of story users - // once that page is implemented + const resp: Response | null = yield call(putNewUsers, tokens, users, provider); + if (!resp || !resp.ok) { + return yield handleResponseError(resp); } - ); - yield takeEvery( - UPDATE_COURSE_RESEARCH_AGREEMENT, - function* (action: ReturnType): any { - const tokens: Tokens = yield selectTokens(); - const { agreedToResearch } = action.payload; + yield put(actions.fetchAdminPanelCourseRegistrations()); + yield call(showSuccessMessage, 'Users added!'); + }, + addNewStoriesUsersToCourse: function* (action) { + const tokens: Tokens = yield selectTokens(); + const { users, provider } = action.payload; - const resp: Response | null = yield call( - putCourseResearchAgreement, - tokens, - agreedToResearch - ); - if (!resp || !resp.ok) { - return yield handleResponseError(resp); - } + yield call(postNewStoriesUsers, tokens, users, provider); + + // TODO: Refresh the list of story users + // once that page is implemented + }, + updateCourseResearchAgreement: function* (action) { + const tokens: Tokens = yield selectTokens(); + const { agreedToResearch } = action.payload; - yield put(actions.setCourseRegistration({ agreedToResearch })); - yield call(showSuccessMessage, 'Research preference saved!'); + const resp: Response | null = yield call(putCourseResearchAgreement, tokens, agreedToResearch); + if (!resp || !resp.ok) { + return yield handleResponseError(resp); } - ); -} -const newBackendSagaFour = combineSagaHandlers(SessionActions, { + yield put(actions.setCourseRegistration({ agreedToResearch })); + yield call(showSuccessMessage, 'Research preference saved!'); + }, updateUserRole: function* (action): any { const tokens: Tokens = yield selectTokens(); const { courseRegId, role }: { courseRegId: number; role: Role } = action.payload; @@ -1202,7 +1166,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - CHANGE_DATE_ASSESSMENT, + GroundControlActions.changeDateAssessment.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload.id; @@ -1220,7 +1184,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - CHANGE_TEAM_SIZE_ASSESSMENT, + GroundControlActions.changeTeamSizeAssessment.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload.id; @@ -1237,7 +1201,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - DELETE_ASSESSMENT, + GroundControlActions.deleteAssessment.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload; @@ -1253,7 +1217,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - PUBLISH_ASSESSMENT, + GroundControlActions.publishAssessment.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload.id; @@ -1279,7 +1243,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - UPLOAD_ASSESSMENT, + GroundControlActions.uploadAssessment.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const { file, forceUpdate, assessmentConfigId } = action.payload; @@ -1306,7 +1270,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - CONFIGURE_ASSESSMENT, + GroundControlActions.configureAssessment.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload.id; @@ -1328,7 +1292,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - ASSIGN_ENTRIES_FOR_VOTING, + GroundControlActions.assignEntriesForVoting.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload.id; @@ -1350,7 +1314,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - PUBLISH_GRADING_ALL, + GroundControlActions.publishGradingAll.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload; @@ -1366,7 +1330,7 @@ function* oldBackendSagaThree(): SagaIterator { ); yield takeEvery( - UNPUBLISH_GRADING_ALL, + GroundControlActions.unpublishGradingAll.type, function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const id = action.payload; @@ -1399,9 +1363,7 @@ function* BackendSaga(): SagaIterator { fork(newBackendSagaOne), fork(newBackendSagaTwo), fork(newBackendSagaThree), - fork(newBackendSagaFour), fork(oldBackendSagaOne), - fork(oldBackendSagaTwo), fork(oldBackendSagaThree) ]); } diff --git a/src/commons/sagas/PlaygroundSaga.ts b/src/commons/sagas/PlaygroundSaga.ts index 2e99e0cc7c..de524dc246 100644 --- a/src/commons/sagas/PlaygroundSaga.ts +++ b/src/commons/sagas/PlaygroundSaga.ts @@ -17,19 +17,10 @@ import { isSchemeLanguage, isSourceLanguage, OverallState } from '../application import { ExternalLibraryName } from '../application/types/ExternalTypes'; import { retrieveFilesInWorkspaceAsRecord } from '../fileSystem/utils'; import { visitSideContent } from '../sideContent/SideContentActions'; -import { SideContentType, VISIT_SIDE_CONTENT } from '../sideContent/SideContentTypes'; +import { SideContentType } from '../sideContent/SideContentTypes'; import Constants from '../utils/Constants'; import { showSuccessMessage, showWarningMessage } from '../utils/notifications/NotificationsHelper'; -import { - clearReplOutput, - setEditorHighlightedLines, - toggleUpdateCse, - toggleUsingCse, - toggleUsingSubst, - toggleUsingUpload, - updateCurrentStep, - updateStepsTotal -} from '../workspace/WorkspaceActions'; +import WorkspaceActions from '../workspace/WorkspaceActions'; import { EditorTabState, PlaygroundWorkspaceState } from '../workspace/WorkspaceTypes'; import { safeTakeEvery as takeEvery } from './SafeEffects'; @@ -71,7 +62,7 @@ export default function* PlaygroundSaga(): SagaIterator { }); yield takeEvery( - VISIT_SIDE_CONTENT, + visitSideContent.type, function* ({ payload: { newId, prevId, workspaceLocation } }: ReturnType) { @@ -94,23 +85,23 @@ export default function* PlaygroundSaga(): SagaIterator { const hasBreakpoints = editorTabs.find(({ breakpoints }) => breakpoints.find(x => !!x)); if (!hasBreakpoints) { - yield put(toggleUsingSubst(false, workspaceLocation)); - yield put(clearReplOutput(workspaceLocation)); + yield put(WorkspaceActions.toggleUsingSubst(false, workspaceLocation)); + yield put(WorkspaceActions.clearReplOutput(workspaceLocation)); } } if (newId !== SideContentType.cseMachine) { - yield put(toggleUsingCse(false, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingCse(false, workspaceLocation)); yield call([CseMachine, CseMachine.clearCse]); yield call([JavaCseMachine, JavaCseMachine.clearCse]); - yield put(updateCurrentStep(-1, workspaceLocation)); - yield put(updateStepsTotal(0, workspaceLocation)); - yield put(toggleUpdateCse(true, workspaceLocation)); - yield put(setEditorHighlightedLines(workspaceLocation, 0, [])); + yield put(WorkspaceActions.updateCurrentStep(-1, workspaceLocation)); + yield put(WorkspaceActions.updateStepsTotal(0, workspaceLocation)); + yield put(WorkspaceActions.toggleUpdateCse(true, workspaceLocation)); + yield put(WorkspaceActions.setEditorHighlightedLines(workspaceLocation, 0, [])); } if (playgroundSourceChapter === Chapter.FULL_JAVA && newId === SideContentType.cseMachine) { - yield put(toggleUsingCse(true, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingCse(true, workspaceLocation)); } if ( @@ -118,20 +109,20 @@ export default function* PlaygroundSaga(): SagaIterator { (newId === SideContentType.substVisualizer || newId === SideContentType.cseMachine) ) { if (playgroundSourceChapter <= Chapter.SOURCE_2) { - yield put(toggleUsingSubst(true, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingSubst(true, workspaceLocation)); } else { - yield put(toggleUsingCse(true, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingCse(true, workspaceLocation)); } } if (newId === SideContentType.upload) { - yield put(toggleUsingUpload(true, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingUpload(true, workspaceLocation)); } else { - yield put(toggleUsingUpload(false, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingUpload(false, workspaceLocation)); } if (isSchemeLanguage(playgroundSourceChapter) && newId === SideContentType.cseMachine) { - yield put(toggleUsingCse(true, workspaceLocation)); + yield put(WorkspaceActions.toggleUsingCse(true, workspaceLocation)); } } ); diff --git a/src/commons/sagas/RemoteExecutionSaga.ts b/src/commons/sagas/RemoteExecutionSaga.ts index 5fbe1e2ce2..48000bcf81 100644 --- a/src/commons/sagas/RemoteExecutionSaga.ts +++ b/src/commons/sagas/RemoteExecutionSaga.ts @@ -3,8 +3,8 @@ import { assemble, compileFiles, Context } from 'js-slang'; import { ExceptionError } from 'js-slang/dist/errors/errors'; import { Chapter, Variant } from 'js-slang/dist/types'; import _ from 'lodash'; -import { SagaIterator } from 'redux-saga'; import { call, put, race, select, take } from 'redux-saga/effects'; +import RemoteExecutionActions from 'src/features/remoteExecution/RemoteExecutionActions'; import { Ev3DevicePeripherals, Ev3MotorData, @@ -16,10 +16,6 @@ import { Device, DeviceSession, deviceTypesById, - REMOTE_EXEC_CONNECT, - REMOTE_EXEC_DISCONNECT, - REMOTE_EXEC_FETCH_DEVICES, - REMOTE_EXEC_RUN, WebSocketEndpointInformation } from 'src/features/remoteExecution/RemoteExecutionTypes'; import { store } from 'src/pages/createStore'; @@ -27,18 +23,21 @@ import { store } from 'src/pages/createStore'; import InterpreterActions from '../application/actions/InterpreterActions'; import { OverallState } from '../application/ApplicationTypes'; import { ExternalLibraryName } from '../application/types/ExternalTypes'; +import { combineSagaHandlers } from '../redux/utils'; import { actions } from '../utils/ActionsHelper'; import { MaybePromise } from '../utils/TypeHelper'; import { fetchDevices, getDeviceWSEndpoint } from './RequestsSaga'; -import { safeTakeEvery as takeEvery, safeTakeLatest as takeLatest } from './SafeEffects'; const dummyLocation = { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } }; -export function* remoteExecutionSaga(): SagaIterator { - yield takeLatest(REMOTE_EXEC_FETCH_DEVICES, function* () { +// TODO: Refactor and combine in a future commit +const sagaActions = { ...RemoteExecutionActions, ...InterpreterActions }; +const RemoteExecutionSaga = combineSagaHandlers(sagaActions, { + // TODO: Should be `takeLatest`, not `takeEvery` + remoteExecFetchDevices: function* () { const [tokens, session]: [any, DeviceSession | undefined] = yield select( (state: OverallState) => [ { @@ -64,204 +63,195 @@ export function* remoteExecutionSaga(): SagaIterator { }) ); } - }); - - yield takeEvery( - REMOTE_EXEC_CONNECT, - function* (action: ReturnType) { - const [tokens, session]: [any, DeviceSession | undefined] = yield select( - (state: OverallState) => [ - { - accessToken: state.session.accessToken, - refreshToken: state.session.refreshToken - }, - state.session.remoteExecutionSession - ] - ); - const { device, workspace } = action.payload; + }, + remoteExecConnect: function* (action): any { + const [tokens, session]: [any, DeviceSession | undefined] = yield select( + (state: OverallState) => [ + { + accessToken: state.session.accessToken, + refreshToken: state.session.refreshToken + }, + state.session.remoteExecutionSession + ] + ); + const { device, workspace } = action.payload; + yield put( + actions.remoteExecUpdateSession({ + device, + workspace, + connection: { status: 'CONNECTING' } + }) + ); + const endpoint: WebSocketEndpointInformation | null = yield call( + getDeviceWSEndpoint, + device, + tokens + ); + if (!endpoint) { yield put( actions.remoteExecUpdateSession({ - device, - workspace, - connection: { status: 'CONNECTING' } + ...action.payload, + connection: { status: 'FAILED', error: 'Could not retrieve MQTT endpoint' } }) ); - const endpoint: WebSocketEndpointInformation | null = yield call( - getDeviceWSEndpoint, - device, - tokens + return; + } + + const oldClient = session?.connection.client; + if (oldClient) { + try { + oldClient.disconnect(); + } catch {} + } + const client: SlingClient = new SlingClient({ + clientId: `${endpoint.clientNamePrefix}${generateClientNonce()}`, + deviceId: endpoint.thingName, + websocketEndpoint: endpoint.endpoint + }); + client.on('statusChange', isRunning => { + store.dispatch( + actions.updateWorkspace(workspace, { + isRunning + }) ); - if (!endpoint) { - yield put( - actions.remoteExecUpdateSession({ - ...action.payload, - connection: { status: 'FAILED', error: 'Could not retrieve MQTT endpoint' } - }) - ); - return; - } + }); + client.on('monitor', message => { + const port = message[0].split(':')[1]; + const key = `port${port.substring(port.length - 1)}` as keyof Ev3DevicePeripherals; + const currentSession = store.getState().session.remoteExecutionSession!; // Guaranteed valid session - const oldClient = session?.connection.client; - if (oldClient) { - try { - oldClient.disconnect(); - } catch {} - } - const client: SlingClient = new SlingClient({ - clientId: `${endpoint.clientNamePrefix}${generateClientNonce()}`, - deviceId: endpoint.thingName, - websocketEndpoint: endpoint.endpoint - }); - client.on('statusChange', isRunning => { + const dispatchAction = (peripheralData: Ev3MotorData | Ev3SensorData) => store.dispatch( - actions.updateWorkspace(workspace, { - isRunning + actions.remoteExecUpdateSession({ + ...currentSession, + device: { + ...currentSession.device, + peripherals: { + ..._.pickBy( + currentSession.device.peripherals, + p => Date.now() - p.lastUpdated < 3000 + ), + [key]: { ...peripheralData, lastUpdated: Date.now() } + } + } }) ); - }); - client.on('monitor', message => { - const port = message[0].split(':')[1]; - const key = `port${port.substring(port.length - 1)}` as keyof Ev3DevicePeripherals; - const currentSession = store.getState().session.remoteExecutionSession!; // Guaranteed valid session - const dispatchAction = (peripheralData: Ev3MotorData | Ev3SensorData) => - store.dispatch( - actions.remoteExecUpdateSession({ - ...currentSession, - device: { - ...currentSession.device, - peripherals: { - ..._.pickBy( - currentSession.device.peripherals, - p => Date.now() - p.lastUpdated < 3000 - ), - [key]: { ...peripheralData, lastUpdated: Date.now() } - } - } - }) - ); - - if (message[1].endsWith('motor')) { - const type = message[1] as Ev3MotorTypes; - const position = parseInt(message[2]); - const speed = parseInt(message[3]); - const peripheralData: Ev3MotorData = { type, position, speed }; - dispatchAction(peripheralData); - } else { - const type = message[1] as Ev3SensorTypes; - const mode = message[2] as any; - const value = parseInt(message[3]); - const peripheralData: Ev3SensorData = { type, mode, value }; - dispatchAction(peripheralData); - } - }); + if (message[1].endsWith('motor')) { + const type = message[1] as Ev3MotorTypes; + const position = parseInt(message[2]); + const speed = parseInt(message[3]); + const peripheralData: Ev3MotorData = { type, position, speed }; + dispatchAction(peripheralData); + } else { + const type = message[1] as Ev3SensorTypes; + const mode = message[2] as any; + const value = parseInt(message[3]); + const peripheralData: Ev3SensorData = { type, mode, value }; + dispatchAction(peripheralData); + } + }); - client.on('display', (message, type) => { - switch (type) { - case 'output': - store.dispatch(actions.handleConsoleLog(workspace, `${message}`)); - break; - case 'error': { - const error = new ExceptionError(new Error(`${message}`), dummyLocation); - store.dispatch(actions.evalInterpreterError([error], workspace)); - break; - } - case 'result': - store.dispatch(actions.evalInterpreterSuccess(message, workspace)); - break; + client.on('display', (message, type) => { + switch (type) { + case 'output': + store.dispatch(actions.handleConsoleLog(workspace, `${message}`)); + break; + case 'error': { + const error = new ExceptionError(new Error(`${message}`), dummyLocation); + store.dispatch(actions.evalInterpreterError([error], workspace)); + break; } - }); - const deviceType = deviceTypesById.get(device.type); + case 'result': + store.dispatch(actions.evalInterpreterSuccess(message, workspace)); + break; + } + }); + const deviceType = deviceTypesById.get(device.type); - yield put( - actions.remoteExecUpdateSession({ - device, - workspace, - connection: { status: 'CONNECTING', client, endpoint } - }) - ); - yield put( - actions.updateWorkspace(workspace, { - isRunning: false, - isEditorAutorun: false, - isDebugging: false, - externalLibrary: deviceType?.deviceLibraryName, - output: [] - }) - ); - yield put( - actions.beginClearContext( - workspace, - { - chapter: deviceType?.languageChapter || Chapter.SOURCE_3, - variant: Variant.DEFAULT, - external: { - name: deviceType?.deviceLibraryName || ExternalLibraryName.NONE, - symbols: deviceType?.internalFunctions || [] - }, - globals: [] + yield put( + actions.remoteExecUpdateSession({ + device, + workspace, + connection: { status: 'CONNECTING', client, endpoint } + }) + ); + yield put( + actions.updateWorkspace(workspace, { + isRunning: false, + isEditorAutorun: false, + isDebugging: false, + externalLibrary: deviceType?.deviceLibraryName, + output: [] + }) + ); + yield put( + actions.beginClearContext( + workspace, + { + chapter: deviceType?.languageChapter || Chapter.SOURCE_3, + variant: Variant.DEFAULT, + external: { + name: deviceType?.deviceLibraryName || ExternalLibraryName.NONE, + symbols: deviceType?.internalFunctions || [] }, - false - ) - ); - try { - const connectPromise = new Promise((resolve, reject) => { - try { - client.once('connect', () => resolve(true)); - client.once('error', reject); - client.connect(); - } catch { - reject(); - } - }); - const connectOrCancel: { - connect?: boolean; - } = yield race({ - connect: connectPromise, - reconnect: take(REMOTE_EXEC_CONNECT), - disconnect: take(REMOTE_EXEC_DISCONNECT) - }); - if (connectOrCancel.connect) { - yield put( - actions.remoteExecUpdateSession({ - ...action.payload, - connection: { status: 'CONNECTED', client, endpoint } - }) - ); - } else { - client.disconnect(); + globals: [] + }, + false + ) + ); + try { + const connectPromise = new Promise((resolve, reject) => { + try { + client.once('connect', () => resolve(true)); + client.once('error', reject); + client.connect(); + } catch { + reject(); } - } catch (err) { - client.disconnect(); + }); + const connectOrCancel: { + connect?: boolean; + } = yield race({ + connect: connectPromise, + reconnect: take(RemoteExecutionActions.remoteExecConnect.type), + disconnect: take(RemoteExecutionActions.remoteExecDisconnect.type) + }); + if (connectOrCancel.connect) { yield put( actions.remoteExecUpdateSession({ ...action.payload, - connection: { status: 'FAILED', client, error: err.toString() } + connection: { status: 'CONNECTED', client, endpoint } }) ); + } else { + client.disconnect(); } - } - ); - - yield takeLatest( - REMOTE_EXEC_DISCONNECT, - function* (action: ReturnType) { - const session: DeviceSession | undefined = yield select( - (state: OverallState) => state.session.remoteExecutionSession + } catch (err) { + client.disconnect(); + yield put( + actions.remoteExecUpdateSession({ + ...action.payload, + connection: { status: 'FAILED', client, error: err.toString() } + }) ); - if (!session) { - return; - } - const oldClient = session.connection.client; - if (oldClient) { - oldClient.disconnect(); - } - yield put(actions.remoteExecUpdateSession(undefined)); - yield put(actions.externalLibrarySelect(ExternalLibraryName.NONE, session.workspace, true)); } - ); - - yield takeEvery(REMOTE_EXEC_RUN, function* (action: ReturnType) { + }, + remoteExecDisconnect: function* (action) { + const session: DeviceSession | undefined = yield select( + (state: OverallState) => state.session.remoteExecutionSession + ); + if (!session) { + return; + } + const oldClient = session.connection.client; + if (oldClient) { + oldClient.disconnect(); + } + yield put(actions.remoteExecUpdateSession(undefined)); + yield put(actions.externalLibrarySelect(ExternalLibraryName.NONE, session.workspace, true)); + }, + remoteExecRun: function* (action) { const { files, entrypointFilePath } = action.payload; const session: DeviceSession | undefined = yield select( @@ -302,9 +292,8 @@ export function* remoteExecutionSaga(): SagaIterator { const assembled = assemble(compiled); client.sendRun(Buffer.from(assembled)); - }); - - yield takeEvery(InterpreterActions.beginInterruptExecution.type, function* () { + }, + beginInterruptExecution: function* () { const session: DeviceSession | undefined = yield select( (state: OverallState) => state.session.remoteExecutionSession ); @@ -313,8 +302,8 @@ export function* remoteExecutionSaga(): SagaIterator { } session.connection.client.sendStop(); - }); -} + } +}); const ALPHANUMERIC = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; @@ -324,4 +313,4 @@ const generateClientNonce = () => .map(_ => ALPHANUMERIC[Math.floor(Math.random() * ALPHANUMERIC.length)]) .join(''); -export default remoteExecutionSaga; +export default RemoteExecutionSaga; diff --git a/src/commons/sagas/SideContentSaga.ts b/src/commons/sagas/SideContentSaga.ts index 94a1665d38..38b2381a26 100644 --- a/src/commons/sagas/SideContentSaga.ts +++ b/src/commons/sagas/SideContentSaga.ts @@ -1,55 +1,46 @@ import { Action } from 'redux'; -import type { SagaIterator } from 'redux-saga'; import { put, take } from 'redux-saga/effects'; -import { notifyStoriesEvaluated } from 'src/features/stories/StoriesActions'; +import StoriesActions from 'src/features/stories/StoriesActions'; -import * as actions from '../sideContent/SideContentActions'; -import { BEGIN_ALERT_SIDE_CONTENT, SPAWN_SIDE_CONTENT } from '../sideContent/SideContentTypes'; -import { notifyProgramEvaluated } from '../workspace/WorkspaceActions'; -import { safeTakeEvery as takeEvery } from './SafeEffects'; +import { combineSagaHandlers } from '../redux/utils'; +import SideContentActions from '../sideContent/SideContentActions'; +import WorkspaceActions from '../workspace/WorkspaceActions'; const isSpawnSideContent = ( action: Action -): action is ReturnType => action.type === SPAWN_SIDE_CONTENT; +): action is ReturnType => + action.type === SideContentActions.spawnSideContent.type; -export default function* SideContentSaga(): SagaIterator { - yield takeEvery( - BEGIN_ALERT_SIDE_CONTENT, - function* ({ - payload: { id, workspaceLocation } - }: ReturnType) { - // When a program finishes evaluation, we clear all alerts, - // So we must wait until after and all module tabs have been spawned - // to process any kind of alerts that were raised by non-module side content - yield take( - (action: Action) => - isSpawnSideContent(action) && action.payload.workspaceLocation === workspaceLocation - ); - yield put(actions.endAlertSideContent(id, workspaceLocation)); - } - ); +// TODO: Refactor and combine in a future commit +const sagaActions = { ...SideContentActions, ...WorkspaceActions, ...StoriesActions }; +export const SideContentSaga = combineSagaHandlers(sagaActions, { + beginAlertSideContent: function* ({ payload: { id, workspaceLocation } }) { + // When a program finishes evaluation, we clear all alerts, + // So we must wait until after and all module tabs have been spawned + // to process any kind of alerts that were raised by non-module side content + yield take( + (action: Action) => + isSpawnSideContent(action) && action.payload.workspaceLocation === workspaceLocation + ); + yield put(SideContentActions.endAlertSideContent(id, workspaceLocation)); + }, + notifyProgramEvaluated: function* (action) { + if (!action.payload.workspaceLocation || action.payload.workspaceLocation === 'stories') return; - yield takeEvery( - notifyProgramEvaluated.type, - function* (action: ReturnType) { - if (!action.payload.workspaceLocation || action.payload.workspaceLocation === 'stories') - return; + const debuggerContext = { + result: action.payload.result, + lastDebuggerResult: action.payload.lastDebuggerResult, + code: action.payload.code, + context: action.payload.context, + workspaceLocation: action.payload.workspaceLocation + }; + yield put( + SideContentActions.spawnSideContent(action.payload.workspaceLocation, debuggerContext) + ); + }, + notifyStoriesEvaluated: function* (action) { + yield put(SideContentActions.spawnSideContent(`stories.${action.payload.env}`, action.payload)); + } +}); - const debuggerContext = { - result: action.payload.result, - lastDebuggerResult: action.payload.lastDebuggerResult, - code: action.payload.code, - context: action.payload.context, - workspaceLocation: action.payload.workspaceLocation - }; - yield put(actions.spawnSideContent(action.payload.workspaceLocation, debuggerContext)); - } - ); - - yield takeEvery( - notifyStoriesEvaluated.type, - function* (action: ReturnType) { - yield put(actions.spawnSideContent(`stories.${action.payload.env}`, action.payload)); - } - ); -} +export default SideContentSaga; diff --git a/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts b/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts index 0cf922a454..a12b6f3100 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts @@ -2,7 +2,7 @@ import { Context } from 'js-slang'; import { defineSymbol } from 'js-slang/dist/createContext'; import { Variant } from 'js-slang/dist/types'; import { put, select, take } from 'redux-saga/effects'; -import { endClearContext } from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; import { OverallState } from '../../../application/ApplicationTypes'; import { ExternalLibraryName } from '../../../application/types/ExternalTypes'; @@ -37,7 +37,7 @@ export function* clearContext(workspaceLocation: WorkspaceLocation, entrypointCo // Clear the context, with the same chapter and externalSymbols as before. yield put(actions.beginClearContext(workspaceLocation, library, false)); // Wait for the clearing to be done. - yield take(endClearContext.type); + yield take(WorkspaceActions.endClearContext.type); const context: Context = yield select( (state: OverallState) => state.workspaces[workspaceLocation].context diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts index 2e7c67190c..2b813836fd 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -20,7 +20,7 @@ import { actions } from '../../../utils/ActionsHelper'; import DisplayBufferService from '../../../utils/DisplayBufferService'; import { showWarningMessage } from '../../../utils/notifications/NotificationsHelper'; import { makeExternalBuiltins as makeSourcerorExternalBuiltins } from '../../../utils/SourcerorHelper'; -import { evalEditor, evalRepl, notifyProgramEvaluated } from '../../../workspace/WorkspaceActions'; +import WorkspaceActions from '../../../workspace/WorkspaceActions'; import { EVAL_SILENT, PlaygroundWorkspaceState, @@ -40,7 +40,8 @@ export function* evalCodeSaga( storyEnv?: string ): SagaIterator { context.runtime.debuggerOn = - (actionType === evalEditor.type || actionType === InterpreterActions.debuggerResume.type) && + (actionType === WorkspaceActions.evalEditor.type || + actionType === InterpreterActions.debuggerResume.type) && context.chapter > 2; const isStoriesBlock = actionType === actions.evalStory.type || workspaceLocation === 'stories'; @@ -134,7 +135,12 @@ export function* evalCodeSaga( }); } else if (variant === Variant.WASM) { // Note: WASM does not support multiple file programs. - return call(wasm_compile_and_run, entrypointCode, context, actionType === evalRepl.type); + return call( + wasm_compile_and_run, + entrypointCode, + context, + actionType === WorkspaceActions.evalRepl.type + ); } else { throw new Error('Unknown variant: ' + variant); } @@ -321,7 +327,7 @@ export function* evalCodeSaga( return; } - if (actionType === evalEditor.type) { + if (actionType === WorkspaceActions.evalEditor.type) { yield put(actions.updateLastDebuggerResult(result, workspaceLocation)); } @@ -387,7 +393,7 @@ export function* evalCodeSaga( yield* dumpDisplayBuffer(workspaceLocation, isStoriesBlock, storyEnv); // Change token count if its assessment and EVAL_EDITOR - if (actionType === evalEditor.type && workspaceLocation === 'assessment') { + if (actionType === WorkspaceActions.evalEditor.type && workspaceLocation === 'assessment') { const tokens = [...tokenizer(entrypointCode, ACORN_PARSE_OPTIONS)]; const tokenCounter = tokens.length; yield put(actions.setTokenCount(workspaceLocation, tokenCounter)); @@ -408,15 +414,21 @@ export function* evalCodeSaga( ); // For EVAL_EDITOR and EVAL_REPL, we send notification to workspace that a program has been evaluated if ( - actionType === evalEditor.type || - actionType === evalRepl.type || + actionType === WorkspaceActions.evalEditor.type || + actionType === WorkspaceActions.evalRepl.type || actionType === InterpreterActions.debuggerResume.type ) { if (context.errors.length > 0) { yield put(actions.addEvent([EventType.ERROR])); } yield put( - notifyProgramEvaluated(result, lastDebuggerResult, entrypointCode, context, workspaceLocation) + WorkspaceActions.notifyProgramEvaluated( + result, + lastDebuggerResult, + entrypointCode, + context, + workspaceLocation + ) ); } if (isStoriesBlock) { diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts index 4386903c54..4e7cb11ca2 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts @@ -1,6 +1,6 @@ import { FSModule } from 'browserfs/dist/node/core/FS'; import { call, put, select, StrictEffect } from 'redux-saga/effects'; -import { evalEditor } from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; import { EventType } from '../../../../features/achievement/AchievementTypes'; import { DeviceSession } from '../../../../features/remoteExecution/RemoteExecutionTypes'; @@ -113,7 +113,7 @@ export function* evalEditorSaga( context, execTime, workspaceLocation, - evalEditor.type + WorkspaceActions.evalEditor.type ); } } diff --git a/src/commons/sagas/WorkspaceSaga/index.ts b/src/commons/sagas/WorkspaceSaga/index.ts index a995e50463..d286bf6f40 100644 --- a/src/commons/sagas/WorkspaceSaga/index.ts +++ b/src/commons/sagas/WorkspaceSaga/index.ts @@ -5,7 +5,7 @@ import Phaser from 'phaser'; import { call, put, select } from 'redux-saga/effects'; import InterpreterActions from 'src/commons/application/actions/InterpreterActions'; import { combineSagaHandlers } from 'src/commons/redux/utils'; -import WorkspaceActions, { evalRepl } from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; import CseMachine from 'src/features/cseMachine/CseMachine'; import { EventType } from '../../../features/achievement/AchievementTypes'; @@ -246,7 +246,7 @@ const WorkspaceSaga = combineSagaHandlers( context, execTime, workspaceLocation, - evalRepl.type + WorkspaceActions.evalRepl.type ); }, debuggerResume: function* (action) { diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts index 9635b24e6d..92c72f527c 100644 --- a/src/commons/sagas/__tests__/BackendSaga.ts +++ b/src/commons/sagas/__tests__/BackendSaga.ts @@ -11,7 +11,7 @@ import { FETCH_GROUP_GRADING_SUMMARY, UPDATE_GROUP_GRADING_SUMMARY } from '../../../features/dashboard/DashboardTypes'; -import SessionActions, { updateAssessment } from '../../application/actions/SessionActions'; +import SessionActions from '../../application/actions/SessionActions'; import { GameState, Role, @@ -23,8 +23,6 @@ import { AdminPanelCourseRegistration, CourseConfiguration, CourseRegistration, - UPDATE_ASSESSMENT, - UPDATE_COURSE_RESEARCH_AGREEMENT, UpdateCourseConfiguration, User } from '../../application/types/SessionTypes'; @@ -48,7 +46,7 @@ import { showSuccessMessage, showWarningMessage } from '../../utils/notifications/NotificationsHelper'; -import { changeSublanguage, updateHasUnsavedChanges } from '../../workspace/WorkspaceActions'; +import WorkspaceActions from '../../workspace/WorkspaceActions'; import { WorkspaceLocation } from '../../workspace/WorkspaceTypes'; import BackendSaga from '../BackendSaga'; import { @@ -598,7 +596,7 @@ describe('Test FETCH_ASSESSMENT action', () => { return expectSaga(BackendSaga) .withState({ session: mockTokens }) .provide([[call(getAssessment, mockId, mockTokens, undefined, undefined), mockAssessment]]) - .put(updateAssessment(mockAssessment)) + .put(SessionActions.updateAssessment(mockAssessment)) .hasFinalState({ session: mockTokens }) .dispatch({ type: SessionActions.fetchAssessment.type, payload: { assessmentId: mockId } }) .silentRun(); @@ -610,7 +608,7 @@ describe('Test FETCH_ASSESSMENT action', () => { .withState({ session: mockTokens }) .provide([[call(getAssessment, mockId, mockTokens, undefined, undefined), null]]) .call(getAssessment, mockId, mockTokens, undefined, undefined) - .not.put.actionType(UPDATE_ASSESSMENT) + .not.put.actionType(SessionActions.updateAssessment.type) .hasFinalState({ session: mockTokens }) .dispatch({ type: SessionActions.fetchAssessment.type, payload: { assessmentId: mockId } }) .silentRun(); @@ -650,8 +648,8 @@ describe('Test SUBMIT_ANSWER action', () => { ]) .not.call.fn(showWarningMessage) .call(showSuccessMessage, 'Saved!', 1000) - .put(updateAssessment(mockNewAssessment)) - .put(updateHasUnsavedChanges('assessment' as WorkspaceLocation, false)) + .put(SessionActions.updateAssessment(mockNewAssessment)) + .put(WorkspaceActions.updateHasUnsavedChanges('assessment' as WorkspaceLocation, false)) .dispatch({ type: SessionActions.submitAnswer.type, payload: mockAnsweredAssessmentQuestion }) .silentRun(); // To make sure no changes in state @@ -692,8 +690,8 @@ describe('Test SUBMIT_ANSWER action', () => { ]) .not.call.fn(showWarningMessage) .call(showSuccessMessage, 'Saved!', 1000) - .put(updateAssessment(mockNewAssessment)) - .put(updateHasUnsavedChanges('assessment' as WorkspaceLocation, false)) + .put(SessionActions.updateAssessment(mockNewAssessment)) + .put(WorkspaceActions.updateHasUnsavedChanges('assessment' as WorkspaceLocation, false)) .dispatch({ type: SessionActions.submitAnswer.type, payload: mockAnsweredAssessmentQuestion }) .silentRun(); // To make sure no changes in state @@ -725,8 +723,8 @@ describe('Test SUBMIT_ANSWER action', () => { ) .call(showWarningMessage, "Couldn't reach our servers. Are you online?") .not.call.fn(showSuccessMessage) - .not.put.actionType(UPDATE_ASSESSMENT) - .not.put.actionType(updateHasUnsavedChanges.type) + .not.put.actionType(SessionActions.updateAssessment.type) + .not.put.actionType(WorkspaceActions.updateHasUnsavedChanges.type) .hasFinalState({ session: { ...mockTokens, role: Role.Student } }) .dispatch({ type: SessionActions.submitAnswer.type, payload: mockAnsweredAssessmentQuestion }) .silentRun(); @@ -853,7 +851,7 @@ describe('Test CHANGE_SUBLANGUAGE action', () => { { ok: true } ] ]) - .dispatch({ type: changeSublanguage.type, payload: { sublang } }) + .dispatch({ type: WorkspaceActions.changeSublanguage.type, payload: { sublang } }) .silentRun(); }); }); @@ -1198,7 +1196,10 @@ describe('Test UPDATE_COURSE_RESEARCH_AGREEMENT', () => { .put(SessionActions.setCourseRegistration({ agreedToResearch })) .call.fn(showSuccessMessage) .provide([[call(putCourseResearchAgreement, mockTokens, agreedToResearch), okResp]]) - .dispatch({ type: UPDATE_COURSE_RESEARCH_AGREEMENT, payload: { agreedToResearch } }) + .dispatch({ + type: SessionActions.updateCourseResearchAgreement.type, + payload: { agreedToResearch } + }) .silentRun(); }); @@ -1209,7 +1210,10 @@ describe('Test UPDATE_COURSE_RESEARCH_AGREEMENT', () => { .not.put.actionType(SessionActions.setCourseRegistration.type) .not.call.fn(showSuccessMessage) .provide([[call(putCourseResearchAgreement, mockTokens, agreedToResearch), errorResp]]) - .dispatch({ type: UPDATE_COURSE_RESEARCH_AGREEMENT, payload: { agreedToResearch } }) + .dispatch({ + type: SessionActions.updateCourseResearchAgreement.type, + payload: { agreedToResearch } + }) .silentRun(); }); }); diff --git a/src/commons/sagas/__tests__/PersistenceSaga.ts b/src/commons/sagas/__tests__/PersistenceSaga.ts index 6a5d2ffe1f..c62124602e 100644 --- a/src/commons/sagas/__tests__/PersistenceSaga.ts +++ b/src/commons/sagas/__tests__/PersistenceSaga.ts @@ -1,10 +1,6 @@ import { Chapter, Variant } from 'js-slang/dist/types'; import { expectSaga } from 'redux-saga-test-plan'; -import { - changeExternalLibrary, - chapterSelect, - updateEditorValue -} from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; import { PLAYGROUND_UPDATE_PERSISTENCE_FILE } from '../../../features/playground/PlaygroundTypes'; import { ExternalLibraryName } from '../../application/types/ExternalTypes'; @@ -146,9 +142,9 @@ describe('PERSISTENCE_OPEN_PICKER', () => { } }) .not.put.like({ action: { type: PLAYGROUND_UPDATE_PERSISTENCE_FILE } }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); }); @@ -168,9 +164,9 @@ describe('PERSISTENCE_OPEN_PICKER', () => { } }) .not.put.like({ action: { type: PLAYGROUND_UPDATE_PERSISTENCE_FILE } }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); }); }); @@ -218,9 +214,9 @@ test('PERSISTENCE_SAVE_FILE saves', () => { .put.like({ action: actions.playgroundUpdatePersistenceFile({ id: FILE_ID, name: FILE_NAME }) }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); }); @@ -278,9 +274,9 @@ describe('PERSISTENCE_SAVE_FILE_AS', () => { .put.like({ action: actions.playgroundUpdatePersistenceFile({ id: FILE_ID, name: FILE_NAME }) }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); expect(updateFileCalled).toBe(true); }); @@ -336,9 +332,9 @@ describe('PERSISTENCE_SAVE_FILE_AS', () => { .put.like({ action: actions.playgroundUpdatePersistenceFile({ id: FILE_ID, name: FILE_NAME }) }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); expect(updateFileCalled).toBe(true); }); @@ -396,9 +392,9 @@ describe('PERSISTENCE_SAVE_FILE_AS', () => { .put.like({ action: actions.playgroundUpdatePersistenceFile({ id: FILE_ID, name: FILE_NAME }) }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); expect(createFileCalled).toBe(true); }); @@ -456,9 +452,9 @@ describe('PERSISTENCE_SAVE_FILE_AS', () => { .put.like({ action: actions.playgroundUpdatePersistenceFile({ id: FILE_ID, name: FILE_NAME }) }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun(); expect(createFileCalled).toBe(true); }); @@ -500,9 +496,9 @@ describe('PERSISTENCE_SAVE_FILE_AS', () => { } }) .not.put.like({ action: { type: PLAYGROUND_UPDATE_PERSISTENCE_FILE } }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun()); test('does not create a new file if cancelled', () => @@ -544,8 +540,8 @@ describe('PERSISTENCE_SAVE_FILE_AS', () => { } }) .not.put.like({ action: { type: PLAYGROUND_UPDATE_PERSISTENCE_FILE } }) - .not.put.like({ action: { type: updateEditorValue.type } }) - .not.put.like({ action: { type: chapterSelect.type } }) - .not.put.like({ action: { type: changeExternalLibrary.type } }) + .not.put.like({ action: { type: WorkspaceActions.updateEditorValue.type } }) + .not.put.like({ action: { type: WorkspaceActions.chapterSelect.type } }) + .not.put.like({ action: { type: WorkspaceActions.changeExternalLibrary.type } }) .silentRun()); }); diff --git a/src/commons/sagas/__tests__/WorkspaceSaga.ts b/src/commons/sagas/__tests__/WorkspaceSaga.ts index 7a0f12dbfe..bd6f4f1159 100644 --- a/src/commons/sagas/__tests__/WorkspaceSaga.ts +++ b/src/commons/sagas/__tests__/WorkspaceSaga.ts @@ -21,27 +21,7 @@ import { showSuccessMessage, showWarningMessage } from '../../utils/notifications/NotificationsHelper'; -import { - beginClearContext, - changeExternalLibrary, - chapterSelect, - clearReplInput, - clearReplOutput, - clearReplOutputLast, - endClearContext, - evalEditor, - evalRepl, - evalTestcase, - externalLibrarySelect, - moveCursor, - navigateToDeclaration, - runAllTestcases, - sendReplInputToOutput, - setEditorHighlightedLines, - setFolderMode, - toggleEditorAutorun, - toggleFolderMode -} from '../../workspace/WorkspaceActions'; +import WorkspaceActions from '../../workspace/WorkspaceActions'; import { WorkspaceLocation, WorkspaceState } from '../../workspace/WorkspaceTypes'; import workspaceSaga from '../WorkspaceSaga'; import { evalCodeSaga } from '../WorkspaceSaga/helpers/evalCode'; @@ -87,10 +67,10 @@ describe('TOGGLE_FOLDER_MODE', () => { return expectSaga(workspaceSaga) .withState(updatedDefaultState) - .put(setFolderMode(workspaceLocation, true)) + .put(WorkspaceActions.setFolderMode(workspaceLocation, true)) .call(showWarningMessage, 'Folder mode enabled', 750) .dispatch({ - type: toggleFolderMode.type, + type: WorkspaceActions.toggleFolderMode.type, payload: { workspaceLocation } }) .silentRun(); @@ -105,10 +85,10 @@ describe('TOGGLE_FOLDER_MODE', () => { return expectSaga(workspaceSaga) .withState(updatedDefaultState) - .put(setFolderMode(workspaceLocation, false)) + .put(WorkspaceActions.setFolderMode(workspaceLocation, false)) .call(showWarningMessage, 'Folder mode disabled', 750) .dispatch({ - type: toggleFolderMode.type, + type: WorkspaceActions.toggleFolderMode.type, payload: { workspaceLocation } }) .silentRun(); @@ -159,8 +139,8 @@ describe('EVAL_EDITOR', () => { expectSaga(workspaceSaga) .withState(newDefaultState) .put(InterpreterActions.beginInterruptExecution(workspaceLocation)) - .put(beginClearContext(workspaceLocation, library, false)) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.beginClearContext(workspaceLocation, library, false)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) // calls evalCode here with the prepend in elevated Context: silent run .call.like({ fn: runFilesInContext, @@ -201,11 +181,11 @@ describe('EVAL_EDITOR', () => { // should NOT attempt to execute the postpend block after above .not.call(runFilesInContext) .dispatch({ - type: evalEditor.type, + type: WorkspaceActions.evalEditor.type, payload: { workspaceLocation } }) .dispatch({ - type: endClearContext.type + type: WorkspaceActions.endClearContext.type }) .silentRun() ); @@ -219,7 +199,7 @@ describe('TOGGLE_EDITOR_AUTORUN', () => { .withState(defaultState) .call(showWarningMessage, 'Autorun Stopped', 750) .dispatch({ - type: toggleEditorAutorun.type, + type: WorkspaceActions.toggleEditorAutorun.type, payload: { workspaceLocation } }) .silentRun(); @@ -234,7 +214,7 @@ describe('TOGGLE_EDITOR_AUTORUN', () => { .withState(newDefaultState) .call(showWarningMessage, 'Autorun Started', 750) .dispatch({ - type: toggleEditorAutorun.type, + type: WorkspaceActions.toggleEditorAutorun.type, payload: { workspaceLocation } }) .silentRun(); @@ -252,8 +232,8 @@ describe('EVAL_REPL', () => { expectSaga(workspaceSaga) .withState(newState) .put(InterpreterActions.beginInterruptExecution(workspaceLocation)) - .put(clearReplInput(workspaceLocation)) - .put(sendReplInputToOutput(replValue, workspaceLocation)) + .put(WorkspaceActions.clearReplInput(workspaceLocation)) + .put(WorkspaceActions.sendReplInputToOutput(replValue, workspaceLocation)) // also calls evalCode here .call(runFilesInContext, { '/code.js': replValue }, '/code.js', context, { scheduler: 'preemptive', @@ -264,7 +244,7 @@ describe('EVAL_REPL', () => { envSteps: -1 }) .dispatch({ - type: evalRepl.type, + type: WorkspaceActions.evalRepl.type, payload: { workspaceLocation } }) .silentRun() @@ -300,7 +280,7 @@ describe('DEBUG_RESUME', () => { context, execTime, workspaceLocation, - evalEditor.type + WorkspaceActions.evalEditor.type ) .withState(state) .silentRun(); @@ -325,9 +305,9 @@ describe('DEBUG_RESUME', () => { } }) .put(InterpreterActions.beginInterruptExecution(workspaceLocation)) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - .put(setEditorHighlightedLines(workspaceLocation, 0, [])) + .put(WorkspaceActions.setEditorHighlightedLines(workspaceLocation, 0, [])) // also calls evalCode here .call.like({ fn: evalCodeSaga, @@ -359,9 +339,9 @@ describe('DEBUG_RESET', () => { return ( expectSaga(workspaceSaga) .withState(newDefaultState) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - .put(setEditorHighlightedLines(workspaceLocation, 0, [])) + .put(WorkspaceActions.setEditorHighlightedLines(workspaceLocation, 0, [])) .dispatch({ type: InterpreterActions.debuggerReset.type, payload: { workspaceLocation } @@ -430,8 +410,8 @@ describe('EVAL_TESTCASE', () => { .withState(newDefaultState) // Should interrupt execution and clear context but not clear REPL .not.put(InterpreterActions.beginInterruptExecution(workspaceLocation)) - .put(beginClearContext(workspaceLocation, library, false)) - .not.put.actionType(clearReplOutput.type) + .put(WorkspaceActions.beginClearContext(workspaceLocation, library, false)) + .not.put.actionType(WorkspaceActions.clearReplOutput.type) // Expect it to shard a new privileged context here and execute chunks in order // calls evalCode here with the prepend in elevated Context: silent run .call.like({ @@ -482,11 +462,11 @@ describe('EVAL_TESTCASE', () => { .put(InterpreterActions.evalInterpreterSuccess(42, workspaceLocation)) .put(InterpreterActions.evalTestcaseSuccess(42, workspaceLocation, testcaseId)) .dispatch({ - type: evalTestcase.type, + type: WorkspaceActions.evalTestcase.type, payload: { workspaceLocation, testcaseId } }) .dispatch({ - type: endClearContext.type + type: WorkspaceActions.endClearContext.type }) .silentRun() ); @@ -527,11 +507,11 @@ describe('CHAPTER_SELECT', () => { return expectSaga(workspaceSaga) .withState(newDefaultState) - .put(beginClearContext(workspaceLocation, library, false)) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.beginClearContext(workspaceLocation, library, false)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) .call(showSuccessMessage, `Switched to Source \xa7${newChapter}`, 1000) .dispatch({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter: newChapter, variant: Variant.DEFAULT, workspaceLocation } }) .silentRun(); @@ -544,11 +524,11 @@ describe('CHAPTER_SELECT', () => { return expectSaga(workspaceSaga) .withState(newDefaultState) - .not.put.actionType(beginClearContext.type) - .not.put.actionType(clearReplOutput.type) + .not.put.actionType(WorkspaceActions.beginClearContext.type) + .not.put.actionType(WorkspaceActions.clearReplOutput.type) .not.call.fn(showSuccessMessage) .dispatch({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter: newChapter, variant: newVariant, workspaceLocation } }) .silentRun(); @@ -571,11 +551,11 @@ describe('CHAPTER_SELECT', () => { .provide([[matchers.call.fn(showFullJSDisclaimer), true]]) .withState(newDefaultState) .call(showFullJSDisclaimer) - .put(beginClearContext(workspaceLocation, library, false)) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.beginClearContext(workspaceLocation, library, false)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) .call(showSuccessMessage, `Switched to full JavaScript`, 1000) .dispatch({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter: fullJSLanguage.chapter, variant: fullJSLanguage.variant, @@ -592,11 +572,11 @@ describe('CHAPTER_SELECT', () => { .provide([[matchers.call.fn(showFullJSDisclaimer), false]]) .withState(newDefaultState) .call(showFullJSDisclaimer) - .not.put.actionType(beginClearContext.type) - .not.put.actionType(clearReplOutput.type) + .not.put.actionType(WorkspaceActions.beginClearContext.type) + .not.put.actionType(WorkspaceActions.clearReplOutput.type) .not.call.fn(showSuccessMessage) .dispatch({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter: fullJSLanguage.chapter, variant: fullJSLanguage.variant, @@ -624,11 +604,11 @@ describe('CHAPTER_SELECT', () => { .provide([[matchers.call.fn(showFullTSDisclaimer), true]]) .withState(newDefaultState) .call(showFullTSDisclaimer) - .put(beginClearContext(workspaceLocation, library, false)) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.beginClearContext(workspaceLocation, library, false)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) .call(showSuccessMessage, `Switched to full TypeScript`, 1000) .dispatch({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter: fullTSLanguage.chapter, variant: fullTSLanguage.variant, @@ -645,11 +625,11 @@ describe('CHAPTER_SELECT', () => { .provide([[matchers.call.fn(showFullTSDisclaimer), false]]) .withState(newDefaultState) .call(showFullTSDisclaimer) - .not.put.actionType(beginClearContext.type) - .not.put.actionType(clearReplOutput.type) + .not.put.actionType(WorkspaceActions.beginClearContext.type) + .not.put.actionType(WorkspaceActions.clearReplOutput.type) .not.call.fn(showSuccessMessage) .dispatch({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter: fullTSLanguage.chapter, variant: fullTSLanguage.variant, @@ -703,12 +683,12 @@ describe('PLAYGROUND_EXTERNAL_SELECT', () => { return expectSaga(workspaceSaga) .withState(newDefaultState) - .put(changeExternalLibrary(newExternalLibraryName, workspaceLocation)) - .put(beginClearContext(workspaceLocation, library, true)) - .put(clearReplOutput(workspaceLocation)) + .put(WorkspaceActions.changeExternalLibrary(newExternalLibraryName, workspaceLocation)) + .put(WorkspaceActions.beginClearContext(workspaceLocation, library, true)) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) .call(showSuccessMessage, `Switched to ${newExternalLibraryName} library`, 1000) .dispatch({ - type: externalLibrarySelect.type, + type: WorkspaceActions.externalLibrarySelect.type, payload: { externalLibraryName: newExternalLibraryName, workspaceLocation @@ -728,12 +708,12 @@ describe('PLAYGROUND_EXTERNAL_SELECT', () => { return expectSaga(workspaceSaga) .withState(newDefaultState) - .not.put.actionType(changeExternalLibrary.type) - .not.put.actionType(beginClearContext.type) - .not.put.actionType(clearReplOutput.type) + .not.put.actionType(WorkspaceActions.changeExternalLibrary.type) + .not.put.actionType(WorkspaceActions.beginClearContext.type) + .not.put.actionType(WorkspaceActions.clearReplOutput.type) .not.call.fn(showSuccessMessage) .dispatch({ - type: externalLibrarySelect.type, + type: WorkspaceActions.externalLibrarySelect.type, payload: { externalLibraryName: newExternalLibraryName, workspaceLocation @@ -783,9 +763,9 @@ describe('BEGIN_CLEAR_CONTEXT', () => { }; return expectSaga(workspaceSaga) - .put.like({ action: endClearContext(library, workspaceLocation) }) + .put.like({ action: WorkspaceActions.endClearContext(library, workspaceLocation) }) .dispatch({ - type: beginClearContext.type, + type: WorkspaceActions.beginClearContext.type, payload: { library, workspaceLocation, shouldInitLibrary: true } }) .silentRun(); @@ -813,7 +793,7 @@ describe('evalCode', () => { [codeFilePath]: code }; execTime = 1000; - actionType = evalEditor.type; + actionType = WorkspaceActions.evalEditor.type; context = createContext(); // mockRuntimeContext(); value = 'test value'; options = { @@ -954,7 +934,7 @@ describe('evalCode', () => { context, execTime, workspaceLocation, - evalEditor.type + WorkspaceActions.evalEditor.type ) .withState(state) .silentRun(); @@ -1140,7 +1120,7 @@ describe('evalTestCode', () => { .provide([[call(runInContext, code, context, options), { status: 'finished', value }]]) .put(InterpreterActions.evalInterpreterSuccess(value, workspaceLocation)) .put(InterpreterActions.evalTestcaseSuccess(value, workspaceLocation, index)) - .not.put(clearReplOutputLast(workspaceLocation)) + .not.put(WorkspaceActions.clearReplOutputLast(workspaceLocation)) .silentRun(); }); @@ -1152,7 +1132,7 @@ describe('evalTestCode', () => { .provide([[call(runInContext, code, context, options), { status: 'finished', value }]]) .put(InterpreterActions.evalInterpreterSuccess(value, workspaceLocation)) .put(InterpreterActions.evalTestcaseSuccess(value, workspaceLocation, index)) - .put(clearReplOutputLast(workspaceLocation)) + .put(WorkspaceActions.clearReplOutputLast(workspaceLocation)) .silentRun(); }); @@ -1162,7 +1142,7 @@ describe('evalTestCode', () => { .provide([[call(runInContext, code, context, options), { status: 'error' }]]) .put(InterpreterActions.evalInterpreterError(context.errors, workspaceLocation)) .put(InterpreterActions.evalTestcaseFailure(context.errors, workspaceLocation, index)) - .not.put(clearReplOutputLast(workspaceLocation)) + .not.put(WorkspaceActions.clearReplOutputLast(workspaceLocation)) .silentRun(); }); @@ -1174,7 +1154,7 @@ describe('evalTestCode', () => { .provide([[call(runInContext, code, context, options), { status: 'error' }]]) .put(InterpreterActions.evalInterpreterError(context.errors, workspaceLocation)) .put(InterpreterActions.evalTestcaseFailure(context.errors, workspaceLocation, index)) - .put(clearReplOutputLast(workspaceLocation)) + .put(WorkspaceActions.clearReplOutputLast(workspaceLocation)) .silentRun(); }); }); @@ -1235,11 +1215,11 @@ describe('NAV_DECLARATION', () => { expectSaga(workspaceSaga) .withState(state) .dispatch({ - type: navigateToDeclaration.type, + type: WorkspaceActions.navigateToDeclaration.type, payload: { workspaceLocation, cursorPosition: loc } }) // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - .put(moveCursor(workspaceLocation, 0, resultLoc)) + .put(WorkspaceActions.moveCursor(workspaceLocation, 0, resultLoc)) .silentRun() ); }); @@ -1251,11 +1231,11 @@ describe('NAV_DECLARATION', () => { expectSaga(workspaceSaga) .withState(state) .dispatch({ - type: navigateToDeclaration.type, + type: WorkspaceActions.navigateToDeclaration.type, payload: { workspaceLocation, cursorPosition: pos } }) // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - .not.put(moveCursor(workspaceLocation, 0, resultPos)) + .not.put(WorkspaceActions.moveCursor(workspaceLocation, 0, resultPos)) .silentRun() ); }); @@ -1267,11 +1247,11 @@ describe('NAV_DECLARATION', () => { expectSaga(workspaceSaga) .withState(state) .dispatch({ - type: navigateToDeclaration.type, + type: WorkspaceActions.navigateToDeclaration.type, payload: { workspaceLocation, cursorPosition: pos } }) // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - .not.put(moveCursor(workspaceLocation, 0, resultPos)) + .not.put(WorkspaceActions.moveCursor(workspaceLocation, 0, resultPos)) .silentRun() ); }); @@ -1297,7 +1277,7 @@ describe('EVAL_EDITOR_AND_TESTCASES', () => { .not.call.fn(showSuccessMessage) .not.call.fn(runTestCase) .dispatch({ - type: runAllTestcases.type, + type: WorkspaceActions.runAllTestcases.type, payload: { workspaceLocation } }) .silentRun(); @@ -1311,7 +1291,7 @@ describe('EVAL_EDITOR_AND_TESTCASES', () => { return expectSaga(workspaceSaga) .withState(state) .dispatch({ - type: runAllTestcases.type, + type: WorkspaceActions.runAllTestcases.type, payload: { workspaceLocation } }) .call(showSuccessMessage, 'Running all testcases!', 2000) @@ -1337,7 +1317,7 @@ describe('EVAL_EDITOR_AND_TESTCASES', () => { return expectSaga(workspaceSaga) .withState(state) .dispatch({ - type: runAllTestcases.type, + type: WorkspaceActions.runAllTestcases.type, payload: { workspaceLocation } }) .call(showSuccessMessage, 'Running all testcases!', 2000) diff --git a/src/commons/sideContent/SideContentActions.ts b/src/commons/sideContent/SideContentActions.ts index 76f4a805d4..7c3dd4077f 100644 --- a/src/commons/sideContent/SideContentActions.ts +++ b/src/commons/sideContent/SideContentActions.ts @@ -1,60 +1,46 @@ -import { createAction } from '@reduxjs/toolkit'; - +import { createActions } from '../redux/utils'; import { DebuggerContext, type WorkspaceLocation } from '../workspace/WorkspaceTypes'; -import { - BEGIN_ALERT_SIDE_CONTENT, - CHANGE_SIDE_CONTENT_HEIGHT, - END_ALERT_SIDE_CONTENT, - REMOVE_SIDE_CONTENT_ALERT, - RESET_SIDE_CONTENT, - SideContentLocation, - SideContentType, - SPAWN_SIDE_CONTENT, - VISIT_SIDE_CONTENT -} from './SideContentTypes'; +import { SideContentLocation, SideContentType } from './SideContentTypes'; -export const beginAlertSideContent = createAction( - BEGIN_ALERT_SIDE_CONTENT, - (id: SideContentType, workspaceLocation: SideContentLocation) => ({ - payload: { id, workspaceLocation } - }) -); -export const endAlertSideContent = createAction( - END_ALERT_SIDE_CONTENT, - (id: SideContentType, workspaceLocation: SideContentLocation) => ({ - payload: { id, workspaceLocation } - }) -); -export const visitSideContent = createAction( - VISIT_SIDE_CONTENT, - ( +const SideContentActions = createActions('sideContent', { + beginAlertSideContent: (id: SideContentType, workspaceLocation: SideContentLocation) => ({ + id, + workspaceLocation + }), + endAlertSideContent: (id: SideContentType, workspaceLocation: SideContentLocation) => ({ + id, + workspaceLocation + }), + visitSideContent: ( newId: SideContentType, prevId: SideContentType | undefined, workspaceLocation: SideContentLocation - ) => ({ payload: { newId, prevId, workspaceLocation } }) -); - -export const removeSideContentAlert = createAction( - REMOVE_SIDE_CONTENT_ALERT, - (id: SideContentType, workspaceLocation: SideContentLocation) => ({ - payload: { id, workspaceLocation } + ) => ({ newId, prevId, workspaceLocation }), + removeSideContentAlert: (id: SideContentType, workspaceLocation: SideContentLocation) => ({ + id, + workspaceLocation + }), + spawnSideContent: (workspaceLocation: SideContentLocation, debuggerContext: DebuggerContext) => ({ + workspaceLocation, + debuggerContext + }), + resetSideContent: (workspaceLocation: SideContentLocation) => ({ workspaceLocation }), + changeSideContentHeight: (height: number, workspaceLocation: WorkspaceLocation) => ({ + height, + workspaceLocation }) -); +}); -export const spawnSideContent = createAction( - SPAWN_SIDE_CONTENT, - (workspaceLocation: SideContentLocation, debuggerContext: DebuggerContext) => ({ - payload: { workspaceLocation, debuggerContext } - }) -); +// For compatibility with existing code (reducer) +export const { + beginAlertSideContent, + endAlertSideContent, + visitSideContent, + removeSideContentAlert, + spawnSideContent, + resetSideContent, + changeSideContentHeight +} = SideContentActions; -export const resetSideContent = createAction( - RESET_SIDE_CONTENT, - (workspaceLocation: SideContentLocation) => ({ payload: { workspaceLocation } }) -); -export const changeSideContentHeight = createAction( - CHANGE_SIDE_CONTENT_HEIGHT, - (height: number, workspaceLocation: WorkspaceLocation) => ({ - payload: { height, workspaceLocation } - }) -); +// For compatibility with existing code (actions helper) +export default SideContentActions; diff --git a/src/commons/sideContent/SideContentReducer.ts b/src/commons/sideContent/SideContentReducer.ts index 6781a1ba32..72da18de5c 100644 --- a/src/commons/sideContent/SideContentReducer.ts +++ b/src/commons/sideContent/SideContentReducer.ts @@ -1,15 +1,15 @@ import { defaultSideContent, defaultSideContentManager } from '../application/ApplicationTypes'; import { SourceActionType } from '../utils/ActionsHelper'; -import { getDynamicTabs, getTabId } from './SideContentHelper'; -import { getLocation } from './SideContentHelper'; -import { CHANGE_SIDE_CONTENT_HEIGHT, SPAWN_SIDE_CONTENT } from './SideContentTypes'; import { - END_ALERT_SIDE_CONTENT, - REMOVE_SIDE_CONTENT_ALERT, - RESET_SIDE_CONTENT, - SideContentManagerState, - VISIT_SIDE_CONTENT -} from './SideContentTypes'; + changeSideContentHeight, + endAlertSideContent, + removeSideContentAlert, + resetSideContent, + spawnSideContent, + visitSideContent +} from './SideContentActions'; +import { getDynamicTabs, getLocation, getTabId } from './SideContentHelper'; +import { SideContentManagerState } from './SideContentTypes'; export function SideContentReducer( state: SideContentManagerState = defaultSideContentManager, @@ -24,7 +24,7 @@ export function SideContentReducer( workspaceLocation === 'stories' ? state.stories[storyEnv] : state[workspaceLocation]; switch (action.type) { - case CHANGE_SIDE_CONTENT_HEIGHT: + case changeSideContentHeight.type: return workspaceLocation === 'stories' ? { ...state, @@ -43,7 +43,7 @@ export function SideContentReducer( height: action.payload.height } }; - case END_ALERT_SIDE_CONTENT: { + case endAlertSideContent.type: { if (action.payload.id !== sideContentState.selectedTab) { return workspaceLocation === 'stories' ? { @@ -66,7 +66,7 @@ export function SideContentReducer( } return state; } - case REMOVE_SIDE_CONTENT_ALERT: + case removeSideContentAlert.type: return workspaceLocation === 'stories' ? { ...state, @@ -85,7 +85,7 @@ export function SideContentReducer( alerts: state[workspaceLocation].alerts.filter(id => id !== action.payload.id) } }; - case RESET_SIDE_CONTENT: + case resetSideContent.type: return workspaceLocation === 'stories' ? { ...state, @@ -98,7 +98,7 @@ export function SideContentReducer( ...state, [workspaceLocation]: defaultSideContent }; - case SPAWN_SIDE_CONTENT: { + case spawnSideContent.type: { const dynamicTabs = getDynamicTabs(action.payload.debuggerContext); const alerts = dynamicTabs.map(getTabId).filter(id => id !== sideContentState.selectedTab); return workspaceLocation === 'stories' @@ -122,7 +122,7 @@ export function SideContentReducer( } }; } - case VISIT_SIDE_CONTENT: + case visitSideContent.type: return workspaceLocation === 'stories' ? { ...state, diff --git a/src/commons/sideContent/SideContentTypes.ts b/src/commons/sideContent/SideContentTypes.ts index dd87e949d7..f4598968ca 100644 --- a/src/commons/sideContent/SideContentTypes.ts +++ b/src/commons/sideContent/SideContentTypes.ts @@ -2,13 +2,6 @@ import { IconName } from '@blueprintjs/core'; import { DebuggerContext, WorkspaceLocation } from '../workspace/WorkspaceTypes'; -export const BEGIN_ALERT_SIDE_CONTENT = 'BEGIN_ALERT_SIDE_CONTENT'; -export const END_ALERT_SIDE_CONTENT = 'END_ALERT_SIDE_CONTENT'; -export const VISIT_SIDE_CONTENT = 'VISIT_SIDE_CONTENT'; -export const RESET_SIDE_CONTENT = 'RESET_SIDE_CONTENT'; -export const SPAWN_SIDE_CONTENT = 'SPAWN_SIDE_CONTENT'; -export const REMOVE_SIDE_CONTENT_ALERT = 'REMOVE_SIDE_CONTENT_ALERT'; - export enum SideContentType { autograder = 'autograder', briefing = 'briefing', @@ -122,4 +115,3 @@ export type SideContentDispatchProps = { */ alertSideContent: (newId: SideContentType) => void; }; -export const CHANGE_SIDE_CONTENT_HEIGHT = 'CHANGE_SIDE_CONTENT_HEIGHT'; diff --git a/src/commons/utils/ActionsHelper.ts b/src/commons/utils/ActionsHelper.ts index c2f49b3e11..059b0fe029 100644 --- a/src/commons/utils/ActionsHelper.ts +++ b/src/commons/utils/ActionsHelper.ts @@ -3,19 +3,19 @@ import InterpreterActions from '../../commons/application/actions/InterpreterAct import SessionActions from '../../commons/application/actions/SessionActions'; import * as CollabEditingActions from '../../commons/collabEditing/CollabEditingActions'; import * as FileSystemActions from '../../commons/fileSystem/FileSystemActions'; -import * as SideContentActions from '../../commons/sideContent/SideContentActions'; +import SideContentActions from '../../commons/sideContent/SideContentActions'; import WorkspaceActions from '../../commons/workspace/WorkspaceActions'; import AcademyActions from '../../features/academy/AcademyActions'; -import * as AchievementActions from '../../features/achievement/AchievementActions'; +import AchievementActions from '../../features/achievement/AchievementActions'; import * as DashboardActions from '../../features/dashboard/DashboardActions'; import GitHubActions from '../../features/github/GitHubActions'; -import * as GroundControlActions from '../../features/groundControl/GroundControlActions'; +import GroundControlActions from '../../features/groundControl/GroundControlActions'; import * as PersistenceActions from '../../features/persistence/PersistenceActions'; import * as PlaygroundActions from '../../features/playground/PlaygroundActions'; -import * as RemoteExecutionActions from '../../features/remoteExecution/RemoteExecutionActions'; +import RemoteExecutionActions from '../../features/remoteExecution/RemoteExecutionActions'; import * as SourcecastActions from '../../features/sourceRecorder/sourcecast/SourcecastActions'; import * as SourceRecorderActions from '../../features/sourceRecorder/SourceRecorderActions'; -import * as SourcereelActions from '../../features/sourceRecorder/sourcereel/SourcereelActions'; +import SourcereelActions from '../../features/sourceRecorder/sourcereel/SourcereelActions'; import StoriesActions from '../../features/stories/StoriesActions'; import { ActionType } from './TypeHelper'; diff --git a/src/commons/workspace/__tests__/WorkspaceActions.ts b/src/commons/workspace/__tests__/WorkspaceActions.ts index d1be5be1e6..65dade0f70 100644 --- a/src/commons/workspace/__tests__/WorkspaceActions.ts +++ b/src/commons/workspace/__tests__/WorkspaceActions.ts @@ -1,6 +1,5 @@ import { Chapter, Variant } from 'js-slang/dist/types'; import { changeSideContentHeight } from 'src/commons/sideContent/SideContentActions'; -import { CHANGE_SIDE_CONTENT_HEIGHT } from 'src/commons/sideContent/SideContentTypes'; import { createDefaultWorkspace, @@ -10,49 +9,7 @@ import { import { ExternalLibraryName } from '../../application/types/ExternalTypes'; import { Library } from '../../assessment/AssessmentTypes'; import { HighlightedLines } from '../../editor/EditorTypes'; -import { - addEditorTab, - beginClearContext, - browseReplHistoryDown, - browseReplHistoryUp, - changeExternalLibrary, - changeSublanguage, - chapterSelect, - clearReplInput, - clearReplOutput, - clearReplOutputLast, - endClearContext, - evalEditor, - evalRepl, - evalTestcase, - externalLibrarySelect, - moveCursor, - navigateToDeclaration, - removeEditorTab, - removeEditorTabForFile, - removeEditorTabsForDirectory, - renameEditorTabForFile, - renameEditorTabsForDirectory, - resetTestcase, - resetWorkspace, - sendReplInputToOutput, - setEditorBreakpoint, - setEditorHighlightedLines, - setFolderMode, - shiftEditorTab, - toggleEditorAutorun, - toggleFolderMode, - toggleUsingSubst, - updateActiveEditorTab, - updateActiveEditorTabIndex, - updateCurrentAssessmentId, - updateCurrentSubmissionId, - updateEditorValue, - updateHasUnsavedChanges, - updateReplValue, - updateSublanguage, - updateSubmissionsTableFilters -} from '../WorkspaceActions'; +import WorkspaceActions from '../WorkspaceActions'; import { EditorTabState, WorkspaceLocation } from '../WorkspaceTypes'; const assessmentWorkspace: WorkspaceLocation = 'assessment'; @@ -60,26 +17,26 @@ const gradingWorkspace: WorkspaceLocation = 'grading'; const playgroundWorkspace: WorkspaceLocation = 'playground'; test('browseReplHistoryDown generates correct action object', () => { - const action = browseReplHistoryDown(assessmentWorkspace); + const action = WorkspaceActions.browseReplHistoryDown(assessmentWorkspace); expect(action).toEqual({ - type: browseReplHistoryDown.type, + type: WorkspaceActions.browseReplHistoryDown.type, payload: { workspaceLocation: assessmentWorkspace } }); }); test('browseReplHistoryUp generates correct action object', () => { - const action = browseReplHistoryUp(gradingWorkspace); + const action = WorkspaceActions.browseReplHistoryUp(gradingWorkspace); expect(action).toEqual({ - type: browseReplHistoryUp.type, + type: WorkspaceActions.browseReplHistoryUp.type, payload: { workspaceLocation: gradingWorkspace } }); }); test('changeExternalLibrary generates correct action object', () => { const newExternal = 'new-external-test' as ExternalLibraryName; - const action = changeExternalLibrary(newExternal, playgroundWorkspace); + const action = WorkspaceActions.changeExternalLibrary(newExternal, playgroundWorkspace); expect(action).toEqual({ - type: changeExternalLibrary.type, + type: WorkspaceActions.changeExternalLibrary.type, payload: { newExternal, workspaceLocation: playgroundWorkspace @@ -91,7 +48,7 @@ test('changeSideContentHeight generates correct action object', () => { const height = 100; const action = changeSideContentHeight(height, gradingWorkspace); expect(action).toEqual({ - type: CHANGE_SIDE_CONTENT_HEIGHT, + type: changeSideContentHeight.type, payload: { height, workspaceLocation: gradingWorkspace @@ -102,9 +59,9 @@ test('changeSideContentHeight generates correct action object', () => { test('chapterSelect generates correct action object', () => { const chapter = Chapter.SOURCE_3; const variant = Variant.DEFAULT; - const action = chapterSelect(chapter, variant, playgroundWorkspace); + const action = WorkspaceActions.chapterSelect(chapter, variant, playgroundWorkspace); expect(action).toEqual({ - type: chapterSelect.type, + type: WorkspaceActions.chapterSelect.type, payload: { chapter, variant, @@ -115,9 +72,9 @@ test('chapterSelect generates correct action object', () => { test('externalLibrarySelect generates correct action object', () => { const externalLibraryName = ExternalLibraryName.SOUNDS; - const action = externalLibrarySelect(externalLibraryName, assessmentWorkspace); + const action = WorkspaceActions.externalLibrarySelect(externalLibraryName, assessmentWorkspace); expect(action).toEqual({ - type: externalLibrarySelect.type, + type: WorkspaceActions.externalLibrarySelect.type, payload: { externalLibraryName, workspaceLocation: assessmentWorkspace, @@ -127,9 +84,9 @@ test('externalLibrarySelect generates correct action object', () => { }); test('toggleEditorAutorun generates correct action object', () => { - const action = toggleEditorAutorun(gradingWorkspace); + const action = WorkspaceActions.toggleEditorAutorun(gradingWorkspace); expect(action).toEqual({ - type: toggleEditorAutorun.type, + type: WorkspaceActions.toggleEditorAutorun.type, payload: { workspaceLocation: gradingWorkspace } @@ -146,9 +103,9 @@ test('beginClearContext generates correct action object', () => { globals: [] }; - const action = beginClearContext(playgroundWorkspace, library, true); + const action = WorkspaceActions.beginClearContext(playgroundWorkspace, library, true); expect(action).toEqual({ - type: beginClearContext.type, + type: WorkspaceActions.beginClearContext.type, payload: { library, workspaceLocation: playgroundWorkspace, @@ -158,9 +115,9 @@ test('beginClearContext generates correct action object', () => { }); test('clearReplInput generates correct action object', () => { - const action = clearReplInput(assessmentWorkspace); + const action = WorkspaceActions.clearReplInput(assessmentWorkspace); expect(action).toEqual({ - type: clearReplInput.type, + type: WorkspaceActions.clearReplInput.type, payload: { workspaceLocation: assessmentWorkspace } @@ -168,9 +125,9 @@ test('clearReplInput generates correct action object', () => { }); test('clearReplOutputLast generates correct action object', () => { - const action = clearReplOutputLast(assessmentWorkspace); + const action = WorkspaceActions.clearReplOutputLast(assessmentWorkspace); expect(action).toEqual({ - type: clearReplOutputLast.type, + type: WorkspaceActions.clearReplOutputLast.type, payload: { workspaceLocation: assessmentWorkspace } @@ -178,9 +135,9 @@ test('clearReplOutputLast generates correct action object', () => { }); test('clearReplOutput generates correct action object', () => { - const action = clearReplOutput(gradingWorkspace); + const action = WorkspaceActions.clearReplOutput(gradingWorkspace); expect(action).toEqual({ - type: clearReplOutput.type, + type: WorkspaceActions.clearReplOutput.type, payload: { workspaceLocation: gradingWorkspace } @@ -197,9 +154,9 @@ test('endClearContext generates correct action object', () => { globals: [] }; - const action = endClearContext(library, playgroundWorkspace); + const action = WorkspaceActions.endClearContext(library, playgroundWorkspace); expect(action).toEqual({ - type: endClearContext.type, + type: WorkspaceActions.endClearContext.type, payload: { library, workspaceLocation: playgroundWorkspace @@ -208,9 +165,9 @@ test('endClearContext generates correct action object', () => { }); test('evalEditor generates correct action object', () => { - const action = evalEditor(assessmentWorkspace); + const action = WorkspaceActions.evalEditor(assessmentWorkspace); expect(action).toEqual({ - type: evalEditor.type, + type: WorkspaceActions.evalEditor.type, payload: { workspaceLocation: assessmentWorkspace } @@ -218,9 +175,9 @@ test('evalEditor generates correct action object', () => { }); test('evalRepl generates correct action object', () => { - const action = evalRepl(gradingWorkspace); + const action = WorkspaceActions.evalRepl(gradingWorkspace); expect(action).toEqual({ - type: evalRepl.type, + type: WorkspaceActions.evalRepl.type, payload: { workspaceLocation: gradingWorkspace } @@ -229,9 +186,9 @@ test('evalRepl generates correct action object', () => { test('evalTestcase generates correct action object', () => { const testcaseId = 3; - const action = evalTestcase(playgroundWorkspace, testcaseId); + const action = WorkspaceActions.evalTestcase(playgroundWorkspace, testcaseId); expect(action).toEqual({ - type: evalTestcase.type, + type: WorkspaceActions.evalTestcase.type, payload: { testcaseId, workspaceLocation: playgroundWorkspace @@ -240,9 +197,9 @@ test('evalTestcase generates correct action object', () => { }); test('toggleFolderMode generates correct action object', () => { - const action = toggleFolderMode(gradingWorkspace); + const action = WorkspaceActions.toggleFolderMode(gradingWorkspace); expect(action).toEqual({ - type: toggleFolderMode.type, + type: WorkspaceActions.toggleFolderMode.type, payload: { workspaceLocation: gradingWorkspace } @@ -251,9 +208,9 @@ test('toggleFolderMode generates correct action object', () => { test('setFolderMode generates correct action object', () => { const isFolderModeEnabled = true; - const action = setFolderMode(gradingWorkspace, isFolderModeEnabled); + const action = WorkspaceActions.setFolderMode(gradingWorkspace, isFolderModeEnabled); expect(action).toEqual({ - type: setFolderMode.type, + type: WorkspaceActions.setFolderMode.type, payload: { workspaceLocation: gradingWorkspace, isFolderModeEnabled @@ -263,9 +220,12 @@ test('setFolderMode generates correct action object', () => { test('updateActiveEditorTabIndex generates correct action object', () => { const activeEditorTabIndex = 3; - const action = updateActiveEditorTabIndex(playgroundWorkspace, activeEditorTabIndex); + const action = WorkspaceActions.updateActiveEditorTabIndex( + playgroundWorkspace, + activeEditorTabIndex + ); expect(action).toEqual({ - type: updateActiveEditorTabIndex.type, + type: WorkspaceActions.updateActiveEditorTabIndex.type, payload: { workspaceLocation: playgroundWorkspace, activeEditorTabIndex @@ -275,9 +235,9 @@ test('updateActiveEditorTabIndex generates correct action object', () => { test('updateActiveEditorTab generates correct action object', () => { const newEditorTab: Partial = { value: 'Hello World' }; - const action = updateActiveEditorTab(assessmentWorkspace, newEditorTab); + const action = WorkspaceActions.updateActiveEditorTab(assessmentWorkspace, newEditorTab); expect(action).toEqual({ - type: updateActiveEditorTab.type, + type: WorkspaceActions.updateActiveEditorTab.type, payload: { workspaceLocation: assessmentWorkspace, activeEditorTabOptions: newEditorTab @@ -288,9 +248,13 @@ test('updateActiveEditorTab generates correct action object', () => { test('updateEditorValue generates correct action object', () => { const editorTabIndex = 3; const newEditorValue = 'new_editor_value'; - const action = updateEditorValue(assessmentWorkspace, editorTabIndex, newEditorValue); + const action = WorkspaceActions.updateEditorValue( + assessmentWorkspace, + editorTabIndex, + newEditorValue + ); expect(action).toEqual({ - type: updateEditorValue.type, + type: WorkspaceActions.updateEditorValue.type, payload: { workspaceLocation: assessmentWorkspace, editorTabIndex, @@ -302,9 +266,13 @@ test('updateEditorValue generates correct action object', () => { test('setEditorBreakpoint generates correct action object', () => { const editorTabIndex = 3; const newBreakpoints = ['ace_breakpoint', 'ace_breakpoint']; - const action = setEditorBreakpoint(gradingWorkspace, editorTabIndex, newBreakpoints); + const action = WorkspaceActions.setEditorBreakpoint( + gradingWorkspace, + editorTabIndex, + newBreakpoints + ); expect(action).toEqual({ - type: setEditorBreakpoint.type, + type: WorkspaceActions.setEditorBreakpoint.type, payload: { workspaceLocation: gradingWorkspace, editorTabIndex, @@ -319,13 +287,13 @@ test('setEditorHighlightedLines generates correct action object', () => { [1, 2], [5, 6] ]; - const action = setEditorHighlightedLines( + const action = WorkspaceActions.setEditorHighlightedLines( playgroundWorkspace, editorTabIndex, newHighlightedLines ); expect(action).toEqual({ - type: setEditorHighlightedLines.type, + type: WorkspaceActions.setEditorHighlightedLines.type, payload: { workspaceLocation: playgroundWorkspace, editorTabIndex, @@ -339,9 +307,13 @@ test('setEditorHighlightedLines generates correct action object', () => { test('moveCursor generates correct action object', () => { const editorTabIndex = 3; const newCursorPosition = { row: 0, column: 0 }; - const action = moveCursor(playgroundWorkspace, editorTabIndex, newCursorPosition); + const action = WorkspaceActions.moveCursor( + playgroundWorkspace, + editorTabIndex, + newCursorPosition + ); expect(action).toEqual({ - type: moveCursor.type, + type: WorkspaceActions.moveCursor.type, payload: { workspaceLocation: playgroundWorkspace, editorTabIndex, @@ -353,9 +325,9 @@ test('moveCursor generates correct action object', () => { test('addEditorTab generates correct action object', () => { const filePath = '/playground/program.js'; const editorValue = 'Hello World!'; - const action = addEditorTab(playgroundWorkspace, filePath, editorValue); + const action = WorkspaceActions.addEditorTab(playgroundWorkspace, filePath, editorValue); expect(action).toEqual({ - type: addEditorTab.type, + type: WorkspaceActions.addEditorTab.type, payload: { workspaceLocation: playgroundWorkspace, filePath, @@ -367,9 +339,13 @@ test('addEditorTab generates correct action object', () => { test('shiftEditorTab generates correct action object', () => { const previousEditorTabIndex = 3; const newEditorTabIndex = 1; - const action = shiftEditorTab(playgroundWorkspace, previousEditorTabIndex, newEditorTabIndex); + const action = WorkspaceActions.shiftEditorTab( + playgroundWorkspace, + previousEditorTabIndex, + newEditorTabIndex + ); expect(action).toEqual({ - type: shiftEditorTab.type, + type: WorkspaceActions.shiftEditorTab.type, payload: { workspaceLocation: playgroundWorkspace, previousEditorTabIndex, @@ -380,9 +356,9 @@ test('shiftEditorTab generates correct action object', () => { test('removeEditorTab generates correct action object', () => { const editorTabIndex = 3; - const action = removeEditorTab(playgroundWorkspace, editorTabIndex); + const action = WorkspaceActions.removeEditorTab(playgroundWorkspace, editorTabIndex); expect(action).toEqual({ - type: removeEditorTab.type, + type: WorkspaceActions.removeEditorTab.type, payload: { workspaceLocation: playgroundWorkspace, editorTabIndex @@ -392,9 +368,9 @@ test('removeEditorTab generates correct action object', () => { test('removeEditorTabForFile generates correct action object', () => { const removedFilePath = '/dir1/a.js'; - const action = removeEditorTabForFile(playgroundWorkspace, removedFilePath); + const action = WorkspaceActions.removeEditorTabForFile(playgroundWorkspace, removedFilePath); expect(action).toEqual({ - type: removeEditorTabForFile.type, + type: WorkspaceActions.removeEditorTabForFile.type, payload: { workspaceLocation: playgroundWorkspace, removedFilePath @@ -404,9 +380,12 @@ test('removeEditorTabForFile generates correct action object', () => { test('removeEditorTabsForDirectory generates correct action object', () => { const removedDirectoryPath = '/dir1'; - const action = removeEditorTabsForDirectory(playgroundWorkspace, removedDirectoryPath); + const action = WorkspaceActions.removeEditorTabsForDirectory( + playgroundWorkspace, + removedDirectoryPath + ); expect(action).toEqual({ - type: removeEditorTabsForDirectory.type, + type: WorkspaceActions.removeEditorTabsForDirectory.type, payload: { workspaceLocation: playgroundWorkspace, removedDirectoryPath @@ -417,9 +396,13 @@ test('removeEditorTabsForDirectory generates correct action object', () => { test('renameEditorTabForFile generates correct action object', () => { const oldFilePath = '/dir1/a.js'; const newFilePath = '/dir1/b.js'; - const action = renameEditorTabForFile(playgroundWorkspace, oldFilePath, newFilePath); + const action = WorkspaceActions.renameEditorTabForFile( + playgroundWorkspace, + oldFilePath, + newFilePath + ); expect(action).toEqual({ - type: renameEditorTabForFile.type, + type: WorkspaceActions.renameEditorTabForFile.type, payload: { workspaceLocation: playgroundWorkspace, oldFilePath, @@ -431,13 +414,13 @@ test('renameEditorTabForFile generates correct action object', () => { test('renameEditorTabsForDirectory generates correct action object', () => { const oldDirectoryPath = '/dir1'; const newDirectoryPath = '/dir2'; - const action = renameEditorTabsForDirectory( + const action = WorkspaceActions.renameEditorTabsForDirectory( playgroundWorkspace, oldDirectoryPath, newDirectoryPath ); expect(action).toEqual({ - type: renameEditorTabsForDirectory.type, + type: WorkspaceActions.renameEditorTabsForDirectory.type, payload: { workspaceLocation: playgroundWorkspace, oldDirectoryPath, @@ -448,9 +431,9 @@ test('renameEditorTabsForDirectory generates correct action object', () => { test('updateReplValue generates correct action object', () => { const newReplValue = 'new_repl_value'; - const action = updateReplValue(newReplValue, assessmentWorkspace); + const action = WorkspaceActions.updateReplValue(newReplValue, assessmentWorkspace); expect(action).toEqual({ - type: updateReplValue.type, + type: WorkspaceActions.updateReplValue.type, payload: { newReplValue, workspaceLocation: assessmentWorkspace @@ -460,9 +443,9 @@ test('updateReplValue generates correct action object', () => { test('sendReplInputToOutput generates correct action object', () => { const newOutput = 'new_output'; - const action = sendReplInputToOutput(newOutput, gradingWorkspace); + const action = WorkspaceActions.sendReplInputToOutput(newOutput, gradingWorkspace); expect(action).toEqual({ - type: sendReplInputToOutput.type, + type: WorkspaceActions.sendReplInputToOutput.type, payload: { type: 'code', value: newOutput, @@ -473,9 +456,9 @@ test('sendReplInputToOutput generates correct action object', () => { test('resetTestcase generates correct action object', () => { const index = 420; - const action = resetTestcase(assessmentWorkspace, index); + const action = WorkspaceActions.resetTestcase(assessmentWorkspace, index); expect(action).toEqual({ - type: resetTestcase.type, + type: WorkspaceActions.resetTestcase.type, payload: { workspaceLocation: assessmentWorkspace, index @@ -484,9 +467,9 @@ test('resetTestcase generates correct action object', () => { }); test('resetWorkspace generates correct default action object', () => { - const action = resetWorkspace(playgroundWorkspace); + const action = WorkspaceActions.resetWorkspace(playgroundWorkspace); expect(action).toEqual({ - type: resetWorkspace.type, + type: WorkspaceActions.resetWorkspace.type, payload: { workspaceLocation: playgroundWorkspace } @@ -495,9 +478,9 @@ test('resetWorkspace generates correct default action object', () => { test('resetWorkspace generates correct action object with provided workspace', () => { const workspaceOptions = createDefaultWorkspace(assessmentWorkspace); - const action = resetWorkspace(assessmentWorkspace, workspaceOptions); + const action = WorkspaceActions.resetWorkspace(assessmentWorkspace, workspaceOptions); expect(action).toEqual({ - type: resetWorkspace.type, + type: WorkspaceActions.resetWorkspace.type, payload: { workspaceLocation: assessmentWorkspace, workspaceOptions @@ -516,9 +499,9 @@ test('updateSubmissionsTableFilters generates correct action object', () => { value: 'Missions' } ]; - const action = updateSubmissionsTableFilters({ columnFilters }); + const action = WorkspaceActions.updateSubmissionsTableFilters({ columnFilters }); expect(action).toEqual({ - type: updateSubmissionsTableFilters.type, + type: WorkspaceActions.updateSubmissionsTableFilters.type, payload: { filters: { columnFilters @@ -530,9 +513,9 @@ test('updateSubmissionsTableFilters generates correct action object', () => { test('updateCurrentAssessmentId generates correct action object', () => { const assessmentId = 2; const questionId = 4; - const action = updateCurrentAssessmentId(assessmentId, questionId); + const action = WorkspaceActions.updateCurrentAssessmentId(assessmentId, questionId); expect(action).toEqual({ - type: updateCurrentAssessmentId.type, + type: WorkspaceActions.updateCurrentAssessmentId.type, payload: { assessmentId, questionId @@ -543,9 +526,9 @@ test('updateCurrentAssessmentId generates correct action object', () => { test('updateCurrentSubmissionId generates correct action object', () => { const submissionId = 3; const questionId = 6; - const action = updateCurrentSubmissionId(submissionId, questionId); + const action = WorkspaceActions.updateCurrentSubmissionId(submissionId, questionId); expect(action).toEqual({ - type: updateCurrentSubmissionId.type, + type: WorkspaceActions.updateCurrentSubmissionId.type, payload: { submissionId, questionId @@ -555,9 +538,9 @@ test('updateCurrentSubmissionId generates correct action object', () => { test('updateHasUnsavedChanges generates correct action object', () => { const hasUnsavedChanges = true; - const action = updateHasUnsavedChanges(assessmentWorkspace, hasUnsavedChanges); + const action = WorkspaceActions.updateHasUnsavedChanges(assessmentWorkspace, hasUnsavedChanges); expect(action).toEqual({ - type: updateHasUnsavedChanges.type, + type: WorkspaceActions.updateHasUnsavedChanges.type, payload: { workspaceLocation: assessmentWorkspace, hasUnsavedChanges @@ -567,9 +550,9 @@ test('updateHasUnsavedChanges generates correct action object', () => { test('navigateToDeclaration generates correct action object', () => { const cursorPosition = { row: 0, column: 0 }; - const action = navigateToDeclaration(playgroundWorkspace, cursorPosition); + const action = WorkspaceActions.navigateToDeclaration(playgroundWorkspace, cursorPosition); expect(action).toEqual({ - type: navigateToDeclaration.type, + type: WorkspaceActions.navigateToDeclaration.type, payload: { workspaceLocation: playgroundWorkspace, cursorPosition @@ -585,9 +568,9 @@ test('changeSublanguage generates correct action object', () => { mainLanguage: SupportedLanguage.JAVASCRIPT, supports: {} }; - const action = changeSublanguage(sublang); + const action = WorkspaceActions.changeSublanguage(sublang); expect(action).toEqual({ - type: changeSublanguage.type, + type: WorkspaceActions.changeSublanguage.type, payload: { sublang } @@ -602,9 +585,9 @@ test('updateChapter generates correct action object', () => { mainLanguage: SupportedLanguage.JAVASCRIPT, supports: {} }; - const action = updateSublanguage(sublang); + const action = WorkspaceActions.updateSublanguage(sublang); expect(action).toEqual({ - type: updateSublanguage.type, + type: WorkspaceActions.updateSublanguage.type, payload: { sublang } @@ -612,9 +595,9 @@ test('updateChapter generates correct action object', () => { }); test('toggleUsingSubst generates correct action object', () => { - const action = toggleUsingSubst(true, playgroundWorkspace); + const action = WorkspaceActions.toggleUsingSubst(true, playgroundWorkspace); expect(action).toEqual({ - type: toggleUsingSubst.type, + type: WorkspaceActions.toggleUsingSubst.type, payload: { workspaceLocation: playgroundWorkspace, usingSubst: true diff --git a/src/commons/workspace/reducers/cseReducer.ts b/src/commons/workspace/reducers/cseReducer.ts index 982be2d6a1..597534c02b 100644 --- a/src/commons/workspace/reducers/cseReducer.ts +++ b/src/commons/workspace/reducers/cseReducer.ts @@ -1,15 +1,6 @@ import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; -import { - changeStepLimit, - toggleUpdateCse, - toggleUsingCse, - toggleUsingSubst, - updateBreakpointSteps, - updateChangePointSteps, - updateCurrentStep, - updateStepsTotal -} from '../WorkspaceActions'; +import WorkspaceActions from '../WorkspaceActions'; import { getWorkspaceLocation } from '../WorkspaceReducer'; import { WorkspaceManagerState } from '../WorkspaceTypes'; @@ -17,30 +8,30 @@ export const handleCseAndStepperActions = ( builder: ActionReducerMapBuilder ) => { builder - .addCase(changeStepLimit, (state, action) => { + .addCase(WorkspaceActions.changeStepLimit, (state, action) => { // TODO: Use a separate step limit for CSE and Stepper const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].stepLimit = action.payload.stepLimit; }) - .addCase(toggleUsingSubst, (state, action) => { + .addCase(WorkspaceActions.toggleUsingSubst, (state, action) => { const { workspaceLocation } = action.payload; if (workspaceLocation === 'playground' || workspaceLocation === 'sicp') { state[workspaceLocation].usingSubst = action.payload.usingSubst; } }) - .addCase(toggleUsingCse, (state, action) => { + .addCase(WorkspaceActions.toggleUsingCse, (state, action) => { const { workspaceLocation } = action.payload; if (workspaceLocation === 'playground' || workspaceLocation === 'sicp') { state[workspaceLocation].usingCse = action.payload.usingCse; } }) - .addCase(toggleUpdateCse, (state, action) => { + .addCase(WorkspaceActions.toggleUpdateCse, (state, action) => { const { workspaceLocation } = action.payload; if (workspaceLocation === 'playground' || workspaceLocation === 'sicp') { state[workspaceLocation].updateCse = action.payload.updateCse; } }) - .addCase(updateCurrentStep, (state, action) => { + .addCase(WorkspaceActions.updateCurrentStep, (state, action) => { // For some reason mutating the state directly results in type // errors, so we have to do it the old-fashioned way const workspaceLocation = getWorkspaceLocation(action); @@ -52,7 +43,7 @@ export const handleCseAndStepperActions = ( } }; }) - .addCase(updateStepsTotal, (state, action) => { + .addCase(WorkspaceActions.updateStepsTotal, (state, action) => { // For some reason mutating the state directly results in type // errors, so we have to do it the old-fashioned way const workspaceLocation = getWorkspaceLocation(action); @@ -64,7 +55,7 @@ export const handleCseAndStepperActions = ( } }; }) - .addCase(updateBreakpointSteps, (state, action) => { + .addCase(WorkspaceActions.updateBreakpointSteps, (state, action) => { // For some reason mutating the state directly results in type // errors, so we have to do it the old-fashioned way const workspaceLocation = getWorkspaceLocation(action); @@ -76,7 +67,7 @@ export const handleCseAndStepperActions = ( } }; }) - .addCase(updateChangePointSteps, (state, action) => { + .addCase(WorkspaceActions.updateChangePointSteps, (state, action) => { // For some reason mutating the state directly results in type // errors, so we have to do it the old-fashioned way const workspaceLocation = getWorkspaceLocation(action); diff --git a/src/commons/workspace/reducers/editorReducer.ts b/src/commons/workspace/reducers/editorReducer.ts index 3fc12ad3f1..3f3a9bcbca 100644 --- a/src/commons/workspace/reducers/editorReducer.ts +++ b/src/commons/workspace/reducers/editorReducer.ts @@ -1,32 +1,16 @@ import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; -import { - addEditorTab, - moveCursor, - removeEditorTab, - removeEditorTabForFile, - removeEditorTabsForDirectory, - renameEditorTabForFile, - renameEditorTabsForDirectory, - setEditorBreakpoint, - setEditorHighlightedLines, - setEditorHighlightedLinesControl, - setFolderMode, - shiftEditorTab, - updateActiveEditorTab, - updateActiveEditorTabIndex, - updateEditorValue -} from '../WorkspaceActions'; +import WorkspaceActions from '../WorkspaceActions'; import { getWorkspaceLocation } from '../WorkspaceReducer'; import { EditorTabState, WorkspaceManagerState } from '../WorkspaceTypes'; export const handleEditorActions = (builder: ActionReducerMapBuilder) => { builder - .addCase(setFolderMode, (state, action) => { + .addCase(WorkspaceActions.setFolderMode, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].isFolderModeEnabled = action.payload.isFolderModeEnabled; }) - .addCase(updateActiveEditorTabIndex, (state, action) => { + .addCase(WorkspaceActions.updateActiveEditorTabIndex, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const activeEditorTabIndex = action.payload.activeEditorTabIndex; if (activeEditorTabIndex !== null) { @@ -40,7 +24,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.updateActiveEditorTab, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { activeEditorTabOptions } = action.payload; const activeEditorTabIndex = state[workspaceLocation].activeEditorTabIndex; @@ -57,7 +41,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.updateEditorValue, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { editorTabIndex, newEditorValue } = action.payload; if (editorTabIndex < 0) { @@ -69,7 +53,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.setEditorBreakpoint, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { editorTabIndex, newBreakpoints } = action.payload; if (editorTabIndex < 0) { @@ -81,7 +65,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.setEditorHighlightedLines, (state, action) => { // TODO: This and the subsequent reducer achieves the same thing? const workspaceLocation = getWorkspaceLocation(action); const { editorTabIndex, newHighlightedLines } = action.payload; @@ -94,7 +78,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.setEditorHighlightedLinesControl, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { editorTabIndex, newHighlightedLines } = action.payload; @@ -107,7 +91,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.moveCursor, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { editorTabIndex, newCursorPosition } = action.payload; if (editorTabIndex < 0) { @@ -119,7 +103,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.addEditorTab, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { filePath, editorValue } = action.payload; @@ -144,7 +128,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.shiftEditorTab, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { previousEditorTabIndex, newEditorTabIndex } = action.payload; if (previousEditorTabIndex < 0) { @@ -178,7 +162,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.removeEditorTab, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const editorTabIndex = action.payload.editorTabIndex; if (editorTabIndex < 0) { @@ -201,7 +185,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.removeEditorTabForFile, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const removedFilePath = action.payload.removedFilePath; @@ -226,7 +210,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.removeEditorTabsForDirectory, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const removedDirectoryPath = action.payload.removedDirectoryPath; @@ -258,7 +242,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.renameEditorTabForFile, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { oldFilePath, newFilePath } = action.payload; @@ -268,7 +252,7 @@ export const handleEditorActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.renameEditorTabsForDirectory, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const { oldDirectoryPath, newDirectoryPath } = action.payload; diff --git a/src/commons/workspace/reducers/replReducer.ts b/src/commons/workspace/reducers/replReducer.ts index 98ce57a719..bca8b3c12e 100644 --- a/src/commons/workspace/reducers/replReducer.ts +++ b/src/commons/workspace/reducers/replReducer.ts @@ -2,22 +2,13 @@ import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; import { CodeOutput, InterpreterOutput } from '../../application/ApplicationTypes'; import Constants from '../../utils/Constants'; -import { - browseReplHistoryDown, - browseReplHistoryUp, - clearReplInput, - clearReplOutput, - clearReplOutputLast, - evalRepl, - sendReplInputToOutput, - updateReplValue -} from '../WorkspaceActions'; +import WorkspaceActions from '../WorkspaceActions'; import { getWorkspaceLocation } from '../WorkspaceReducer'; import { WorkspaceManagerState } from '../WorkspaceTypes'; export const handleReplActions = (builder: ActionReducerMapBuilder) => { builder - .addCase(browseReplHistoryDown, (state, action) => { + .addCase(WorkspaceActions.browseReplHistoryDown, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); if (state[workspaceLocation].replHistory.browseIndex === null) { // Not yet started browsing history, nothing to do @@ -45,7 +36,7 @@ export const handleReplActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.browseReplHistoryUp, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); const lastRecords = state[workspaceLocation].replHistory.records; const lastIndex = state[workspaceLocation].replHistory.browseIndex; @@ -77,19 +68,19 @@ export const handleReplActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.clearReplInput, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].replValue = ''; }) - .addCase(clearReplOutputLast, (state, action) => { + .addCase(WorkspaceActions.clearReplOutputLast, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].output.pop(); }) - .addCase(clearReplOutput, (state, action) => { + .addCase(WorkspaceActions.clearReplOutput, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].output = []; }) - .addCase(sendReplInputToOutput, (state, action) => { + .addCase(WorkspaceActions.sendReplInputToOutput, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); // CodeOutput properties exist in parallel with workspaceLocation const newOutput: InterpreterOutput[] = state[workspaceLocation].output.concat( @@ -111,11 +102,11 @@ export const handleReplActions = (builder: ActionReducerMapBuilder { + .addCase(WorkspaceActions.evalRepl, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].isRunning = true; }) - .addCase(updateReplValue, (state, action) => { + .addCase(WorkspaceActions.updateReplValue, (state, action) => { const workspaceLocation = getWorkspaceLocation(action); state[workspaceLocation].replValue = action.payload.newReplValue; }); diff --git a/src/features/achievement/AchievementActions.ts b/src/features/achievement/AchievementActions.ts index c62bd98a25..f184297454 100644 --- a/src/features/achievement/AchievementActions.ts +++ b/src/features/achievement/AchievementActions.ts @@ -1,112 +1,74 @@ -import { createAction } from '@reduxjs/toolkit'; import { AssessmentOverview } from 'src/commons/assessment/AssessmentTypes'; +import { createActions } from 'src/commons/redux/utils'; import { AchievementGoal, AchievementItem, AchievementUser, - ADD_EVENT, - BULK_UPDATE_ACHIEVEMENTS, - BULK_UPDATE_GOALS, EventType, - GET_ACHIEVEMENTS, - GET_GOALS, - GET_OWN_GOALS, - GET_USER_ASSESSMENT_OVERVIEWS, - GET_USERS, GoalDefinition, - GoalProgress, - HANDLE_EVENT, - REMOVE_ACHIEVEMENT, - REMOVE_GOAL, - SAVE_ACHIEVEMENTS, - SAVE_GOALS, - SAVE_USER_ASSESSMENT_OVERVIEWS, - SAVE_USERS, - UPDATE_GOAL_PROGRESS, - UPDATE_OWN_GOAL_PROGRESS + GoalProgress } from './AchievementTypes'; -export const bulkUpdateAchievements = createAction( - BULK_UPDATE_ACHIEVEMENTS, - (achievements: AchievementItem[]) => ({ payload: achievements }) -); - -export const bulkUpdateGoals = createAction(BULK_UPDATE_GOALS, (goals: GoalDefinition[]) => ({ - payload: goals -})); - -export const getAchievements = createAction(GET_ACHIEVEMENTS, () => ({ payload: {} })); - -export const getGoals = createAction(GET_GOALS, (studentCourseRegId: number) => ({ - payload: studentCourseRegId -})); - -export const getOwnGoals = createAction(GET_OWN_GOALS, () => ({ payload: {} })); - -export const getUserAssessmentOverviews = createAction( - GET_USER_ASSESSMENT_OVERVIEWS, - (studentCourseRegId: number) => ({ payload: studentCourseRegId }) -); - -export const getUsers = createAction(GET_USERS, () => ({ payload: {} })); - -export const removeAchievement = createAction(REMOVE_ACHIEVEMENT, (uuid: string) => ({ - payload: uuid -})); - -export const removeGoal = createAction(REMOVE_GOAL, (uuid: string) => ({ payload: uuid })); - -export const updateOwnGoalProgress = createAction( - UPDATE_OWN_GOAL_PROGRESS, - (progress: GoalProgress) => ({ payload: progress }) -); - -export const addEvent = createAction(ADD_EVENT, (eventNames: EventType[]) => ({ - payload: eventNames -})); - -export const handleEvent = createAction(HANDLE_EVENT, (loggedEvents: EventType[][]) => ({ - payload: loggedEvents -})); - -export const updateGoalProgress = createAction( - UPDATE_GOAL_PROGRESS, - (studentCourseRegId: number, progress: GoalProgress) => ({ - payload: { studentCourseRegId, progress } - }) -); - -/* - Note: This updates the frontend Achievement Redux store. - Please refer to AchievementReducer to find out more. -*/ -export const saveAchievements = createAction( - SAVE_ACHIEVEMENTS, - (achievements: AchievementItem[]) => ({ payload: achievements }) -); - -/* - Note: This updates the frontend Achievement Redux store. - Please refer to AchievementReducer to find out more. -*/ -export const saveGoals = createAction(SAVE_GOALS, (goals: AchievementGoal[]) => ({ - payload: goals -})); - -/* - Note: This updates the frontend Achievement Redux store. - Please refer to AchievementReducer to find out more. -*/ -export const saveUsers = createAction(SAVE_USERS, (users: AchievementUser[]) => ({ - payload: users -})); - -/* - Note: This updates the frontend Achievement Redux store. - Please refer to AchievementReducer to find out more. -*/ -export const saveUserAssessmentOverviews = createAction( - SAVE_USER_ASSESSMENT_OVERVIEWS, - (assessmentOverviews: AssessmentOverview[]) => ({ payload: assessmentOverviews }) -); +const AchievementActions = createActions('achievement', { + bulkUpdateAchievements: (achievements: AchievementItem[]) => achievements, + bulkUpdateGoals: (goals: GoalDefinition[]) => goals, + getAchievements: () => ({}), + getGoals: (studentCourseRegId: number) => studentCourseRegId, + getOwnGoals: () => ({}), + getUserAssessmentOverviews: (studentCourseRegId: number) => studentCourseRegId, + getUsers: () => ({}), + removeAchievement: (uuid: string) => uuid, + removeGoal: (uuid: string) => uuid, + updateOwnGoalProgress: (progress: GoalProgress) => progress, + addEvent: (eventNames: EventType[]) => eventNames, + handleEvent: (loggedEvents: EventType[][]) => loggedEvents, + updateGoalProgress: (studentCourseRegId: number, progress: GoalProgress) => ({ + studentCourseRegId, + progress + }), + /** + * Note: This updates the frontend Achievement Redux store. + * Please refer to AchievementReducer to find out more. + */ + saveAchievements: (achievements: AchievementItem[]) => achievements, + /** + * Note: This updates the frontend Achievement Redux store. + * Please refer to AchievementReducer to find out more. + */ + saveGoals: (goals: AchievementGoal[]) => goals, + /** + * Note: This updates the frontend Achievement Redux store. + * Please refer to AchievementReducer to find out more. + */ + saveUsers: (users: AchievementUser[]) => users, + /** + * Note: This updates the frontend Achievement Redux store. + * Please refer to AchievementReducer to find out more. + */ + saveUserAssessmentOverviews: (assessmentOverviews: AssessmentOverview[]) => assessmentOverviews +}); + +// For compatibility with existing code (reducer) +export const { + bulkUpdateAchievements, + bulkUpdateGoals, + getAchievements, + getGoals, + getOwnGoals, + getUserAssessmentOverviews, + getUsers, + removeAchievement, + removeGoal, + updateOwnGoalProgress, + addEvent, + handleEvent, + updateGoalProgress, + saveAchievements, + saveGoals, + saveUsers, + saveUserAssessmentOverviews +} = AchievementActions; + +// For compatibility with existing code (actions helper) +export default AchievementActions; diff --git a/src/features/achievement/AchievementTypes.ts b/src/features/achievement/AchievementTypes.ts index 48c302f18f..93fc1df3e7 100644 --- a/src/features/achievement/AchievementTypes.ts +++ b/src/features/achievement/AchievementTypes.ts @@ -2,24 +2,6 @@ import { AssessmentOverview } from 'src/commons/assessment/AssessmentTypes'; import { BooleanExpression } from './ExpressionTypes'; -export const ADD_EVENT = 'ADD_EVENT'; -export const BULK_UPDATE_ACHIEVEMENTS = 'BULK_UPDATE_ACHIEVEMENTS'; -export const BULK_UPDATE_GOALS = 'BULK_UPDATE_GOALS'; -export const HANDLE_EVENT = 'HANDLE_EVENT'; -export const GET_ACHIEVEMENTS = 'GET_ACHIEVEMENTS'; -export const GET_GOALS = 'GET_GOALS'; -export const GET_OWN_GOALS = 'GET_OWN_GOALS'; -export const GET_USER_ASSESSMENT_OVERVIEWS = 'GET_USER_ASSESSMENT_OVERVIEWS'; -export const GET_USERS = 'GET_USERS'; -export const REMOVE_ACHIEVEMENT = 'REMOVE_ACHIEVEMENT'; -export const REMOVE_GOAL = 'REMOVE_GOAL'; -export const SAVE_ACHIEVEMENTS = 'SAVE_ACHIEVEMENTS'; -export const SAVE_GOALS = 'SAVE_GOALS'; -export const SAVE_USER_ASSESSMENT_OVERVIEWS = 'SAVE_USER_ASSESSMENT_OVERVIEWS'; -export const SAVE_USERS = 'SAVE_USERS'; -export const UPDATE_GOAL_PROGRESS = 'UPDATE_GOAL_PROGRESS'; -export const UPDATE_OWN_GOAL_PROGRESS = 'UPDATE_OWN_GOAL_PROGRESS'; - export enum AchievementStatus { ACTIVE = 'ACTIVE', // deadline not over and not completed COMPLETED = 'COMPLETED', // completed, regardless of deadline diff --git a/src/features/achievement/__tests__/AchievementActions.ts b/src/features/achievement/__tests__/AchievementActions.ts index a4fcdfc379..5b7ac2dc94 100644 --- a/src/features/achievement/__tests__/AchievementActions.ts +++ b/src/features/achievement/__tests__/AchievementActions.ts @@ -1,12 +1,11 @@ import { mockAchievements } from '../../../commons/mocks/AchievementMocks'; import { getAchievements, removeAchievement, saveAchievements } from '../AchievementActions'; -import { GET_ACHIEVEMENTS, REMOVE_ACHIEVEMENT, SAVE_ACHIEVEMENTS } from '../AchievementTypes'; test('saveAchievements generates correct action object', () => { const action = saveAchievements([]); expect(action).toEqual({ - type: SAVE_ACHIEVEMENTS, + type: saveAchievements.type, payload: [] }); }); @@ -15,7 +14,7 @@ test('getAchievements generates correct action object', () => { const action = getAchievements(); expect(action).toEqual({ - type: GET_ACHIEVEMENTS, + type: getAchievements.type, payload: {} }); }); @@ -24,7 +23,7 @@ test('removeAchievement generates correct action object', () => { const action = removeAchievement(mockAchievements[0].uuid); expect(action).toEqual({ - type: REMOVE_ACHIEVEMENT, + type: removeAchievement.type, payload: mockAchievements[0].uuid }); }); diff --git a/src/features/achievement/__tests__/AchievementReducer.ts b/src/features/achievement/__tests__/AchievementReducer.ts index 13c493968b..9f37c5e910 100644 --- a/src/features/achievement/__tests__/AchievementReducer.ts +++ b/src/features/achievement/__tests__/AchievementReducer.ts @@ -1,12 +1,13 @@ import { defaultAchievement } from 'src/commons/application/ApplicationTypes'; +import { saveAchievements } from '../AchievementActions'; import { AchievementReducer } from '../AchievementReducer'; -import { AchievementItem, AchievementState, SAVE_ACHIEVEMENTS } from '../AchievementTypes'; +import { AchievementItem, AchievementState } from '../AchievementTypes'; test('SAVE_ACHIEVEMENTS works correctly on default achievements', () => { const achievementItems: AchievementItem[] = []; const action = { - type: SAVE_ACHIEVEMENTS, + type: saveAchievements.type, payload: achievementItems } as const; const result: AchievementState = AchievementReducer(defaultAchievement, action); diff --git a/src/features/groundControl/GroundControlActions.ts b/src/features/groundControl/GroundControlActions.ts index 0f4b32f555..cdb7439b82 100644 --- a/src/features/groundControl/GroundControlActions.ts +++ b/src/features/groundControl/GroundControlActions.ts @@ -1,58 +1,40 @@ -import { createAction } from '@reduxjs/toolkit'; - -import { - ASSIGN_ENTRIES_FOR_VOTING, - CHANGE_DATE_ASSESSMENT, - CHANGE_TEAM_SIZE_ASSESSMENT, - CONFIGURE_ASSESSMENT, - DELETE_ASSESSMENT, - PUBLISH_ASSESSMENT, - PUBLISH_GRADING_ALL, - UNPUBLISH_GRADING_ALL, - UPLOAD_ASSESSMENT -} from './GroundControlTypes'; - -export const changeDateAssessment = createAction( - CHANGE_DATE_ASSESSMENT, - (id: number, openAt: string, closeAt: string) => ({ payload: { id, openAt, closeAt } }) -); - -export const changeTeamSizeAssessment = createAction( - CHANGE_TEAM_SIZE_ASSESSMENT, - (id: number, maxTeamSize: number) => ({ payload: { id, maxTeamSize } }) -); - -export const deleteAssessment = createAction(DELETE_ASSESSMENT, (id: number) => ({ payload: id })); - -export const publishAssessment = createAction( - PUBLISH_ASSESSMENT, - (togglePublishAssessmentTo: boolean, id: number) => ({ - payload: { id, togglePublishAssessmentTo } - }) -); - -export const publishGradingAll = createAction(PUBLISH_GRADING_ALL, (id: number) => ({ - payload: id -})); - -export const unpublishGradingAll = createAction(UNPUBLISH_GRADING_ALL, (id: number) => ({ - payload: id -})); - -export const uploadAssessment = createAction( - UPLOAD_ASSESSMENT, - (file: File, forceUpdate: boolean, assessmentConfigId: number) => ({ - payload: { file, forceUpdate, assessmentConfigId } - }) -); - -export const configureAssessment = createAction( - CONFIGURE_ASSESSMENT, - (id: number, hasVotingFeatures: boolean, hasTokenCounter: boolean) => ({ - payload: { id, hasVotingFeatures, hasTokenCounter } - }) -); - -export const assignEntriesForVoting = createAction(ASSIGN_ENTRIES_FOR_VOTING, (id: number) => ({ - payload: { id } -})); +import { createActions } from 'src/commons/redux/utils'; + +const GroundControlActions = createActions('groundControl', { + changeDateAssessment: (id: number, openAt: string, closeAt: string) => ({ id, openAt, closeAt }), + changeTeamSizeAssessment: (id: number, maxTeamSize: number) => ({ id, maxTeamSize }), + deleteAssessment: (id: number) => id, + publishAssessment: (togglePublishAssessmentTo: boolean, id: number) => ({ + id, + togglePublishAssessmentTo + }), + publishGradingAll: (id: number) => id, + unpublishGradingAll: (id: number) => id, + uploadAssessment: (file: File, forceUpdate: boolean, assessmentConfigId: number) => ({ + file, + forceUpdate, + assessmentConfigId + }), + configureAssessment: (id: number, hasVotingFeatures: boolean, hasTokenCounter: boolean) => ({ + id, + hasVotingFeatures, + hasTokenCounter + }), + assignEntriesForVoting: (id: number) => ({ id }) +}); + +// For compatibility with existing code (reducer) +export const { + changeDateAssessment, + changeTeamSizeAssessment, + deleteAssessment, + publishAssessment, + publishGradingAll, + unpublishGradingAll, + uploadAssessment, + configureAssessment, + assignEntriesForVoting +} = GroundControlActions; + +// For compatibility with existing code (actions helper) +export default GroundControlActions; diff --git a/src/features/groundControl/GroundControlTypes.ts b/src/features/groundControl/GroundControlTypes.ts deleted file mode 100644 index 7999c9860a..0000000000 --- a/src/features/groundControl/GroundControlTypes.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const CHANGE_DATE_ASSESSMENT = 'CHANGE_DATE_ASSESSMENT'; -export const CHANGE_TEAM_SIZE_ASSESSMENT = 'CHANGE_TEAM_SIZE_ASSESSMENT'; -export const DELETE_ASSESSMENT = 'DELETE_ASSESSMENT'; -export const PUBLISH_ASSESSMENT = 'PUBLISH_ASSESSMENT'; -export const PUBLISH_GRADING_ALL = 'PUBLISH_GRADING_ALL'; -export const UNPUBLISH_GRADING_ALL = 'UNPUBLISH_GRADING_ALL'; -export const UPLOAD_ASSESSMENT = 'UPLOAD_ASSESSMENT'; -export const CONFIGURE_ASSESSMENT = 'CONFIGURE_ASSESSMENT'; -export const ASSIGN_ENTRIES_FOR_VOTING = 'ASSIGN_ENTRIES_FOR_VOTING'; diff --git a/src/features/remoteExecution/RemoteExecutionActions.ts b/src/features/remoteExecution/RemoteExecutionActions.ts index f61b3ffa2d..71445cd4d0 100644 --- a/src/features/remoteExecution/RemoteExecutionActions.ts +++ b/src/features/remoteExecution/RemoteExecutionActions.ts @@ -1,46 +1,31 @@ -import { createAction } from '@reduxjs/toolkit'; +import { createActions } from 'src/commons/redux/utils'; import { WorkspaceLocation } from '../../commons/workspace/WorkspaceTypes'; -import { - Device, - DeviceSession, - REMOTE_EXEC_CONNECT, - REMOTE_EXEC_DISCONNECT, - REMOTE_EXEC_FETCH_DEVICES, - REMOTE_EXEC_REPL_INPUT, - REMOTE_EXEC_RUN, - REMOTE_EXEC_UPDATE_DEVICES, - REMOTE_EXEC_UPDATE_SESSION -} from './RemoteExecutionTypes'; - -export const remoteExecFetchDevices = createAction(REMOTE_EXEC_FETCH_DEVICES, () => ({ - payload: {} -})); - -export const remoteExecUpdateDevices = createAction( - REMOTE_EXEC_UPDATE_DEVICES, - (devices: Device[]) => ({ payload: devices }) -); - -export const remoteExecUpdateSession = createAction( - REMOTE_EXEC_UPDATE_SESSION, - (session?: DeviceSession) => ({ payload: session }) -); - -export const remoteExecConnect = createAction( - REMOTE_EXEC_CONNECT, - (workspace: WorkspaceLocation, device: Device) => ({ payload: { workspace, device } }) -); - -export const remoteExecDisconnect = createAction(REMOTE_EXEC_DISCONNECT, () => ({ payload: {} })); - -export const remoteExecRun = createAction( - REMOTE_EXEC_RUN, - (files: Record, entrypointFilePath: string) => ({ - payload: { files, entrypointFilePath } - }) -); - -export const remoteExecReplInput = createAction(REMOTE_EXEC_REPL_INPUT, (input: string) => ({ - payload: input -})); +import { Device, DeviceSession } from './RemoteExecutionTypes'; + +const RemoteExecutionActions = createActions('remoteExecution', { + remoteExecFetchDevices: () => ({}), + remoteExecUpdateDevices: (devices: Device[]) => devices, + remoteExecUpdateSession: (session?: DeviceSession) => session, + remoteExecConnect: (workspace: WorkspaceLocation, device: Device) => ({ workspace, device }), + remoteExecDisconnect: () => ({}), + remoteExecRun: (files: Record, entrypointFilePath: string) => ({ + files, + entrypointFilePath + }), + remoteExecReplInput: (input: string) => input +}); + +// For compatibility with existing code (reducer) +export const { + remoteExecFetchDevices, + remoteExecUpdateDevices, + remoteExecUpdateSession, + remoteExecConnect, + remoteExecDisconnect, + remoteExecRun, + remoteExecReplInput +} = RemoteExecutionActions; + +// For compatibility with existing code (actions helper) +export default RemoteExecutionActions; diff --git a/src/features/remoteExecution/RemoteExecutionTypes.ts b/src/features/remoteExecution/RemoteExecutionTypes.ts index a35c1036b5..bc037e0f6a 100644 --- a/src/features/remoteExecution/RemoteExecutionTypes.ts +++ b/src/features/remoteExecution/RemoteExecutionTypes.ts @@ -5,16 +5,6 @@ import { WorkspaceLocation } from 'src/commons/workspace/WorkspaceTypes'; import { Ev3DevicePeripherals } from './RemoteExecutionEv3Types'; -export const REMOTE_EXEC_FETCH_DEVICES = 'REMOTE_EXEC_FETCH_DEVICES'; -export const REMOTE_EXEC_UPDATE_DEVICES = 'REMOTE_EXEC_UPDATE_DEVICES'; -export const REMOTE_EXEC_UPDATE_SESSION = 'REMOTE_EXEC_UPDATE_SESSION'; - -export const REMOTE_EXEC_CONNECT = 'REMOTE_EXEC_CONNECT'; -export const REMOTE_EXEC_DISCONNECT = 'REMOTE_EXEC_DISCONNECT'; - -export const REMOTE_EXEC_RUN = 'REMOTE_EXEC_RUN'; -export const REMOTE_EXEC_REPL_INPUT = 'REMOTE_EXEC_REPL_INPUT'; - export interface Device { id: number; title: string; diff --git a/src/features/sourceRecorder/sourcereel/SourcereelActions.ts b/src/features/sourceRecorder/sourcereel/SourcereelActions.ts index 1238150cb1..9d57160397 100644 --- a/src/features/sourceRecorder/sourcereel/SourcereelActions.ts +++ b/src/features/sourceRecorder/sourcereel/SourcereelActions.ts @@ -1,64 +1,54 @@ -import { createAction } from '@reduxjs/toolkit'; +import { createActions } from 'src/commons/redux/utils'; import { WorkspaceLocation } from '../../../commons/workspace/WorkspaceTypes'; import { Input, PlaybackData } from '../SourceRecorderTypes'; -import { - DELETE_SOURCECAST_ENTRY, - RECORD_INIT, - RECORD_INPUT, - RESET_INPUTS, - TIMER_PAUSE, - TIMER_RESET, - TIMER_RESUME, - TIMER_START, - TIMER_STOP -} from './SourcereelTypes'; -export const deleteSourcecastEntry = createAction( - DELETE_SOURCECAST_ENTRY, - (id: number, workspaceLocation: WorkspaceLocation) => ({ payload: { id, workspaceLocation } }) -); - -export const recordInit = createAction( - RECORD_INIT, - (initData: PlaybackData['init'], workspaceLocation: WorkspaceLocation) => ({ - payload: { initData, workspaceLocation } - }) -); - -export const recordInput = createAction( - RECORD_INPUT, - (input: Input, workspaceLocation: WorkspaceLocation) => ({ - payload: { input, workspaceLocation } - }) -); - -export const resetInputs = createAction( - RESET_INPUTS, - (inputs: Input[], workspaceLocation: WorkspaceLocation) => ({ - payload: { inputs, workspaceLocation } - }) -); - -export const timerPause = createAction(TIMER_PAUSE, (workspaceLocation: WorkspaceLocation) => ({ - payload: { timeNow: Date.now(), workspaceLocation } -})); - -export const timerReset = createAction(TIMER_RESET, (workspaceLocation: WorkspaceLocation) => ({ - payload: { workspaceLocation } -})); - -export const timerResume = createAction( - TIMER_RESUME, - (timeBefore: number, workspaceLocation: WorkspaceLocation) => ({ - payload: { timeBefore, timeNow: Date.now(), workspaceLocation } - }) -); - -export const timerStart = createAction(TIMER_START, (workspaceLocation: WorkspaceLocation) => ({ - payload: { timeNow: Date.now(), workspaceLocation } -})); - -export const timerStop = createAction(TIMER_STOP, (workspaceLocation: WorkspaceLocation) => ({ - payload: { timeNow: Date.now(), workspaceLocation } -})); +const SourcereelActions = createActions('sourcereel', { + deleteSourcecastEntry: (id: number, workspaceLocation: WorkspaceLocation) => ({ + id, + workspaceLocation + }), + recordInit: (initData: PlaybackData['init'], workspaceLocation: WorkspaceLocation) => ({ + initData, + workspaceLocation + }), + recordInput: (input: Input, workspaceLocation: WorkspaceLocation) => ({ + input, + workspaceLocation + }), + resetInputs: (inputs: Input[], workspaceLocation: WorkspaceLocation) => ({ + inputs, + workspaceLocation + }), + timerPause: (workspaceLocation: WorkspaceLocation) => ({ + timeNow: Date.now(), + workspaceLocation + }), + timerReset: (workspaceLocation: WorkspaceLocation) => ({ workspaceLocation }), + timerResume: (timeBefore: number, workspaceLocation: WorkspaceLocation) => ({ + timeBefore, + timeNow: Date.now(), + workspaceLocation + }), + timerStart: (workspaceLocation: WorkspaceLocation) => ({ + timeNow: Date.now(), + workspaceLocation + }), + timerStop: (workspaceLocation: WorkspaceLocation) => ({ timeNow: Date.now(), workspaceLocation }) +}); + +// For compatibility with existing code (reducer) +export const { + deleteSourcecastEntry, + recordInit, + recordInput, + resetInputs, + timerPause, + timerReset, + timerResume, + timerStart, + timerStop +} = SourcereelActions; + +// For compatibility with existing code (actions helper) +export default SourcereelActions; diff --git a/src/features/sourceRecorder/sourcereel/SourcereelTypes.ts b/src/features/sourceRecorder/sourcereel/SourcereelTypes.ts index 76f739922a..0d4a4ad67c 100644 --- a/src/features/sourceRecorder/sourcereel/SourcereelTypes.ts +++ b/src/features/sourceRecorder/sourcereel/SourcereelTypes.ts @@ -1,16 +1,6 @@ import { WorkspaceState } from '../../../commons/workspace/WorkspaceTypes'; import { PlaybackData, RecordingStatus } from '../SourceRecorderTypes'; -export const DELETE_SOURCECAST_ENTRY = 'DELETE_SOURCECAST_ENTRY'; -export const RECORD_INIT = 'RECORD_INIT'; -export const RECORD_INPUT = 'RECORD_INPUT'; -export const RESET_INPUTS = 'RESET_INPUTS'; -export const TIMER_PAUSE = 'TIMER_PAUSE'; -export const TIMER_RESET = 'TIMER_RESET'; -export const TIMER_RESUME = 'TIMER_RESUME'; -export const TIMER_START = 'TIMER_START'; -export const TIMER_STOP = 'TIMER_STOP'; - type SourcereelWorkspaceAttr = { readonly playbackData: PlaybackData; readonly recordingStatus: RecordingStatus; diff --git a/src/features/sourceRecorder/sourcereel/__tests__/SourcereelActions.ts b/src/features/sourceRecorder/sourcereel/__tests__/SourcereelActions.ts index 928a078f04..fe91ab6db5 100644 --- a/src/features/sourceRecorder/sourcereel/__tests__/SourcereelActions.ts +++ b/src/features/sourceRecorder/sourcereel/__tests__/SourcereelActions.ts @@ -3,24 +3,7 @@ import { Chapter } from 'js-slang/dist/types'; import { ExternalLibraryName } from '../../../../commons/application/types/ExternalTypes'; import { WorkspaceLocation } from '../../../../commons/workspace/WorkspaceTypes'; import { CodeDelta, Input, PlaybackData } from '../../SourceRecorderTypes'; -import { - recordInit, - recordInput, - timerPause, - timerReset, - timerResume, - timerStart, - timerStop -} from '../SourcereelActions'; -import { - RECORD_INIT, - RECORD_INPUT, - TIMER_PAUSE, - TIMER_RESET, - TIMER_RESUME, - TIMER_START, - TIMER_STOP -} from '../SourcereelTypes'; +import SourcereelActions from '../SourcereelActions'; const sourcereelWorkspace: WorkspaceLocation = 'sourcereel'; @@ -34,9 +17,9 @@ test('recordInit generates correct action object', () => { chapter: Chapter.SOURCE_1, externalLibrary: ExternalLibraryName.NONE }; - const action = recordInit(initData, sourcereelWorkspace); + const action = SourcereelActions.recordInit(initData, sourcereelWorkspace); expect(action).toEqual({ - type: RECORD_INIT, + type: SourcereelActions.recordInit.type, payload: { initData, workspaceLocation: sourcereelWorkspace @@ -62,9 +45,9 @@ test('recordInput generates correct action object', () => { type: 'codeDelta', data: codeDelta }; - const action = recordInput(input, sourcereelWorkspace); + const action = SourcereelActions.recordInput(input, sourcereelWorkspace); expect(action).toEqual({ - type: RECORD_INPUT, + type: SourcereelActions.recordInput.type, payload: { input, workspaceLocation: sourcereelWorkspace @@ -74,40 +57,40 @@ test('recordInput generates correct action object', () => { test('timerPause generates correct action object', () => { const currentTime = Date.now(); - const action = timerPause(sourcereelWorkspace); - expect(action.type).toEqual(TIMER_PAUSE); + const action = SourcereelActions.timerPause(sourcereelWorkspace); + expect(action.type).toEqual(SourcereelActions.timerPause.type); expect(action.payload.workspaceLocation).toEqual(sourcereelWorkspace); expect(dateIsCloseEnough(currentTime, action.payload.timeNow)).toBeTruthy(); }); test('timerReset generates correct action object', () => { - const action = timerReset(sourcereelWorkspace); + const action = SourcereelActions.timerReset(sourcereelWorkspace); expect(action).toEqual({ - type: TIMER_RESET, + type: SourcereelActions.timerReset.type, payload: { workspaceLocation: sourcereelWorkspace } }); }); test('timerResume generates correct action object', () => { const currentTime = Date.now(); - const action = timerResume(1000, sourcereelWorkspace); - expect(action.type).toEqual(TIMER_RESUME); + const action = SourcereelActions.timerResume(1000, sourcereelWorkspace); + expect(action.type).toEqual(SourcereelActions.timerResume.type); expect(action.payload.workspaceLocation).toEqual(sourcereelWorkspace); expect(dateIsCloseEnough(currentTime, action.payload.timeNow)).toBeTruthy(); }); test('timerStart generates correct action object', () => { const currentTime = Date.now(); - const action = timerStart(sourcereelWorkspace); - expect(action.type).toEqual(TIMER_START); + const action = SourcereelActions.timerStart(sourcereelWorkspace); + expect(action.type).toEqual(SourcereelActions.timerStart.type); expect(action.payload.workspaceLocation).toEqual(sourcereelWorkspace); expect(dateIsCloseEnough(currentTime, action.payload.timeNow)).toBeTruthy(); }); test('timerStop generates correct action object', () => { const currentTime = Date.now(); - const action = timerStop(sourcereelWorkspace); - expect(action.type).toEqual(TIMER_STOP); + const action = SourcereelActions.timerStop(sourcereelWorkspace); + expect(action.type).toEqual(SourcereelActions.timerStop.type); expect(action.payload.workspaceLocation).toEqual(sourcereelWorkspace); expect(dateIsCloseEnough(currentTime, action.payload.timeNow)).toBeTruthy(); }); diff --git a/src/features/sourceRecorder/sourcereel/__tests__/SourcereelReducer.ts b/src/features/sourceRecorder/sourcereel/__tests__/SourcereelReducer.ts index 6ec831bff7..da781f0015 100644 --- a/src/features/sourceRecorder/sourcereel/__tests__/SourcereelReducer.ts +++ b/src/features/sourceRecorder/sourcereel/__tests__/SourcereelReducer.ts @@ -5,16 +5,8 @@ import { SourceActionType } from 'src/commons/utils/ActionsHelper'; import { defaultWorkspaceManager } from '../../../../commons/application/ApplicationTypes'; import { ExternalLibraryName } from '../../../../commons/application/types/ExternalTypes'; import { CodeDelta, Input, PlaybackData, RecordingStatus } from '../../SourceRecorderTypes'; +import SourcereelActions from '../SourcereelActions'; import { SourcereelReducer } from '../SourcereelReducer'; -import { - RECORD_INIT, - RECORD_INPUT, - TIMER_PAUSE, - TIMER_RESET, - TIMER_RESUME, - TIMER_START, - TIMER_STOP -} from '../SourcereelTypes'; const generateAction = (type: S, payload: T) => createAction(type, (payload: T) => ({ payload }))(payload); @@ -26,7 +18,10 @@ describe('RECORD_INIT', () => { chapter: Chapter.SOURCE_1, externalLibrary: ExternalLibraryName.NONE }; - const action = generateAction(RECORD_INIT, { initData, workspaceLocation: undefined! }); + const action = generateAction(SourcereelActions.recordInit.type, { + initData, + workspaceLocation: undefined! + }); const result = SourcereelReducer(defaultWorkspaceManager.sourcereel, action); expect(result).toEqual({ ...defaultWorkspaceManager.sourcereel, @@ -59,7 +54,10 @@ describe('RECORD_INPUT', () => { data: delta }; - const action = generateAction(RECORD_INPUT, { input, workspaceLocation: undefined! }); + const action = generateAction(SourcereelActions.recordInput.type, { + input, + workspaceLocation: undefined! + }); const result = SourcereelReducer(defaultWorkspaceManager.sourcereel, action); expect(result).toEqual({ ...defaultWorkspaceManager.sourcereel, @@ -74,7 +72,10 @@ describe('RECORD_INPUT', () => { describe('TIMER_PAUSE', () => { test('pauses timer correctly', () => { const timeNow = 123456; - const action = generateAction(TIMER_PAUSE, { timeNow, workspaceLocation: undefined! }); + const action = generateAction(SourcereelActions.timerPause.type, { + timeNow, + workspaceLocation: undefined! + }); const result = SourcereelReducer(defaultWorkspaceManager.sourcereel, action); expect(result).toEqual({ ...defaultWorkspaceManager.sourcereel, @@ -89,7 +90,9 @@ describe('TIMER_PAUSE', () => { describe('TIMER_RESET', () => { test('pauses timer correctly', () => { - const action = generateAction(TIMER_RESET, { workspaceLocation: undefined! }); + const action = generateAction(SourcereelActions.timerReset.type, { + workspaceLocation: undefined! + }); const result = SourcereelReducer(defaultWorkspaceManager.sourcereel, action); expect(result).toEqual({ ...defaultWorkspaceManager.sourcereel, @@ -103,7 +106,7 @@ describe('TIMER_RESET', () => { describe('TIMER_RESUME', () => { test('pauses timer correctly', () => { const timeNow = 123456; - const action = generateAction(TIMER_RESUME, { + const action = generateAction(SourcereelActions.timerResume.type, { timeNow, workspaceLocation: undefined!, timeBefore: 0 @@ -120,7 +123,10 @@ describe('TIMER_RESUME', () => { describe('TIMER_START', () => { test('pauses timer correctly', () => { const timeNow = 123456; - const action = generateAction(TIMER_START, { timeNow, workspaceLocation: undefined! }); + const action = generateAction(SourcereelActions.timerStart.type, { + timeNow, + workspaceLocation: undefined! + }); const result = SourcereelReducer(defaultWorkspaceManager.sourcereel, action); expect(result).toEqual({ ...defaultWorkspaceManager.sourcereel, @@ -134,7 +140,10 @@ describe('TIMER_START', () => { describe('TIMER_STOP', () => { test('pauses timer correctly', () => { const timeNow = 123456; - const action = generateAction(TIMER_STOP, { timeNow, workspaceLocation: undefined! }); + const action = generateAction(SourcereelActions.timerStop.type, { + timeNow, + workspaceLocation: undefined! + }); const result = SourcereelReducer(defaultWorkspaceManager.sourcereel, action); expect(result).toEqual({ ...defaultWorkspaceManager.sourcereel, diff --git a/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx b/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx index 996933e88e..7cbdbd1a1f 100644 --- a/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx +++ b/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx @@ -9,27 +9,7 @@ import SessionActions from 'src/commons/application/actions/SessionActions'; import { changeSideContentHeight } from 'src/commons/sideContent/SideContentActions'; import { showSimpleErrorDialog } from 'src/commons/utils/DialogHelper'; import { useTypedSelector } from 'src/commons/utils/Hooks'; -import { - beginClearContext, - browseReplHistoryDown, - browseReplHistoryUp, - changeExecTime, - clearReplOutput, - evalEditor, - evalRepl, - evalTestcase, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - resetWorkspace, - runAllTestcases, - setEditorBreakpoint, - updateActiveEditorTabIndex, - updateCurrentSubmissionId, - updateEditorValue, - updateHasUnsavedChanges, - updateReplValue -} from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; import { defaultWorkspaceManager } from '../../../../commons/application/ApplicationTypes'; import { @@ -120,42 +100,48 @@ const GradingWorkspace: React.FC = props => { handlePromptAutocomplete } = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), + handleBrowseHistoryDown: () => + dispatch(WorkspaceActions.browseReplHistoryDown(workspaceLocation)), + handleBrowseHistoryUp: () => + dispatch(WorkspaceActions.browseReplHistoryUp(workspaceLocation)), handleClearContext: (library: Library, shouldInitLibrary: boolean) => - dispatch(beginClearContext(workspaceLocation, library, shouldInitLibrary)), + dispatch(WorkspaceActions.beginClearContext(workspaceLocation, library, shouldInitLibrary)), handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + dispatch(WorkspaceActions.navigateToDeclaration(workspaceLocation, cursorPosition)), + handleEditorEval: () => dispatch(WorkspaceActions.evalEditor(workspaceLocation)), handleSetActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), + dispatch( + WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex) + ), handleRemoveEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)), + dispatch(WorkspaceActions.removeEditorTab(workspaceLocation, editorTabIndex)), handleEditorValueChange: (editorTabIndex: number, newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, 0, newEditorValue)), + dispatch(WorkspaceActions.updateEditorValue(workspaceLocation, 0, newEditorValue)), handleEditorUpdateBreakpoints: (editorTabIndex: number, newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints)), + dispatch( + WorkspaceActions.setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints) + ), handleGradingFetch: (submissionId: number) => dispatch(SessionActions.fetchGrading(submissionId)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), - handleReplOutputClear: () => dispatch(clearReplOutput(workspaceLocation)), + handleReplEval: () => dispatch(WorkspaceActions.evalRepl(workspaceLocation)), + handleReplOutputClear: () => dispatch(WorkspaceActions.clearReplOutput(workspaceLocation)), handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)), + dispatch(WorkspaceActions.updateReplValue(newValue, workspaceLocation)), handleResetWorkspace: (options: Partial) => - dispatch(resetWorkspace(workspaceLocation, options)), + dispatch(WorkspaceActions.resetWorkspace(workspaceLocation, options)), handleChangeExecTime: (execTimeMs: number) => - dispatch(changeExecTime(execTimeMs, workspaceLocation)), + dispatch(WorkspaceActions.changeExecTime(execTimeMs, workspaceLocation)), handleSideContentHeightChange: (heightChange: number) => dispatch(changeSideContentHeight(heightChange, workspaceLocation)), handleTestcaseEval: (testcaseId: number) => - dispatch(evalTestcase(workspaceLocation, testcaseId)), - handleRunAllTestcases: () => dispatch(runAllTestcases(workspaceLocation)), + dispatch(WorkspaceActions.evalTestcase(workspaceLocation, testcaseId)), + handleRunAllTestcases: () => dispatch(WorkspaceActions.runAllTestcases(workspaceLocation)), handleUpdateCurrentSubmissionId: (submissionId: number, questionId: number) => - dispatch(updateCurrentSubmissionId(submissionId, questionId)), + dispatch(WorkspaceActions.updateCurrentSubmissionId(submissionId, questionId)), handleUpdateHasUnsavedChanges: (unsavedChanges: boolean) => - dispatch(updateHasUnsavedChanges(workspaceLocation, unsavedChanges)), + dispatch(WorkspaceActions.updateHasUnsavedChanges(workspaceLocation, unsavedChanges)), handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)) + dispatch(WorkspaceActions.promptAutocomplete(workspaceLocation, row, col, callback)) }; }, [dispatch]); diff --git a/src/pages/academy/sourcereel/Sourcereel.tsx b/src/pages/academy/sourcereel/Sourcereel.tsx index a514cd2a45..c591898897 100644 --- a/src/pages/academy/sourcereel/Sourcereel.tsx +++ b/src/pages/academy/sourcereel/Sourcereel.tsx @@ -48,24 +48,7 @@ import SourceRecorderControlBar, { import SourcecastTable from '../../../commons/sourceRecorder/SourceRecorderTable'; import { useTypedSelector } from '../../../commons/utils/Hooks'; import Workspace, { WorkspaceProps } from '../../../commons/workspace/Workspace'; -import { - browseReplHistoryDown, - browseReplHistoryUp, - chapterSelect, - clearReplOutput, - evalEditor, - evalRepl, - externalLibrarySelect, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - setEditorBreakpoint, - setIsEditorReadonly, - toggleEditorAutorun, - updateActiveEditorTabIndex, - updateEditorValue, - updateReplValue -} from '../../../commons/workspace/WorkspaceActions'; +import WorkspaceActions from '../../../commons/workspace/WorkspaceActions'; import { WorkspaceLocation } from '../../../commons/workspace/WorkspaceTypes'; import { CodeDelta, @@ -129,19 +112,19 @@ const Sourcereel: React.FC = () => { } = useMemo(() => { return { handleChapterSelect: (chapter: Chapter) => - dispatch(chapterSelect(chapter, Variant.DEFAULT, workspaceLocation)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + dispatch(WorkspaceActions.chapterSelect(chapter, Variant.DEFAULT, workspaceLocation)), + handleEditorEval: () => dispatch(WorkspaceActions.evalEditor(workspaceLocation)), // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. handleEditorValueChange: (newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, 0, newEditorValue)), + dispatch(WorkspaceActions.updateEditorValue(workspaceLocation, 0, newEditorValue)), handleExternalSelect: (externalLibraryName: ExternalLibraryName) => - dispatch(externalLibrarySelect(externalLibraryName, workspaceLocation)), + dispatch(WorkspaceActions.externalLibrarySelect(externalLibraryName, workspaceLocation)), handleRecordInput: (input: Input) => dispatch(recordInput(input, workspaceLocation)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), + handleReplEval: () => dispatch(WorkspaceActions.evalRepl(workspaceLocation)), handleSetSourcecastStatus: (playbackStatus: PlaybackStatus) => dispatch(setSourcecastStatus(playbackStatus, sourcecastLocation)), handleSetIsEditorReadonly: (readonly: boolean) => - dispatch(setIsEditorReadonly(workspaceLocation, readonly)) + dispatch(WorkspaceActions.setIsEditorReadonly(workspaceLocation, readonly)) }; }, [dispatch]); @@ -208,7 +191,8 @@ const Sourcereel: React.FC = () => { handleDebuggerReset: () => dispatch(InterpreterActions.debuggerReset(workspaceLocation)), handleInterruptEval: () => dispatch(InterpreterActions.beginInterruptExecution(workspaceLocation)), - handleToggleEditorAutorun: () => dispatch(toggleEditorAutorun(workspaceLocation)) + handleToggleEditorAutorun: () => + dispatch(WorkspaceActions.toggleEditorAutorun(workspaceLocation)) }; }, [dispatch]); const autorunButtons = ( @@ -252,7 +236,7 @@ const Sourcereel: React.FC = () => { const clearButton = useMemo( () => ( dispatch(clearReplOutput(workspaceLocation))} + handleReplOutputClear={() => dispatch(WorkspaceActions.clearReplOutput(workspaceLocation))} key="clear_repl" /> ), @@ -266,14 +250,16 @@ const Sourcereel: React.FC = () => { const editorContainerHandlers = useMemo(() => { return { setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), + dispatch( + WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex) + ), removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)), + dispatch(WorkspaceActions.removeEditorTab(workspaceLocation, editorTabIndex)), handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), + dispatch(WorkspaceActions.navigateToDeclaration(workspaceLocation, cursorPosition)), // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. handleEditorUpdateBreakpoints: (newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, 0, newBreakpoints)) + dispatch(WorkspaceActions.setEditorBreakpoint(workspaceLocation, 0, newBreakpoints)) }; }, [dispatch]); const editorContainerProps: SourcecastEditorContainerProps = { @@ -315,10 +301,12 @@ const Sourcereel: React.FC = () => { const workspaceHandlers = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), + handleBrowseHistoryDown: () => + dispatch(WorkspaceActions.browseReplHistoryDown(workspaceLocation)), + handleBrowseHistoryUp: () => + dispatch(WorkspaceActions.browseReplHistoryUp(workspaceLocation)), handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)), + dispatch(WorkspaceActions.updateReplValue(newValue, workspaceLocation)), handleDeleteSourcecastEntry: (id: number) => dispatch(deleteSourcecastEntry(id, sourcecastLocation)), // SourcereelControlbar handlers @@ -450,7 +438,7 @@ const Sourcereel: React.FC = () => { handleSetSourcecastDuration: (duration: number) => dispatch(setSourcecastDuration(duration, sourcecastLocation)), handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)) + dispatch(WorkspaceActions.promptAutocomplete(workspaceLocation, row, col, callback)) }; }, [dispatch]); const sourcecastControlbarProps: SourceRecorderControlBarProps = { diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index 3acb2a317f..b7274483d1 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -30,34 +30,7 @@ import { showFulTSWarningOnUrlLoad, showHTMLDisclaimer } from 'src/commons/utils/WarningDialogHelper'; -import { - addEditorTab, - addHtmlConsoleError, - browseReplHistoryDown, - browseReplHistoryUp, - changeExecTime, - changeStepLimit, - chapterSelect, - clearReplOutput, - evalEditor, - evalRepl, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - removeEditorTabsForDirectory, - sendReplInputToOutput, - setEditorBreakpoint, - setEditorHighlightedLines, - setFolderMode, - toggleEditorAutorun, - toggleFolderMode, - toggleUpdateCse, - toggleUsingSubst, - updateActiveEditorTabIndex, - updateEditorValue, - updateReplValue, - uploadFiles -} from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions, { uploadFiles } from 'src/commons/workspace/WorkspaceActions'; import { WorkspaceLocation } from 'src/commons/workspace/WorkspaceTypes'; import GithubActions from 'src/features/github/GitHubActions'; import { @@ -176,12 +149,12 @@ export async function handleHash( // BrowserFS does not provide a way of listening to changes in the file system, which makes // updating the file system view troublesome. To force the file system view to re-render // (and thus display the updated file system), we first disable Folder mode. - dispatch(setFolderMode(workspaceLocation, false)); + dispatch(WorkspaceActions.setFolderMode(workspaceLocation, false)); const isFolderModeEnabled = convertParamToBoolean(qs.isFolder) ?? false; // If Folder mode should be enabled, enabling it after disabling it earlier will cause the // newly-added files to be shown. Note that this has to take place after the files are // already added to the file system. - dispatch(setFolderMode(workspaceLocation, isFolderModeEnabled)); + dispatch(WorkspaceActions.setFolderMode(workspaceLocation, isFolderModeEnabled)); // By default, open a single editor tab containing the default playground file. const editorTabFilePaths = qs.tabs?.split(',').map(decompressFromEncodedURIComponent) ?? [ @@ -189,17 +162,20 @@ export async function handleHash( ]; // Remove all editor tabs before populating with the ones from the query string. dispatch( - removeEditorTabsForDirectory(workspaceLocation, WORKSPACE_BASE_PATHS[workspaceLocation]) + WorkspaceActions.removeEditorTabsForDirectory( + workspaceLocation, + WORKSPACE_BASE_PATHS[workspaceLocation] + ) ); // Add editor tabs from the query string. editorTabFilePaths.forEach(filePath => // Fall back on the empty string if the file contents do not exist. - dispatch(addEditorTab(workspaceLocation, filePath, files[filePath] ?? '')) + dispatch(WorkspaceActions.addEditorTab(workspaceLocation, filePath, files[filePath] ?? '')) ); // By default, use the first editor tab. const activeEditorTabIndex = convertParamToInt(qs.tabIdx) ?? 0; - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)); + dispatch(WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)); if (chapter) { // TODO: To migrate the state logic away from playgroundSourceChapter // and playgroundSourceVariant into the language config instead @@ -271,17 +247,21 @@ const Playground: React.FC = props => { } = useMemo(() => { return { handleChangeExecTime: (execTime: number) => - dispatch(changeExecTime(execTime, workspaceLocation)), + dispatch(WorkspaceActions.changeExecTime(execTime, workspaceLocation)), handleChapterSelect: (chapter: Chapter, variant: Variant) => - dispatch(chapterSelect(chapter, variant, workspaceLocation)), + dispatch(WorkspaceActions.chapterSelect(chapter, variant, workspaceLocation)), handleEditorValueChange: (editorTabIndex: number, newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue)), + dispatch( + WorkspaceActions.updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue) + ), handleSetEditorBreakpoints: (editorTabIndex: number, newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), - handleReplOutputClear: () => dispatch(clearReplOutput(workspaceLocation)), + dispatch( + WorkspaceActions.setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints) + ), + handleReplEval: () => dispatch(WorkspaceActions.evalRepl(workspaceLocation)), + handleReplOutputClear: () => dispatch(WorkspaceActions.clearReplOutput(workspaceLocation)), handleUsingSubst: (usingSubst: boolean) => - dispatch(toggleUsingSubst(usingSubst, workspaceLocation)) + dispatch(WorkspaceActions.toggleUsingSubst(usingSubst, workspaceLocation)) }; }, [dispatch, workspaceLocation]); @@ -354,7 +334,7 @@ const Playground: React.FC = props => { dispatch(playgroundConfigLanguage(languageConfig)); // Disable Folder mode when forcing the Source chapter and variant to follow the current course's. // This is because Folder mode only works in Source 2+. - dispatch(setFolderMode(workspaceLocation, false)); + dispatch(WorkspaceActions.setFolderMode(workspaceLocation, false)); } return; } @@ -456,10 +436,11 @@ const Playground: React.FC = props => { const autorunButtonHandlers = useMemo(() => { return { - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + handleEditorEval: () => dispatch(WorkspaceActions.evalEditor(workspaceLocation)), handleInterruptEval: () => dispatch(InterpreterActions.beginInterruptExecution(workspaceLocation)), - handleToggleEditorAutorun: () => dispatch(toggleEditorAutorun(workspaceLocation)), + handleToggleEditorAutorun: () => + dispatch(WorkspaceActions.toggleEditorAutorun(workspaceLocation)), handleDebuggerPause: () => dispatch(InterpreterActions.beginDebuggerPause(workspaceLocation)), handleDebuggerReset: () => dispatch(InterpreterActions.debuggerReset(workspaceLocation)), handleDebuggerResume: () => dispatch(InterpreterActions.debuggerResume(workspaceLocation)) @@ -632,14 +613,14 @@ const Playground: React.FC = props => { stepLimit={stepLimit} stepSize={usingSubst ? 2 : 1} handleChangeStepLimit={limit => { - dispatch(changeStepLimit(limit, workspaceLocation)); - usingCse && dispatch(toggleUpdateCse(true, workspaceLocation)); + dispatch(WorkspaceActions.changeStepLimit(limit, workspaceLocation)); + usingCse && dispatch(WorkspaceActions.toggleUpdateCse(true, workspaceLocation)); }} handleOnBlurAutoScale={limit => { limit % 2 === 0 || !usingSubst - ? dispatch(changeStepLimit(limit, workspaceLocation)) - : dispatch(changeStepLimit(limit + 1, workspaceLocation)); - usingCse && dispatch(toggleUpdateCse(true, workspaceLocation)); + ? dispatch(WorkspaceActions.changeStepLimit(limit, workspaceLocation)) + : dispatch(WorkspaceActions.changeStepLimit(limit + 1, workspaceLocation)); + usingCse && dispatch(WorkspaceActions.toggleUpdateCse(true, workspaceLocation)); }} key="step_limit" /> @@ -696,7 +677,7 @@ const Playground: React.FC = props => { isFolderModeEnabled={isFolderModeEnabled} isSessionActive={editorSessionId !== ''} isPersistenceActive={persistenceFile !== undefined || githubSaveInfo.repoName !== ''} - toggleFolderMode={() => dispatch(toggleFolderMode(workspaceLocation))} + toggleFolderMode={() => dispatch(WorkspaceActions.toggleFolderMode(workspaceLocation))} key="folder" /> ); @@ -736,7 +717,7 @@ const Playground: React.FC = props => { tabs.push( makeHtmlDisplayTabFrom( output[0] as ResultOutput, - errorMsg => dispatch(addHtmlConsoleError(errorMsg, workspaceLocation)), + errorMsg => dispatch(WorkspaceActions.addHtmlConsoleError(errorMsg, workspaceLocation)), workspaceLocation ) ); @@ -804,8 +785,8 @@ const Playground: React.FC = props => { }; pushLog(input); - dispatch(toggleUpdateCse(true, workspaceLocation)); - dispatch(setEditorHighlightedLines(workspaceLocation, 0, [])); + dispatch(WorkspaceActions.toggleUpdateCse(true, workspaceLocation)); + dispatch(WorkspaceActions.setEditorHighlightedLines(workspaceLocation, 0, [])); }, [pushLog, dispatch, workspaceLocation] ); @@ -864,7 +845,7 @@ const Playground: React.FC = props => { } } handleSetEditorBreakpoints(editorTabIndex, breakpoints); - dispatch(toggleUpdateCse(true, workspaceLocation)); + dispatch(WorkspaceActions.toggleUpdateCse(true, workspaceLocation)); }, [ selectedTab, @@ -882,17 +863,19 @@ const Playground: React.FC = props => { const editorContainerHandlers = useMemo(() => { return { handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), + dispatch(WorkspaceActions.navigateToDeclaration(workspaceLocation, cursorPosition)), handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)), + dispatch(WorkspaceActions.promptAutocomplete(workspaceLocation, row, col, callback)), handleSendReplInputToOutput: (code: string) => - dispatch(sendReplInputToOutput(code, workspaceLocation)), + dispatch(WorkspaceActions.sendReplInputToOutput(code, workspaceLocation)), handleSetSharedbConnected: (connected: boolean) => dispatch(setSharedbConnected(workspaceLocation, connected)), setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), + dispatch( + WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex) + ), removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)) + dispatch(WorkspaceActions.removeEditorTab(workspaceLocation, editorTabIndex)) }; }, [dispatch, workspaceLocation]); const editorContainerProps: NormalEditorContainerProps = { @@ -924,10 +907,12 @@ const Playground: React.FC = props => { const replHandlers = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), + handleBrowseHistoryDown: () => + dispatch(WorkspaceActions.browseReplHistoryDown(workspaceLocation)), + handleBrowseHistoryUp: () => + dispatch(WorkspaceActions.browseReplHistoryUp(workspaceLocation)), handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)) + dispatch(WorkspaceActions.updateReplValue(newValue, workspaceLocation)) }; }, [dispatch, workspaceLocation]); const replProps = { diff --git a/src/pages/sourcecast/Sourcecast.tsx b/src/pages/sourcecast/Sourcecast.tsx index b5d4f7e274..70c9353004 100644 --- a/src/pages/sourcecast/Sourcecast.tsx +++ b/src/pages/sourcecast/Sourcecast.tsx @@ -10,24 +10,7 @@ import { Position } from 'src/commons/editor/EditorTypes'; import { changeSideContentHeight } from 'src/commons/sideContent/SideContentActions'; import { useSideContent } from 'src/commons/sideContent/SideContentHelper'; import { useResponsive, useTypedSelector } from 'src/commons/utils/Hooks'; -import { - browseReplHistoryDown, - browseReplHistoryUp, - chapterSelect, - clearReplOutput, - evalEditor, - evalRepl, - externalLibrarySelect, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - setEditorBreakpoint, - setIsEditorReadonly, - toggleEditorAutorun, - updateActiveEditorTabIndex, - updateEditorValue, - updateReplValue -} from 'src/commons/workspace/WorkspaceActions'; +import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; import { WorkspaceLocation } from 'src/commons/workspace/WorkspaceTypes'; import { fetchSourcecastIndex } from 'src/features/sourceRecorder/sourcecast/SourcecastActions'; import { @@ -115,14 +98,14 @@ const Sourcecast: React.FC = () => { return { handleFetchSourcecastIndex: () => dispatch(fetchSourcecastIndex(workspaceLocation)), handleChapterSelect: (chapter: Chapter) => - dispatch(chapterSelect(chapter, Variant.DEFAULT, workspaceLocation)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + dispatch(WorkspaceActions.chapterSelect(chapter, Variant.DEFAULT, workspaceLocation)), + handleEditorEval: () => dispatch(WorkspaceActions.evalEditor(workspaceLocation)), // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. handleEditorValueChange: (newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, 0, newEditorValue)), + dispatch(WorkspaceActions.updateEditorValue(workspaceLocation, 0, newEditorValue)), handleExternalSelect: (externalLibraryName: ExternalLibraryName) => - dispatch(externalLibrarySelect(externalLibraryName, workspaceLocation)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), + dispatch(WorkspaceActions.externalLibrarySelect(externalLibraryName, workspaceLocation)), + handleReplEval: () => dispatch(WorkspaceActions.evalRepl(workspaceLocation)), handleSetSourcecastData: ( title: string, description: string, @@ -135,7 +118,7 @@ const Sourcecast: React.FC = () => { ), handleSetSourcecastStatus: (playbackStatus: PlaybackStatus) => dispatch(setSourcecastStatus(playbackStatus, workspaceLocation)), - handleReplOutputClear: () => dispatch(clearReplOutput(workspaceLocation)), + handleReplOutputClear: () => dispatch(WorkspaceActions.clearReplOutput(workspaceLocation)), handleSideContentHeightChange: (change: number) => dispatch(changeSideContentHeight(change, workspaceLocation)) }; @@ -218,7 +201,8 @@ const Sourcecast: React.FC = () => { handleDebuggerResume: () => dispatch(InterpreterActions.debuggerResume(workspaceLocation)), handleInterruptEval: () => dispatch(InterpreterActions.beginInterruptExecution(workspaceLocation)), - handleToggleEditorAutorun: () => dispatch(toggleEditorAutorun(workspaceLocation)) + handleToggleEditorAutorun: () => + dispatch(WorkspaceActions.toggleEditorAutorun(workspaceLocation)) }; }, [dispatch]); const autorunButtons = ( @@ -298,14 +282,16 @@ const Sourcecast: React.FC = () => { const editorContainerHandlers = useMemo(() => { return { handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), + dispatch(WorkspaceActions.navigateToDeclaration(workspaceLocation, cursorPosition)), // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. handleEditorUpdateBreakpoints: (newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, 0, newBreakpoints)), + dispatch(WorkspaceActions.setEditorBreakpoint(workspaceLocation, 0, newBreakpoints)), setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), + dispatch( + WorkspaceActions.updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex) + ), removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)) + dispatch(WorkspaceActions.removeEditorTab(workspaceLocation, editorTabIndex)) }; }, [dispatch]); const editorContainerProps: SourcecastEditorContainerProps = { @@ -329,10 +315,12 @@ const Sourcecast: React.FC = () => { const replHandlers = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), + handleBrowseHistoryDown: () => + dispatch(WorkspaceActions.browseReplHistoryDown(workspaceLocation)), + handleBrowseHistoryUp: () => + dispatch(WorkspaceActions.browseReplHistoryUp(workspaceLocation)), handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)) + dispatch(WorkspaceActions.updateReplValue(newValue, workspaceLocation)) }; }, [dispatch]); const replProps = { @@ -391,13 +379,13 @@ const Sourcecast: React.FC = () => { const sourcecastControlbarHandlers = useMemo(() => { return { handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)), + dispatch(WorkspaceActions.promptAutocomplete(workspaceLocation, row, col, callback)), handleSetCurrentPlayerTime: (playerTime: number) => dispatch(setCurrentPlayerTime(playerTime, workspaceLocation)), handleSetCodeDeltasToApply: (deltas: CodeDelta[]) => dispatch(setCodeDeltasToApply(deltas, workspaceLocation)), handleSetIsEditorReadonly: (editorReadonly: boolean) => - dispatch(setIsEditorReadonly(workspaceLocation, editorReadonly)), + dispatch(WorkspaceActions.setIsEditorReadonly(workspaceLocation, editorReadonly)), handleSetInputToApply: (inputToApply: Input) => dispatch(setInputToApply(inputToApply, workspaceLocation)), handleSetSourcecastDuration: (duration: number) =>