diff --git a/package.json b/package.json index 8d443213af..d547789153 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@blueprintjs/popover2": "^1.14.11", "@blueprintjs/select": "^4.9.24", "@octokit/rest": "^19.0.11", + "@reduxjs/toolkit": "^1.9.5", "@sentry/browser": "^7.57.0", "@sourceacademy/sharedb-ace": "^2.0.2", "@sourceacademy/sling-client": "^0.1.0", @@ -132,6 +133,7 @@ "eslint-plugin-simple-import-sort": "^7.0.0", "https-browserify": "^1.0.0", "husky": "^8.0.2", + "madge": "^6.1.0", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", diff --git a/src/commons/XMLParser/XMLParserHelper.ts b/src/commons/XMLParser/XMLParserHelper.ts index fbf9385575..c6985f8d41 100644 --- a/src/commons/XMLParser/XMLParserHelper.ts +++ b/src/commons/XMLParser/XMLParserHelper.ts @@ -1,7 +1,6 @@ import { Chapter } from 'js-slang/dist/types'; import { Builder } from 'xml2js'; -import { ExternalLibraryName } from '../application/types/ExternalTypes'; import { Assessment, AssessmentOverview, @@ -118,7 +117,7 @@ const makeLibrary = (deploymentArr: XmlParseStrDeployment[] | undefined): Librar } else { const deployment = deploymentArr[0]; const external = deployment.IMPORT || deployment.EXTERNAL; - const nameVal = external ? external[0].$.name : 'NONE'; + // const nameVal = external ? external[0].$.name : 'NONE'; const symbolsVal = external ? external[0].SYMBOL || [] : []; const globalsVal = deployment.GLOBAL ? (deployment.GLOBAL.map(x => [x.IDENTIFIER[0], altEval(x.VALUE[0]), x.VALUE[0]]) as Array< @@ -128,7 +127,7 @@ const makeLibrary = (deploymentArr: XmlParseStrDeployment[] | undefined): Librar return { chapter: parseInt(deployment.$.interpreter, 10) as Chapter, external: { - name: nameVal as ExternalLibraryName, + // name: nameVal as ExternalLibraryName, symbols: symbolsVal }, globals: globalsVal @@ -262,7 +261,7 @@ const exportLibrary = (library: Library) => { }, EXTERNAL: { $: { - name: library.external.name + // name: library.external.name } } }; diff --git a/src/commons/application/ApplicationReducer.ts b/src/commons/application/ApplicationReducer.ts deleted file mode 100644 index 548669192c..0000000000 --- a/src/commons/application/ApplicationReducer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Action, Reducer } from 'redux'; - -import { ApplicationState, defaultApplication } from './ApplicationTypes'; - -export const ApplicationReducer: Reducer = ( - state = defaultApplication, - action: Action -) => { - return state; -}; diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index 1817a8384f..9faf8e7df7 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -1,44 +1,5 @@ import { Chapter, Language, SourceError, Variant } from 'js-slang/dist/types'; -import { AcademyState } from '../../features/academy/AcademyTypes'; -import { AchievementState } from '../../features/achievement/AchievementTypes'; -import { DashboardState } from '../../features/dashboard/DashboardTypes'; -import { Grading } from '../../features/grading/GradingTypes'; -import { PlaygroundState } from '../../features/playground/PlaygroundTypes'; -import { PlaybackStatus, RecordingStatus } from '../../features/sourceRecorder/SourceRecorderTypes'; -import { StoriesEnvState, StoriesState } from '../../features/stories/StoriesTypes'; -import { WORKSPACE_BASE_PATHS } from '../../pages/fileSystem/createInBrowserFileSystem'; -import { Assessment } from '../assessment/AssessmentTypes'; -import { FileSystemState } from '../fileSystem/FileSystemTypes'; -import Constants from '../utils/Constants'; -import { createContext } from '../utils/JsSlangHelper'; -import { - DebuggerContext, - WorkspaceLocation, - WorkspaceManagerState, - WorkspaceState -} from '../workspace/WorkspaceTypes'; -import { RouterState } from './types/CommonsTypes'; -import { ExternalLibraryName } from './types/ExternalTypes'; -import { SessionState } from './types/SessionTypes'; - -export type OverallState = { - readonly router: RouterState; - readonly academy: AcademyState; - readonly achievement: AchievementState; - readonly application: ApplicationState; - readonly playground: PlaygroundState; - readonly session: SessionState; - readonly stories: StoriesState; - readonly workspaces: WorkspaceManagerState; - readonly dashboard: DashboardState; - readonly fileSystem: FileSystemState; -}; - -export type ApplicationState = { - readonly environment: ApplicationEnvironment; -}; - export type Story = { story: string; playStory: boolean; @@ -95,25 +56,12 @@ export type ErrorOutput = { export type InterpreterOutput = RunningOutput | CodeOutput | ResultOutput | ErrorOutput; -export enum ApplicationEnvironment { - Development = 'development', - Production = 'production', - Test = 'test' -} - export enum Role { Student = 'student', Staff = 'staff', Admin = 'admin' } -// Must match https://github.com/source-academy/stories-backend/blob/main/internal/enums/groups/role.go -export enum StoriesRole { - Standard = 'member', - Moderator = 'moderator', - Admin = 'admin' -} - export enum SupportedLanguage { JAVASCRIPT = 'JavaScript', SCHEME = 'Scheme', @@ -288,273 +236,167 @@ export const getLanguageConfig = ( return languageConfig; }; -const currentEnvironment = (): ApplicationEnvironment => { - switch (process.env.NODE_ENV) { - case 'development': - return ApplicationEnvironment.Development; - case 'production': - return ApplicationEnvironment.Production; - default: - return ApplicationEnvironment.Test; - } -}; - -export const defaultRouter: RouterState = null; - -export const defaultAcademy: AcademyState = { - gameCanvas: undefined -}; - -export const defaultApplication: ApplicationState = { - environment: currentEnvironment() -}; - -export const defaultDashboard: DashboardState = { - gradingSummary: { - cols: [], - rows: [] - } -}; - -export const defaultAchievement: AchievementState = { - achievements: [], - goals: [], - users: [], - assessmentOverviews: [] -}; - -const getDefaultLanguageConfig = (): SALanguage => { - const languageConfig = ALL_LANGUAGES.find( - sublang => - sublang.chapter === Constants.defaultSourceChapter && - sublang.variant === Constants.defaultSourceVariant - ); - if (!languageConfig) { - throw new Error('Cannot find language config to match default chapter and variant'); - } - return languageConfig; -}; -export const defaultLanguageConfig: SALanguage = getDefaultLanguageConfig(); - -export const defaultPlayground: PlaygroundState = { - githubSaveInfo: { repoName: '', filePath: '' }, - languageConfig: defaultLanguageConfig -}; - -export const defaultEditorValue = '// Type your program in here!'; - -/** - * Create a default IWorkspaceState for 'resetting' a workspace. - * Takes in parameters to set the js-slang library and chapter. - * - * @param workspaceLocation the location of the workspace, used for context - */ -export const createDefaultWorkspace = (workspaceLocation: WorkspaceLocation): WorkspaceState => ({ - autogradingResults: [], - context: createContext( - Constants.defaultSourceChapter, - [], - workspaceLocation, - Constants.defaultSourceVariant - ), - isFolderModeEnabled: false, - activeEditorTabIndex: 0, - editorTabs: [ - { - filePath: ['playground', 'sicp'].includes(workspaceLocation) - ? getDefaultFilePath(workspaceLocation) - : undefined, - value: ['playground', 'sourcecast', 'githubAssessments'].includes(workspaceLocation) - ? defaultEditorValue - : '', - highlightedLines: [], - breakpoints: [] - } - ], - programPrependValue: '', - programPostpendValue: '', - editorSessionId: '', - isEditorReadonly: false, - editorTestcases: [], - externalLibrary: ExternalLibraryName.NONE, - execTime: 1000, - output: [], - replHistory: { - browseIndex: null, - records: [], - originalValue: '' - }, - replValue: '', - sharedbConnected: false, - stepLimit: 1000, - globals: [], - isEditorAutorun: false, - isRunning: false, - isDebugging: false, - enableDebugging: true, - debuggerContext: {} as DebuggerContext -}); - -const defaultFileName = 'program.js'; -export const getDefaultFilePath = (workspaceLocation: WorkspaceLocation) => - `${WORKSPACE_BASE_PATHS[workspaceLocation]}/${defaultFileName}`; - -export const defaultWorkspaceManager: WorkspaceManagerState = { - assessment: { - ...createDefaultWorkspace('assessment'), - currentAssessment: undefined, - currentQuestion: undefined, - hasUnsavedChanges: false - }, - grading: { - ...createDefaultWorkspace('grading'), - submissionsTableFilters: { - columnFilters: [], - globalFilter: null - }, - currentSubmission: undefined, - currentQuestion: undefined, - hasUnsavedChanges: false - }, - playground: { - ...createDefaultWorkspace('playground'), - usingSubst: false, - usingEnv: false, - updateEnv: true, - envSteps: -1, - envStepsTotal: 0, - breakpointSteps: [], - activeEditorTabIndex: 0, - editorTabs: [ - { - filePath: getDefaultFilePath('playground'), - value: defaultEditorValue, - highlightedLines: [], - breakpoints: [] - } - ] - }, - sourcecast: { - ...createDefaultWorkspace('sourcecast'), - audioUrl: '', - codeDeltasToApply: null, - currentPlayerTime: 0, - description: null, - inputToApply: null, - playbackData: { - init: { - editorValue: '', - chapter: Chapter.SOURCE_1, - externalLibrary: ExternalLibraryName.NONE - }, - inputs: [] - }, - playbackDuration: 0, - playbackStatus: PlaybackStatus.paused, - sourcecastIndex: null, - title: null, - uid: null - }, - sourcereel: { - ...createDefaultWorkspace('sourcereel'), - playbackData: { - init: { - editorValue: '', - chapter: Chapter.SOURCE_1, - externalLibrary: ExternalLibraryName.NONE - }, - inputs: [] - }, - recordingStatus: RecordingStatus.notStarted, - timeElapsedBeforePause: 0, - timeResumed: 0 - }, - sicp: { - ...createDefaultWorkspace('sicp'), - usingSubst: false, - usingEnv: false, - updateEnv: true, - envSteps: -1, - envStepsTotal: 0, - breakpointSteps: [], - activeEditorTabIndex: 0, - editorTabs: [ - { - filePath: getDefaultFilePath('sicp'), - value: defaultEditorValue, - highlightedLines: [], - breakpoints: [] - } - ] - }, - githubAssessment: { - ...createDefaultWorkspace('githubAssessment'), - hasUnsavedChanges: false - }, - stories: { - ...createDefaultWorkspace('stories') - // TODO: Perhaps we can add default values? - } -}; - -export const defaultSession: SessionState = { - courses: [], - group: null, - gameState: { - completed_quests: [], - collectibles: {} - }, - xp: 0, - allUserXp: undefined, - story: { - story: '', - playStory: false - }, - assessments: new Map(), - assessmentOverviews: undefined, - agreedToResearch: undefined, - sessionId: Date.now(), - githubOctokitObject: { octokit: undefined }, - gradingOverviews: undefined, - gradings: new Map(), - notifications: [] -}; - -export const defaultStories: StoriesState = { - storyList: [], - currentStoryId: null, - currentStory: null, - envs: {} -}; - -export const createDefaultStoriesEnv = ( - envName: string, - chapter: Chapter, - variant: Variant -): StoriesEnvState => ({ - context: createContext(chapter, [], envName, variant), - execTime: 1000, - isRunning: false, - output: [], - stepLimit: 1000, - globals: [], - usingSubst: false, - debuggerContext: {} as DebuggerContext -}); - -export const defaultFileSystem: FileSystemState = { - inBrowserFileSystem: null -}; - -export const defaultState: OverallState = { - router: defaultRouter, - academy: defaultAcademy, - achievement: defaultAchievement, - application: defaultApplication, - dashboard: defaultDashboard, - playground: defaultPlayground, - session: defaultSession, - stories: defaultStories, - workspaces: defaultWorkspaceManager, - fileSystem: defaultFileSystem -}; +// /** +// * Create a default IWorkspaceState for 'resetting' a workspace. +// * Takes in parameters to set the js-slang library and chapter. +// * +// * @param workspaceLocation the location of the workspace, used for context +// */ +// export const createDefaultWorkspace = (workspaceLocation: WorkspaceLocation): WorkspaceState => ({ +// autogradingResults: [], +// context: createContext( +// Constants.defaultSourceChapter, +// [], +// workspaceLocation, +// Constants.defaultSourceVariant +// ), +// isFolderModeEnabled: false, +// activeEditorTabIndex: 0, +// editorTabs: [ +// { +// filePath: ['playground', 'sicp'].includes(workspaceLocation) +// ? getDefaultFilePath(workspaceLocation) +// : undefined, +// value: ['playground', 'sourcecast', 'githubAssessments'].includes(workspaceLocation) +// ? defaultEditorValue +// : '', +// highlightedLines: [], +// breakpoints: [] +// } +// ], +// programPrependValue: '', +// programPostpendValue: '', +// editorSessionId: '', +// isEditorReadonly: false, +// editorTestcases: [], +// externalLibrary: ExternalLibraryName.NONE, +// execTime: 1000, +// output: [], +// replHistory: { +// browseIndex: null, +// records: [], +// originalValue: '' +// }, +// replValue: '', +// sharedbConnected: false, +// stepLimit: 1000, +// globals: [], +// isEditorAutorun: false, +// isRunning: false, +// isDebugging: false, +// enableDebugging: true, +// debuggerContext: {} as DebuggerContext +// }); + +// export const defaultWorkspaceManager: WorkspaceManagerState2 = { +// assessment: { +// ...createDefaultWorkspace('assessment'), +// currentAssessment: undefined, +// currentQuestion: undefined, +// hasUnsavedChanges: false +// }, +// grading: { +// ...createDefaultWorkspace('grading'), +// submissionsTableFilters: { +// columnFilters: [], +// globalFilter: null +// }, +// currentSubmission: undefined, +// currentQuestion: undefined, +// hasUnsavedChanges: false +// }, +// playground: { +// ...createDefaultWorkspace('playground'), +// usingSubst: false, +// usingEnv: false, +// updateEnv: true, +// envSteps: -1, +// envStepsTotal: 0, +// breakpointSteps: [], +// activeEditorTabIndex: 0, +// editorTabs: [ +// { +// filePath: getDefaultFilePath('playground'), +// value: defaultEditorValue, +// highlightedLines: [], +// breakpoints: [] +// } +// ] +// }, +// sourcecast: { +// ...createDefaultWorkspace('sourcecast'), +// audioUrl: '', +// codeDeltasToApply: null, +// currentPlayerTime: 0, +// description: null, +// inputToApply: null, +// playbackData: { +// init: { +// editorValue: '', +// chapter: Chapter.SOURCE_1, +// externalLibrary: ExternalLibraryName.NONE +// }, +// inputs: [] +// }, +// playbackDuration: 0, +// playbackStatus: PlaybackStatus.paused, +// sourcecastIndex: null, +// title: null, +// uid: null +// }, +// sourcereel: { +// ...createDefaultWorkspace('sourcereel'), +// playbackData: { +// init: { +// editorValue: '', +// chapter: Chapter.SOURCE_1, +// externalLibrary: ExternalLibraryName.NONE +// }, +// inputs: [] +// }, +// recordingStatus: RecordingStatus.notStarted, +// timeElapsedBeforePause: 0, +// timeResumed: 0 +// }, +// sicp: { +// ...createDefaultWorkspace('sicp'), +// usingSubst: false, +// usingEnv: false, +// updateEnv: true, +// envSteps: -1, +// envStepsTotal: 0, +// breakpointSteps: [], +// activeEditorTabIndex: 0, +// editorTabs: [ +// { +// filePath: getDefaultFilePath('sicp'), +// value: defaultEditorValue, +// highlightedLines: [], +// breakpoints: [] +// } +// ] +// }, +// githubAssessment: { +// ...createDefaultWorkspace('githubAssessment'), +// hasUnsavedChanges: false +// }, +// stories: { +// ...createDefaultWorkspace('stories') +// // TODO: Perhaps we can add default values? +// } +// }; + +// export const createDefaultStoriesEnv = ( +// envName: string, +// chapter: Chapter, +// variant: Variant +// ): StoriesEnvState => ({ +// context: createContext(chapter, [], envName, variant), +// execTime: 1000, +// isRunning: false, +// output: [], +// stepLimit: 1000, +// globals: [], +// usingSubst: false, +// debuggerContext: {} as DebuggerContext +// }); diff --git a/src/commons/application/ApplicationWrapper.tsx b/src/commons/application/ApplicationWrapper.tsx index 5f023eab55..d6a08b56e2 100644 --- a/src/commons/application/ApplicationWrapper.tsx +++ b/src/commons/application/ApplicationWrapper.tsx @@ -4,9 +4,9 @@ import { RouterProvider } from 'react-router'; import { createBrowserRouter } from 'react-router-dom'; import { getFullAcademyRouterConfig, playgroundOnlyRouterConfig } from '../../routes/routerConfig'; +import { actions } from '../redux/ActionsHelper'; import Constants from '../utils/Constants'; import { useSession } from '../utils/Hooks'; -import { updateReactRouter } from './actions/CommonsActions'; /** * Application wrapper component which figures out which deployment and set of routes to render. @@ -31,7 +31,7 @@ const ApplicationWrapper: React.FC = () => { }); const r = createBrowserRouter(routerConfig); - dispatch(updateReactRouter(r)); + dispatch(actions.updateReactRouter(r)); return r; }, [isLoggedIn, role, name, courseId, dispatch]); diff --git a/src/commons/application/__tests__/ApplicationReducer.ts b/src/commons/application/__tests__/ApplicationReducer.ts index 71ec053ba0..0effe73067 100644 --- a/src/commons/application/__tests__/ApplicationReducer.ts +++ b/src/commons/application/__tests__/ApplicationReducer.ts @@ -1,6 +1,6 @@ -import { ApplicationReducer } from '../ApplicationReducer'; // EDITED +import { applicationReducer } from '../../redux/ApplicationRedux'; -const initialState = ApplicationReducer(undefined!, { type: '*' }); +const initialState = applicationReducer(undefined!, { type: '*' }); test('initial state should match a snapshot', () => { expect(initialState).toMatchSnapshot(); diff --git a/src/commons/application/actions/InterpreterActions.ts b/src/commons/application/actions/InterpreterActions.ts deleted file mode 100644 index 95c48356ef..0000000000 --- a/src/commons/application/actions/InterpreterActions.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { SourceError, Value } from 'js-slang/dist/types'; -import { action } from 'typesafe-actions'; - -import { WorkspaceLocation } from '../../workspace/WorkspaceTypes'; -import { - BEGIN_DEBUG_PAUSE, - BEGIN_INTERRUPT_EXECUTION, - DEBUG_RESET, - DEBUG_RESUME, - END_DEBUG_PAUSE, - END_INTERRUPT_EXECUTION, - EVAL_INTERPRETER_ERROR, - EVAL_INTERPRETER_SUCCESS, - EVAL_TESTCASE_FAILURE, - EVAL_TESTCASE_SUCCESS, - HANDLE_CONSOLE_LOG -} from '../types/InterpreterTypes'; - -export const handleConsoleLog = (workspaceLocation: WorkspaceLocation, ...logString: string[]) => - action(HANDLE_CONSOLE_LOG, { logString, workspaceLocation }); - -export const evalInterpreterSuccess = (value: Value, workspaceLocation: WorkspaceLocation) => - action(EVAL_INTERPRETER_SUCCESS, { type: 'result', value, workspaceLocation }); - -export const evalTestcaseSuccess = ( - value: Value, - workspaceLocation: WorkspaceLocation, - index: number -) => action(EVAL_TESTCASE_SUCCESS, { type: 'result', value, workspaceLocation, index }); - -export const evalTestcaseFailure = ( - value: Value, - workspaceLocation: WorkspaceLocation, - index: number -) => action(EVAL_TESTCASE_FAILURE, { type: 'errors', value, workspaceLocation, index }); - -export const evalInterpreterError = (errors: SourceError[], workspaceLocation: WorkspaceLocation) => - action(EVAL_INTERPRETER_ERROR, { type: 'errors', errors, workspaceLocation }); - -export const beginInterruptExecution = (workspaceLocation: WorkspaceLocation) => - action(BEGIN_INTERRUPT_EXECUTION, { workspaceLocation }); - -export const endInterruptExecution = (workspaceLocation: WorkspaceLocation) => - action(END_INTERRUPT_EXECUTION, { workspaceLocation }); - -export const beginDebuggerPause = (workspaceLocation: WorkspaceLocation) => - action(BEGIN_DEBUG_PAUSE, { workspaceLocation }); - -export const endDebuggerPause = (workspaceLocation: WorkspaceLocation) => - action(END_DEBUG_PAUSE, { workspaceLocation }); - -export const debuggerResume = (workspaceLocation: WorkspaceLocation) => - action(DEBUG_RESUME, { workspaceLocation }); - -export const debuggerReset = (workspaceLocation: WorkspaceLocation) => - action(DEBUG_RESET, { workspaceLocation }); diff --git a/src/commons/application/actions/SessionActions.ts b/src/commons/application/actions/SessionActions.ts index 3f86c93269..1b237cf78d 100644 --- a/src/commons/application/actions/SessionActions.ts +++ b/src/commons/application/actions/SessionActions.ts @@ -1,23 +1,13 @@ import { action } from 'typesafe-actions'; // EDITED -import { Grading, GradingOverview } from '../../../features/grading/GradingTypes'; -import { - Assessment, - AssessmentConfiguration, - AssessmentOverview, - ContestEntry -} from '../../assessment/AssessmentTypes'; -import { MissionRepoData } from '../../githubAssessments/GitHubMissionTypes'; +import { AssessmentConfiguration, ContestEntry } from '../../assessment/AssessmentTypes'; import { Notification, NotificationFilterFunction } from '../../notificationBadge/NotificationBadgeTypes'; -import { generateOctokitInstance } from '../../utils/GitHubPersistenceHelper'; import { Role } from '../ApplicationTypes'; import { ACKNOWLEDGE_NOTIFICATIONS, - AdminPanelCourseRegistration, - CourseRegistration, DELETE_ASSESSMENT_CONFIG, DELETE_TIME_OPTIONS, DELETE_USER_COURSE_REGISTRATION, @@ -45,43 +35,22 @@ import { NotificationPreference, REAUTOGRADE_ANSWER, REAUTOGRADE_SUBMISSION, - REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN, - SET_ADMIN_PANEL_COURSE_REGISTRATIONS, - SET_ASSESSMENT_CONFIGURATIONS, - SET_CONFIGURABLE_NOTIFICATION_CONFIGS, - SET_COURSE_CONFIGURATION, - SET_COURSE_REGISTRATION, - SET_GITHUB_ACCESS_TOKEN, - SET_GITHUB_ASSESSMENT, - SET_GITHUB_OCTOKIT_OBJECT, - SET_GOOGLE_USER, - SET_NOTIFICATION_CONFIGS, - SET_TOKENS, - SET_USER, SUBMIT_ANSWER, SUBMIT_ASSESSMENT, SUBMIT_GRADING, SUBMIT_GRADING_AND_CONTINUE, TimeOption, - Tokens, UNSUBMIT_SUBMISSION, - UPDATE_ALL_USER_XP, - UPDATE_ASSESSMENT, UPDATE_ASSESSMENT_CONFIGS, - UPDATE_ASSESSMENT_OVERVIEWS, UPDATE_COURSE_CONFIG, UPDATE_COURSE_RESEARCH_AGREEMENT, - UPDATE_GRADING, - UPDATE_GRADING_OVERVIEWS, UPDATE_LATEST_VIEWED_COURSE, UPDATE_NOTIFICATION_CONFIG, UPDATE_NOTIFICATION_PREFERENCES, UPDATE_NOTIFICATIONS, UPDATE_TIME_OPTIONS, - UPDATE_TOTAL_XP, UPDATE_USER_ROLE, - UpdateCourseConfiguration, - User + UpdateCourseConfiguration } from '../types/SessionTypes'; export const fetchAuth = (code: string, providerId?: string) => @@ -121,48 +90,6 @@ export const loginGitHub = () => action(LOGIN_GITHUB); export const logoutGitHub = () => action(LOGOUT_GITHUB); -export const setTokens = ({ accessToken, refreshToken }: Tokens) => - action(SET_TOKENS, { - accessToken, - refreshToken - }); - -export const setUser = (user: User) => action(SET_USER, user); - -export const setCourseConfiguration = (courseConfiguration: UpdateCourseConfiguration) => - action(SET_COURSE_CONFIGURATION, courseConfiguration); - -export const setCourseRegistration = (courseRegistration: Partial) => - action(SET_COURSE_REGISTRATION, courseRegistration); - -export const setAssessmentConfigurations = (assessmentConfigurations: AssessmentConfiguration[]) => - action(SET_ASSESSMENT_CONFIGURATIONS, assessmentConfigurations); - -export const setConfigurableNotificationConfigs = ( - notificationConfigs: NotificationConfiguration[] -) => action(SET_CONFIGURABLE_NOTIFICATION_CONFIGS, notificationConfigs); - -export const setNotificationConfigs = (notificationConfigs: NotificationConfiguration[]) => - action(SET_NOTIFICATION_CONFIGS, notificationConfigs); - -export const setAdminPanelCourseRegistrations = ( - courseRegistrations: AdminPanelCourseRegistration[] -) => action(SET_ADMIN_PANEL_COURSE_REGISTRATIONS, courseRegistrations); - -export const setGoogleUser = (user?: string) => action(SET_GOOGLE_USER, user); - -export const setGitHubAssessment = (missionRepoData: MissionRepoData) => - action(SET_GITHUB_ASSESSMENT, missionRepoData); - -export const setGitHubOctokitObject = (authToken?: string) => - action(SET_GITHUB_OCTOKIT_OBJECT, generateOctokitInstance(authToken || '')); - -export const setGitHubAccessToken = (authToken?: string) => - action(SET_GITHUB_ACCESS_TOKEN, authToken); - -export const removeGitHubOctokitObjectAndAccessToken = () => - action(REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN); - export const submitAnswer = (id: number, answer: string | number | ContestEntry[]) => action(SUBMIT_ANSWER, { id, @@ -203,28 +130,6 @@ export const reautogradeSubmission = (submissionId: number) => export const reautogradeAnswer = (submissionId: number, questionId: number) => action(REAUTOGRADE_ANSWER, { submissionId, questionId }); -export const updateAssessmentOverviews = (overviews: AssessmentOverview[]) => - action(UPDATE_ASSESSMENT_OVERVIEWS, overviews); - -export const updateTotalXp = (totalXp: number) => action(UPDATE_TOTAL_XP, totalXp); - -export const updateAllUserXp = (allUserXp: string[][]) => action(UPDATE_ALL_USER_XP, allUserXp); - -export const updateAssessment = (assessment: Assessment) => action(UPDATE_ASSESSMENT, assessment); - -export const updateGradingOverviews = (overviews: GradingOverview[]) => - action(UPDATE_GRADING_OVERVIEWS, overviews); - -/** - * An extra id parameter is included here because of - * no id for Grading. - */ -export const updateGrading = (submissionId: number, grading: Grading) => - action(UPDATE_GRADING, { - submissionId, - grading - }); - export const unsubmitSubmission = (submissionId: number) => action(UNSUBMIT_SUBMISSION, { submissionId diff --git a/src/commons/application/reducers/CommonsReducer.ts b/src/commons/application/reducers/CommonsReducer.ts deleted file mode 100644 index e992c747dd..0000000000 --- a/src/commons/application/reducers/CommonsReducer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Reducer } from 'redux'; -import { SourceActionType } from 'src/commons/utils/ActionsHelper'; - -import { defaultRouter } from '../ApplicationTypes'; -import { RouterState, UPDATE_REACT_ROUTER } from '../types/CommonsTypes'; - -export const RouterReducer: Reducer = ( - state = defaultRouter, - action: SourceActionType -) => { - switch (action.type) { - case UPDATE_REACT_ROUTER: - return action.payload; - default: - return state; - } -}; diff --git a/src/commons/application/reducers/RootReducer.ts b/src/commons/application/reducers/RootReducer.ts deleted file mode 100644 index 88c1083947..0000000000 --- a/src/commons/application/reducers/RootReducer.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { combineReducers } from 'redux'; - -import { AcademyReducer as academy } from '../../../features/academy/AcademyReducer'; -import { AchievementReducer as achievement } from '../../../features/achievement/AchievementReducer'; -import { DashboardReducer as dashboard } from '../../../features/dashboard/DashboardReducer'; -import { PlaygroundReducer as playground } from '../../../features/playground/PlaygroundReducer'; -import { StoriesReducer as stories } from '../../../features/stories/StoriesReducer'; -import { FileSystemReducer as fileSystem } from '../../fileSystem/FileSystemReducer'; -import { WorkspaceReducer as workspaces } from '../../workspace/WorkspaceReducer'; -import { ApplicationReducer as application } from '../ApplicationReducer'; -import { RouterReducer as router } from './CommonsReducer'; -import { SessionsReducer as session } from './SessionsReducer'; - -const createRootReducer = () => - combineReducers({ - router, - academy, - achievement, - application, - dashboard, - playground, - session, - stories, - workspaces, - fileSystem - }); - -export default createRootReducer; diff --git a/src/commons/application/reducers/SessionsReducer.ts b/src/commons/application/reducers/SessionsReducer.ts deleted file mode 100644 index 32491eadd6..0000000000 --- a/src/commons/application/reducers/SessionsReducer.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Reducer } from 'redux'; - -import { - REMOTE_EXEC_UPDATE_DEVICES, - REMOTE_EXEC_UPDATE_SESSION -} from '../../../features/remoteExecution/RemoteExecutionTypes'; -import { SourceActionType } from '../../utils/ActionsHelper'; -import { defaultSession } from '../ApplicationTypes'; -import { LOG_OUT } from '../types/CommonsTypes'; -import { - REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN, - SessionState, - SET_ADMIN_PANEL_COURSE_REGISTRATIONS, - SET_ASSESSMENT_CONFIGURATIONS, - SET_CONFIGURABLE_NOTIFICATION_CONFIGS, - SET_COURSE_CONFIGURATION, - SET_COURSE_REGISTRATION, - SET_GITHUB_ACCESS_TOKEN, - SET_GITHUB_ASSESSMENT, - SET_GITHUB_OCTOKIT_OBJECT, - SET_GOOGLE_USER, - SET_NOTIFICATION_CONFIGS, - SET_TOKENS, - SET_USER, - UPDATE_ALL_USER_XP, - UPDATE_ASSESSMENT, - UPDATE_ASSESSMENT_OVERVIEWS, - UPDATE_GRADING, - UPDATE_GRADING_OVERVIEWS, - UPDATE_NOTIFICATIONS, - UPDATE_TOTAL_XP -} from '../types/SessionTypes'; - -export const SessionsReducer: Reducer = ( - state = defaultSession, - action: SourceActionType -) => { - switch (action.type) { - case LOG_OUT: - return defaultSession; - case SET_GITHUB_ASSESSMENT: - return { - ...state, - githubAssessment: action.payload - }; - case SET_GITHUB_OCTOKIT_OBJECT: - return { - ...state, - githubOctokitObject: { octokit: action.payload } - }; - case SET_GITHUB_ACCESS_TOKEN: - return { - ...state, - githubAccessToken: action.payload - }; - case SET_GOOGLE_USER: - return { - ...state, - googleUser: action.payload - }; - case SET_TOKENS: - return { - ...state, - ...action.payload - }; - case SET_USER: - return { - ...state, - ...action.payload - }; - case SET_COURSE_CONFIGURATION: - return { - ...state, - ...action.payload - }; - case SET_COURSE_REGISTRATION: - return { - ...state, - ...action.payload - }; - case SET_ASSESSMENT_CONFIGURATIONS: - return { - ...state, - assessmentConfigurations: action.payload - }; - case SET_NOTIFICATION_CONFIGS: - return { - ...state, - notificationConfigs: action.payload - }; - case SET_CONFIGURABLE_NOTIFICATION_CONFIGS: - return { - ...state, - configurableNotificationConfigs: action.payload - }; - case SET_ADMIN_PANEL_COURSE_REGISTRATIONS: - return { - ...state, - userCourseRegistrations: action.payload - }; - case UPDATE_ASSESSMENT: - const newAssessments = new Map(state.assessments); - newAssessments.set(action.payload.id, action.payload); - return { - ...state, - assessments: newAssessments - }; - case UPDATE_ASSESSMENT_OVERVIEWS: - return { - ...state, - assessmentOverviews: action.payload - }; - case UPDATE_TOTAL_XP: - return { ...state, xp: action.payload }; - case UPDATE_ALL_USER_XP: - return { ...state, allUserXp: action.payload }; - case UPDATE_GRADING: - const newGradings = new Map(state.gradings); - newGradings.set(action.payload.submissionId, action.payload.grading); - return { - ...state, - gradings: newGradings - }; - case UPDATE_GRADING_OVERVIEWS: - return { - ...state, - gradingOverviews: action.payload - }; - case UPDATE_NOTIFICATIONS: - return { - ...state, - notifications: action.payload - }; - case REMOTE_EXEC_UPDATE_DEVICES: - return { - ...state, - remoteExecutionDevices: action.payload - }; - case REMOTE_EXEC_UPDATE_SESSION: - return { - ...state, - remoteExecutionSession: action.payload - }; - case REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN: - return { - ...state, - githubOctokitObject: { octokit: undefined }, - githubAccessToken: undefined - }; - default: - return state; - } -}; diff --git a/src/commons/application/reducers/__tests__/SessionReducer.ts b/src/commons/application/reducers/__tests__/SessionReducer.ts index cbfd2c0e52..4596ec2b4e 100644 --- a/src/commons/application/reducers/__tests__/SessionReducer.ts +++ b/src/commons/application/reducers/__tests__/SessionReducer.ts @@ -8,6 +8,7 @@ import { GradingStatuses } from '../../../assessment/AssessmentTypes'; import { Notification } from '../../../notificationBadge/NotificationBadgeTypes'; +import { SessionsReducer } from '../../../redux/session/SessionsReducer'; import { defaultSession, GameState, Role, Story } from '../../ApplicationTypes'; import { LOG_OUT } from '../../types/CommonsTypes'; import { @@ -25,7 +26,6 @@ import { UPDATE_GRADING_OVERVIEWS, UPDATE_NOTIFICATIONS } from '../../types/SessionTypes'; -import { SessionsReducer } from '../SessionsReducer'; test('LOG_OUT works correctly on default session', () => { const action = { diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index 0169f52ad0..937c824b0e 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -1,7 +1,5 @@ import { Chapter, SourceError, Variant } from 'js-slang/dist/types'; -import { ExternalLibrary, ExternalLibraryName } from '../application/types/ExternalTypes'; - export const FETCH_ASSESSMENT_OVERVIEWS = 'FETCH_ASSESSMENT_OVERVIEWS'; export const SUBMIT_ASSESSMENT = 'SUBMIT_ASSESSMENT'; @@ -152,12 +150,14 @@ export type Library = { chapter: Chapter; variant?: Variant; execTimeMs?: number; - external: ExternalLibrary; globals: Array<{ 0: string; 1: any; 2?: string; // For mission control }>; + external: { + symbols: string[]; + }; moduleParams?: any; }; @@ -207,7 +207,6 @@ export const emptyLibrary = (): Library => { return { chapter: -1, external: { - name: 'NONE' as ExternalLibraryName, symbols: [] }, globals: [] @@ -218,7 +217,6 @@ export const normalLibrary = (): Library => { return { chapter: Chapter.SOURCE_1, external: { - name: 'NONE' as ExternalLibraryName, symbols: [] }, globals: [] diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index bd6a6f94b7..7139a2db29 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -10,6 +10,7 @@ import { SpinnerSize } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; +import { DeepPartial } from '@reduxjs/toolkit'; import classNames from 'classnames'; import { Chapter, Variant } from 'js-slang/dist/types'; import { isEqual } from 'lodash'; @@ -27,7 +28,6 @@ import { SelectionRange } from '../../features/sourceRecorder/SourceRecorderTypes'; import { fetchAssessment, submitAnswer } from '../application/actions/SessionActions'; -import { defaultWorkspaceManager } from '../application/ApplicationTypes'; import { AssessmentConfiguration, AutogradingResult, @@ -35,7 +35,6 @@ import { IContestVotingQuestion, IMCQQuestion, IProgrammingQuestion, - Library, QuestionTypes, Testcase } from '../assessment/AssessmentTypes'; @@ -54,45 +53,30 @@ import { convertEditorTabStateToProps, NormalEditorContainerProps } from '../editor/EditorContainer'; -import { Position } from '../editor/EditorTypes'; import Markdown from '../Markdown'; import { MobileSideContentProps } from '../mobileWorkspace/mobileSideContent/MobileSideContent'; import MobileWorkspace, { MobileWorkspaceProps } from '../mobileWorkspace/MobileWorkspace'; +import { allWorkspaceActions } from '../redux/workspace/AllWorkspacesRedux'; +import { useEditorState, useRepl, useSideContent, useWorkspace } from '../redux/workspace/Hooks'; +import { + AssessmentWorkspaceState, + defaultAssessment, + SideContentLocation +} from '../redux/workspace/WorkspaceReduxTypes'; +import { ReplProps } from '../repl/Repl'; +import SideContentAutograder from '../sideContent/content/SideContentAutograder'; +import SideContentContestLeaderboard from '../sideContent/content/SideContentContestLeaderboard'; +import SideContentContestVotingContainer from '../sideContent/content/SideContentContestVotingContainer'; +import SideContentToneMatrix from '../sideContent/content/SideContentToneMatrix'; import { SideContentProps } from '../sideContent/SideContent'; -import SideContentAutograder from '../sideContent/SideContentAutograder'; -import SideContentContestLeaderboard from '../sideContent/SideContentContestLeaderboard'; -import SideContentContestVotingContainer from '../sideContent/SideContentContestVotingContainer'; -import SideContentToneMatrix from '../sideContent/SideContentToneMatrix'; import { SideContentTab, SideContentType } from '../sideContent/SideContentTypes'; import Constants from '../utils/Constants'; 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, - changeSideContentHeight, - clearReplOutput, - evalEditor, - evalRepl, - evalTestcase, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - resetWorkspace, - runAllTestcases, - setEditorBreakpoint, - updateActiveEditorTabIndex, - updateCurrentAssessmentId, - updateEditorValue, - updateHasUnsavedChanges, - updateReplValue -} from '../workspace/WorkspaceActions'; -import { WorkspaceLocation, WorkspaceState } from '../workspace/WorkspaceTypes'; import AssessmentWorkspaceGradingResult from './AssessmentWorkspaceGradingResult'; + export type AssessmentWorkspaceProps = { assessmentId: number; questionId: number; @@ -101,7 +85,7 @@ export type AssessmentWorkspaceProps = { assessmentConfiguration: AssessmentConfiguration; }; -const workspaceLocation: WorkspaceLocation = 'assessment'; +const workspaceLocation: SideContentLocation = 'assessment'; const AssessmentWorkspace: React.FC = props => { const [showOverlay, setShowOverlay] = useState(false); @@ -110,68 +94,70 @@ const AssessmentWorkspace: React.FC = props => { const { isMobileBreakpoint } = useResponsive(); const assessment = useTypedSelector(state => state.session.assessments.get(props.assessmentId)); - const [selectedTab, setSelectedTab] = useState( + const { selectedTab, setSelectedTab } = useSideContent( + workspaceLocation, assessment?.questions[props.questionId].grader !== undefined ? SideContentType.grading : SideContentType.questionOverview ); + const { + activeEditorTabIndex, + editorTabs, + editorSessionId, + isEditorAutorun, + isFolderModeEnabled, + updateEditorBreakpoints: handleEditorUpdateBreakpoints, + updateEditorValue: handleEditorValueChange, + updateActiveEditorTabIndex: handleUpdateActiveEditorTabIndex, + removeEditorTab: handleRemoveEditorTabByIndex + } = useEditorState(workspaceLocation); + + const { clearReplOutput } = useRepl(workspaceLocation); + const navigate = useNavigate(); const { courseId } = useTypedSelector(state => state.session); const { - isFolderModeEnabled, - activeEditorTabIndex, - editorTabs, autogradingResults, + context, editorTestcases, hasUnsavedChanges, isRunning, - output, - replValue, - sideContentHeight, + repl: { output }, currentAssessment: storedAssessmentId, - currentQuestion: storedQuestionId - } = useTypedSelector(store => store.workspaces[workspaceLocation]); + currentQuestion: storedQuestionId, + globals: workspaceGlobals, + beginClearContext: handleClearContext, + changeExecTime: handleChangeExecTime, + evalEditor: handleEditorEval, + evalRepl: handleReplEval, + navDeclaration: handleDeclarationNavigate, + promptAutocomplete: handlePromptAutocomplete, + resetWorkspace: handleResetWorkspace, + updateHasUnsavedChanges: handleUpdateHasUnsavedChanges + } = useWorkspace(workspaceLocation); const dispatch = useDispatch(); const { handleTestcaseEval, - handleClearContext, - handleChangeExecTime, handleUpdateCurrentAssessmentId, - handleResetWorkspace, handleRunAllTestcases, - handleEditorEval, handleAssessmentFetch, - handleEditorValueChange, - handleEditorUpdateBreakpoints, - handleReplEval, - handleSave, - handleUpdateHasUnsavedChanges + handleSave } = useMemo(() => { return { - handleTestcaseEval: (id: number) => dispatch(evalTestcase(workspaceLocation, id)), - handleClearContext: (library: Library, shouldInitLibrary: boolean) => - dispatch(beginClearContext(workspaceLocation, library, shouldInitLibrary)), - handleChangeExecTime: (execTimeMs: number) => - dispatch(changeExecTime(execTimeMs, workspaceLocation)), + handleTestcaseEval: (id: number) => + dispatch(allWorkspaceActions.evalTestCase(workspaceLocation, id)), handleUpdateCurrentAssessmentId: (assessmentId: number, questionId: number) => - dispatch(updateCurrentAssessmentId(assessmentId, questionId)), - handleResetWorkspace: (options: Partial) => - dispatch(resetWorkspace(workspaceLocation, options)), - handleRunAllTestcases: () => dispatch(runAllTestcases(workspaceLocation)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), + dispatch( + allWorkspaceActions.updateCurrentAssessmentId(workspaceLocation, assessmentId, questionId) + ), + handleRunAllTestcases: () => + dispatch(allWorkspaceActions.evalEditorAndTestcases(workspaceLocation)), handleAssessmentFetch: (assessmentId: number) => dispatch(fetchAssessment(assessmentId)), - handleEditorValueChange: (editorTabIndex: number, newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue)), - handleEditorUpdateBreakpoints: (editorTabIndex: number, newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), handleSave: (id: number, answer: number | string | ContestEntry[]) => - dispatch(submitAnswer(id, answer)), - handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => - dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)) + dispatch(submitAnswer(id, answer)) }; }, [dispatch]); @@ -233,7 +219,7 @@ const AssessmentWorkspace: React.FC = props => { if (!isMobileBreakpoint && mobileOnlyTabIds.includes(selectedTab)) { setSelectedTab(SideContentType.questionOverview); } - }, [isMobileBreakpoint, props, selectedTab]); + }, [isMobileBreakpoint, props, selectedTab, setSelectedTab]); /* ================== onChange handlers @@ -344,7 +330,6 @@ const AssessmentWorkspace: React.FC = props => { setSessionId( initSession(`${(assessment as any).number}/${props.questionId}`, { chapter: question.library.chapter, - externalLibrary: question?.library?.external?.name || 'NONE', editorValue: options.editorValue }) ); @@ -363,19 +348,24 @@ const AssessmentWorkspace: React.FC = props => { // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. handleEditorUpdateBreakpoints(0, []); handleUpdateCurrentAssessmentId(assessmentId, questionId); - const resetWorkspaceOptions = assertType()({ + const resetWorkspaceOptions = assertType>()({ autogradingResults: options.autogradingResults ?? [], - // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - editorTabs: [{ value: options.editorValue ?? '', highlightedLines: [], breakpoints: [] }], + editorState: { + // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. + editorTabs: [{ value: options.editorValue ?? '', highlightedLines: [], breakpoints: [] }] + }, programPrependValue: options.programPrependValue ?? '', programPostpendValue: options.programPostpendValue ?? '', editorTestcases: options.editorTestcases ?? [] }); handleResetWorkspace(resetWorkspaceOptions); - handleChangeExecTime( - question.library.execTimeMs ?? defaultWorkspaceManager.assessment.execTime + handleChangeExecTime(question.library.execTimeMs ?? defaultAssessment.execTime); + handleClearContext( + context.chapter, + context.variant, + workspaceGlobals, + context.externalContext.symbols ); - handleClearContext(question.library, true); handleUpdateHasUnsavedChanges(false); if (options.editorValue) { // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. @@ -518,7 +508,7 @@ const AssessmentWorkspace: React.FC = props => { afterDynamicTabs: [] }, onChange: onChangeTabs, - workspaceLocation: workspaceLocation + location: workspaceLocation }; }; @@ -656,46 +646,14 @@ const AssessmentWorkspace: React.FC = props => { const replButtons = useMemo(() => { const clearButton = ( - dispatch(clearReplOutput(workspaceLocation))} - key="clear_repl" - /> + ); const evalButton = ( ); return [evalButton, clearButton]; - }, [dispatch, isRunning, handleReplEval]); - - const editorContainerHandlers = useMemo(() => { - return { - setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), - removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)), - handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), - handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)) - }; - }, [dispatch]); - - const replHandlers = useMemo(() => { - return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), - handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)) - }; - }, [dispatch]); - - const workspaceHandlers = useMemo(() => { - return { - handleSideContentHeightChange: (heightChange: number) => - dispatch(changeSideContentHeight(heightChange, workspaceLocation)) - }; - }, [dispatch]); + }, [clearReplOutput, handleReplEval, isRunning]); /* =============== Rendering Logic @@ -767,42 +725,37 @@ const AssessmentWorkspace: React.FC = props => { const editorContainerProps: NormalEditorContainerProps | undefined = question.type === QuestionTypes.programming || question.type === QuestionTypes.voting ? { - editorVariant: 'normal', - isFolderModeEnabled, activeEditorTabIndex, - setActiveEditorTabIndex: editorContainerHandlers.setActiveEditorTabIndex, - removeEditorTabByIndex: editorContainerHandlers.removeEditorTabByIndex, + editorVariant: 'normal', + editorSessionId, editorTabs: editorTabs.map(convertEditorTabStateToProps), - editorSessionId: '', sourceChapter: question.library.chapter || Chapter.SOURCE_4, sourceVariant: question.library.variant ?? Variant.DEFAULT, - externalLibraryName: question.library.external.name || 'NONE', - handleDeclarationNavigate: editorContainerHandlers.handleDeclarationNavigate, - handleEditorEval: handleEval, - handleEditorValueChange: handleEditorValueChange, - handleUpdateHasUnsavedChanges: handleUpdateHasUnsavedChanges, - handleEditorUpdateBreakpoints: handleEditorUpdateBreakpoints, - handlePromptAutocomplete: editorContainerHandlers.handlePromptAutocomplete, - isEditorAutorun: false, + handleDeclarationNavigate, + handlePromptAutocomplete, onChange: onChangeMethod, onCursorChange: onCursorChangeMethod, - onSelectionChange: onSelectionChangeMethod + onSelectionChange: onSelectionChangeMethod, + handleEditorEval, + isEditorAutorun, + isFolderModeEnabled, + setActiveEditorTabIndex: handleUpdateActiveEditorTabIndex, + removeEditorTabByIndex: handleRemoveEditorTabByIndex, + // TODO check this + handleEditorUpdateBreakpoints, + handleEditorValueChange } : undefined; const mcqProps = { mcq: question as IMCQQuestion, handleMCQSubmit: (option: number) => handleSave(assessment!.questions[questionId].id, option) }; - const replProps = { - handleBrowseHistoryDown: replHandlers.handleBrowseHistoryDown, - handleBrowseHistoryUp: replHandlers.handleBrowseHistoryUp, + const replProps: ReplProps = { handleReplEval: handleReplEval, - handleReplValueChange: replHandlers.handleReplValueChange, output: output, - replValue: replValue, + location: workspaceLocation, sourceChapter: question?.library?.chapter || Chapter.SOURCE_4, sourceVariant: question.library.variant ?? Variant.DEFAULT, - externalLibrary: question?.library?.external?.name || 'NONE', replButtons: replButtons }; const sideBarProps = { @@ -811,13 +764,12 @@ const AssessmentWorkspace: React.FC = props => { const workspaceProps: WorkspaceProps = { controlBarProps: controlBarProps(questionId), editorContainerProps: editorContainerProps, - handleSideContentHeightChange: workspaceHandlers.handleSideContentHeightChange, hasUnsavedChanges: hasUnsavedChanges, mcqProps: mcqProps, sideBarProps: sideBarProps, - sideContentHeight: sideContentHeight, sideContentProps: sideContentProps(props, questionId), - replProps: replProps + replProps, + workspaceLocation }; const mobileWorkspaceProps: MobileWorkspaceProps = { editorContainerProps: editorContainerProps, diff --git a/src/commons/collabEditing/CollabEditingActions.ts b/src/commons/collabEditing/CollabEditingActions.ts deleted file mode 100644 index f8a75beb55..0000000000 --- a/src/commons/collabEditing/CollabEditingActions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { action } from 'typesafe-actions'; // EDITING - -import { WorkspaceLocation } from '../workspace/WorkspaceTypes'; -import { SET_EDITOR_SESSION_ID, SET_SHAREDB_CONNECTED } from './CollabEditingTypes'; - -export const setEditorSessionId = (workspaceLocation: WorkspaceLocation, editorSessionId: string) => - action(SET_EDITOR_SESSION_ID, { - workspaceLocation, - editorSessionId - }); - -/** - * Sets ShareDB connection status. - * - * @param workspaceLocation the workspace to be reset - * @param connected whether we are connected to ShareDB - */ -export const setSharedbConnected = (workspaceLocation: WorkspaceLocation, connected: boolean) => - action(SET_SHAREDB_CONNECTED, { workspaceLocation, connected }); diff --git a/src/commons/controlBar/ControlBarChapterSelect.tsx b/src/commons/controlBar/ControlBarChapterSelect.tsx index fc611dca3f..d2e8593a3c 100644 --- a/src/commons/controlBar/ControlBarChapterSelect.tsx +++ b/src/commons/controlBar/ControlBarChapterSelect.tsx @@ -78,7 +78,9 @@ export const ControlBarChapterSelect: React.FC = ( handleChapterSelect = () => {}, disabled = false }) => { - const selectedLang = useTypedSelector(store => store.playground.languageConfig.mainLanguage); + const selectedLang = useTypedSelector( + store => store.workspaces.playground.languageConfig.mainLanguage + ); const choices = [ ...sourceLanguages, diff --git a/src/commons/editingWorkspace/EditingWorkspace.tsx b/src/commons/editingWorkspace/EditingWorkspace.tsx index ff84f95e69..7a9906eea6 100644 --- a/src/commons/editingWorkspace/EditingWorkspace.tsx +++ b/src/commons/editingWorkspace/EditingWorkspace.tsx @@ -20,7 +20,6 @@ import { AssessmentOverview, IMCQQuestion, IProgrammingQuestion, - Library, Question, QuestionTypes, Testcase @@ -44,35 +43,14 @@ import MCQQuestionTemplateTab from '../editingWorkspaceSideContent/EditingWorksp import ProgrammingQuestionTemplateTab from '../editingWorkspaceSideContent/EditingWorkspaceSideContentProgrammingQuestionTemplateTab'; import { TextAreaContent } from '../editingWorkspaceSideContent/EditingWorkspaceSideContentTextAreaContent'; import { convertEditorTabStateToProps } from '../editor/EditorContainer'; -import { Position } from '../editor/EditorTypes'; import Markdown from '../Markdown'; +import { allWorkspaceActions } from '../redux/workspace/AllWorkspacesRedux'; +import { useEditorState, useRepl, useSideContent, useWorkspace } from '../redux/workspace/Hooks'; +import { SideContentLocation } from '../redux/workspace/WorkspaceReduxTypes'; +import SideContentToneMatrix from '../sideContent/content/SideContentToneMatrix'; import { SideContentProps } from '../sideContent/SideContent'; -import SideContentToneMatrix from '../sideContent/SideContentToneMatrix'; import { SideContentTab, SideContentType } from '../sideContent/SideContentTypes'; -import { useTypedSelector } from '../utils/Hooks'; import Workspace, { WorkspaceProps } from '../workspace/Workspace'; -import { - beginClearContext, - browseReplHistoryDown, - browseReplHistoryUp, - changeSideContentHeight, - clearReplOutput, - evalEditor, - evalRepl, - evalTestcase, - navigateToDeclaration, - promptAutocomplete, - removeEditorTab, - resetWorkspace, - setEditorBreakpoint, - updateActiveEditorTabIndex, - updateCurrentAssessmentId, - updateEditorValue, - updateHasUnsavedChanges, - updateReplValue, - updateWorkspace -} from '../workspace/WorkspaceActions'; -import { WorkspaceLocation, WorkspaceState } from '../workspace/WorkspaceTypes'; import { retrieveLocalAssessment, storeLocalAssessment, @@ -88,7 +66,7 @@ export type EditingWorkspaceProps = { closeDate: string; }; -const workspaceLocation: WorkspaceLocation = 'assessment'; +const workspaceLocation: SideContentLocation = 'assessment'; const EditingWorkspace: React.FC = props => { const [assessment, setAssessment] = useState(retrieveLocalAssessment()); @@ -99,16 +77,33 @@ const EditingWorkspace: React.FC = props => { const navigate = useNavigate(); const { - isFolderModeEnabled, activeEditorTabIndex, editorTabs, + editorSessionId, + isEditorAutorun, + isFolderModeEnabled, + removeEditorTab: handleRemoveEditorTabByIndex, + updateActiveEditorTabIndex: handleUpdateActiveEditorTabIndex, + updateEditorValue: handleEditorValueChange, + updateEditorBreakpoints: handleEditorUpdateBreakpoints + } = useEditorState(workspaceLocation); + useSideContent(workspaceLocation, SideContentType.introduction); + + const { clearReplOutput: handleReplOutputClear } = useRepl(workspaceLocation); + + const { isRunning, - output, - replValue, - sideContentHeight, + repl: { output }, currentAssessment: storedAssessmentId, - currentQuestion: storedQuestionId - } = useTypedSelector(store => store.workspaces[workspaceLocation]); + currentQuestion: storedQuestionId, + evalEditor: handleEditorEval, + evalRepl: handleReplEval, + navDeclaration: handleDeclarationNavigate, + promptAutocomplete: handlePromptAutocomplete, + resetWorkspace: handleResetWorkspace, + updateHasUnsavedChanges: handleUpdateHasUnsavedChanges, + updateWorkspace: handleUpdateWorkspace + } = useWorkspace(workspaceLocation); /** * After mounting (either an older copy of the assessment @@ -130,61 +125,14 @@ const EditingWorkspace: React.FC = props => { useEffect(() => checkWorkspaceReset()); const dispatch = useDispatch(); - const { - handleBrowseHistoryDown, - handleBrowseHistoryUp, - handleClearContext, - handleDeclarationNavigate, - handleEditorEval, - handleEditorValueChange, - handleEditorUpdateBreakpoints, - handleReplEval, - handleReplOutputClear, - handleReplValueChange, - handleResetWorkspace, - handleUpdateWorkspace, - handleSubmitAnswer, - handleSideContentHeightChange, - handleUpdateHasUnsavedChanges, - handleUpdateCurrentAssessmentId, - handlePromptAutocomplete, - setActiveEditorTabIndex, - removeEditorTabByIndex - } = useMemo(() => { + const { handleSubmitAnswer, handleUpdateCurrentAssessmentId } = useMemo(() => { return { - handleBrowseHistoryDown: () => dispatch(browseReplHistoryDown(workspaceLocation)), - handleBrowseHistoryUp: () => dispatch(browseReplHistoryUp(workspaceLocation)), - handleClearContext: (library: Library, shouldInitLibrary: boolean) => - dispatch(beginClearContext(workspaceLocation, library, shouldInitLibrary)), - handleDeclarationNavigate: (cursorPosition: Position) => - dispatch(navigateToDeclaration(workspaceLocation, cursorPosition)), - handleEditorEval: () => dispatch(evalEditor(workspaceLocation)), - handleEditorValueChange: (editorTabIndex: number, newEditorValue: string) => - dispatch(updateEditorValue(workspaceLocation, editorTabIndex, newEditorValue)), - handleEditorUpdateBreakpoints: (editorTabIndex: number, newBreakpoints: string[]) => - dispatch(setEditorBreakpoint(workspaceLocation, editorTabIndex, newBreakpoints)), - handleReplEval: () => dispatch(evalRepl(workspaceLocation)), - handleReplOutputClear: () => dispatch(clearReplOutput(workspaceLocation)), - handleReplValueChange: (newValue: string) => - dispatch(updateReplValue(newValue, workspaceLocation)), - handleResetWorkspace: (options: Partial) => - dispatch(resetWorkspace(workspaceLocation, options)), - handleUpdateWorkspace: (options: Partial) => - dispatch(updateWorkspace(workspaceLocation, options)), handleSubmitAnswer: (id: number, answer: string | number) => dispatch(submitAnswer(id, answer)), - handleSideContentHeightChange: (heightChange: number) => - dispatch(changeSideContentHeight(heightChange, workspaceLocation)), - handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => - dispatch(updateHasUnsavedChanges(workspaceLocation, hasUnsavedChanges)), handleUpdateCurrentAssessmentId: (assessmentId: number, questionId: number) => - dispatch(updateCurrentAssessmentId(assessmentId, questionId)), - handlePromptAutocomplete: (row: number, col: number, callback: any) => - dispatch(promptAutocomplete(workspaceLocation, row, col, callback)), - setActiveEditorTabIndex: (activeEditorTabIndex: number | null) => - dispatch(updateActiveEditorTabIndex(workspaceLocation, activeEditorTabIndex)), - removeEditorTabByIndex: (editorTabIndex: number) => - dispatch(removeEditorTab(workspaceLocation, editorTabIndex)) + dispatch( + allWorkspaceActions.updateCurrentAssessmentId(workspaceLocation, assessmentId, questionId) + ) }; }, [dispatch]); @@ -244,7 +192,6 @@ const EditingWorkspace: React.FC = props => { setHasUnsavedChanges(false); setShowResetTemplateOverlay(false); setOriginalMaxXp(getMaxXp()); - handleRefreshLibrary(); resetWorkspaceValues(); }} options={{ minimal: false, intent: Intent.DANGER }} @@ -276,28 +223,27 @@ const EditingWorkspace: React.FC = props => { setAssessment(retrieveLocalAssessment()); setHasUnsavedChanges(false); } - handleRefreshLibrary(); } } - const handleRefreshLibrary = (library: Library | undefined = undefined) => { - const question = assessment!.questions[formatedQuestionId()]; - if (!library) { - library = question.library.chapter === -1 ? assessment!.globalDeployment! : question.library; - } - if (library && library.globals.length > 0) { - const globalsVal = library.globals.map((x: any) => x[0]); - const symbolsVal = library.external.symbols.concat(globalsVal); - library = { - ...library, - external: { - name: library.external.name, - symbols: uniq(symbolsVal) - } - }; - } - handleClearContext(library, true); - }; + // const handleRefreshLibrary = (library: Library | undefined = undefined) => { + // const question = assessment!.questions[formatedQuestionId()]; + // if (!library) { + // library = question.library.chapter === -1 ? assessment!.globalDeployment! : question.library; + // } + // if (library && library.globals.length > 0) { + // const globalsVal = library.globals.map((x: any) => x[0]); + // const symbolsVal = library.external.symbols.concat(globalsVal); + // library = { + // ...library, + // external: { + // name: library.external.name, + // symbols: uniq(symbolsVal) + // } + // }; + // } + // handleClearContext(true); + // }; const resetWorkspaceValues = () => { const question: Question = assessment!.questions[formatedQuestionId()]; @@ -318,13 +264,15 @@ const EditingWorkspace: React.FC = props => { handleResetWorkspace({ // TODO: Hardcoded to make use of the first editor tab. Rewrite after editor tabs are added. - editorTabs: [ - { - value: editorValue, - highlightedLines: [], - breakpoints: [] - } - ], + editorState: { + editorTabs: [ + { + value: editorValue, + highlightedLines: [], + breakpoints: [] + } + ] + }, programPrependValue, programPostpendValue }); @@ -335,7 +283,7 @@ const EditingWorkspace: React.FC = props => { const handleTestcaseEval = (testcase: Testcase) => { const editorTestcases = [testcase]; handleUpdateWorkspace({ editorTestcases }); - dispatch(evalTestcase(workspaceLocation, 0)); + dispatch(allWorkspaceActions.evalTestCase(workspaceLocation, 0)); }; const handleSave = () => { @@ -377,7 +325,6 @@ const EditingWorkspace: React.FC = props => { const updateAndSaveAssessment = (assessmentVal: Assessment) => { setAssessment(assessmentVal); - handleRefreshLibrary(); handleSave(); resetWorkspaceValues(); }; @@ -441,7 +388,6 @@ const EditingWorkspace: React.FC = props => { = props => { = props => { = props => { = props => { } return { - tabs: { beforeDynamicTabs: tabs, afterDynamicTabs: [] } + tabs: { beforeDynamicTabs: tabs, afterDynamicTabs: [] }, + location: workspaceLocation }; }; @@ -659,10 +603,10 @@ const EditingWorkspace: React.FC = props => { question.type === QuestionTypes.programming ? { editorVariant: 'normal', - isFolderModeEnabled, + editorSessionId, activeEditorTabIndex, - setActiveEditorTabIndex, - removeEditorTabByIndex, + isEditorAutorun, + isFolderModeEnabled, editorTabs: editorTabs .map(convertEditorTabStateToProps) .map((editorTabStateProps, index) => { @@ -679,17 +623,15 @@ const EditingWorkspace: React.FC = props => { (question as IProgrammingQuestion).solutionTemplate }; }), - editorSessionId: '', - handleDeclarationNavigate: handleDeclarationNavigate, - handleEditorEval: handleEditorEval, - handleEditorValueChange: handleEditorValueChange, - handleEditorUpdateBreakpoints: handleEditorUpdateBreakpoints, - handleUpdateHasUnsavedChanges: handleUpdateHasUnsavedChanges, - handlePromptAutocomplete: handlePromptAutocomplete, - isEditorAutorun: false + handleDeclarationNavigate, + handlePromptAutocomplete, + setActiveEditorTabIndex: handleUpdateActiveEditorTabIndex, + removeEditorTabByIndex: handleRemoveEditorTabByIndex, + handleEditorUpdateBreakpoints, + handleEditorEval, + handleEditorValueChange } : undefined, - handleSideContentHeightChange: handleSideContentHeightChange, hasUnsavedChanges: hasUnsavedChanges, mcqProps: { mcq: question as IMCQQuestion, @@ -699,20 +641,17 @@ const EditingWorkspace: React.FC = props => { sideBarProps: { tabs: [] }, - sideContentHeight: sideContentHeight, sideContentProps: sideContentProps(props, questionId), replProps: { - handleBrowseHistoryDown: handleBrowseHistoryDown, - handleBrowseHistoryUp: handleBrowseHistoryUp, + location: workspaceLocation, handleReplEval: handleReplEval, - handleReplValueChange: handleReplValueChange, output: output, - replValue: replValue, sourceChapter: question?.library?.chapter || Chapter.SOURCE_4, sourceVariant: Variant.DEFAULT, - externalLibrary: question?.library?.external?.name || 'NONE', + // externalLibrary: question?.library?.external?.name || 'NONE', replButtons: replButtons() - } + }, + workspaceLocation }; return (
@@ -722,9 +661,9 @@ const EditingWorkspace: React.FC = props => { ); }; -function uniq(a: string[]) { - const seen = {}; - return a.filter(item => (seen.hasOwnProperty(item) ? false : (seen[item] = true))); -} +// function uniq(a: string[]) { +// const seen = {}; +// return a.filter(item => (seen.hasOwnProperty(item) ? false : (seen[item] = true))); +// } export default EditingWorkspace; diff --git a/src/commons/editingWorkspaceSideContent/EditingWorkspaceSideContentDeploymentTab.tsx b/src/commons/editingWorkspaceSideContent/EditingWorkspaceSideContentDeploymentTab.tsx index 4485f14789..8c10c6bc97 100644 --- a/src/commons/editingWorkspaceSideContent/EditingWorkspaceSideContentDeploymentTab.tsx +++ b/src/commons/editingWorkspaceSideContent/EditingWorkspaceSideContentDeploymentTab.tsx @@ -5,11 +5,6 @@ import { Chapter, Variant } from 'js-slang/dist/types'; import React from 'react'; import { SALanguage, sourceLanguages, styliseSublanguage } from '../application/ApplicationTypes'; -import { - External, - externalLibraries, - ExternalLibraryName -} from '../application/types/ExternalTypes'; import { Assessment, emptyLibrary, Library } from '../assessment/AssessmentTypes'; import ControlButton from '../ControlButton'; import { assignToPath, getValueFromPath } from './EditingWorkspaceSideContentHelper'; @@ -19,7 +14,7 @@ type DeploymentTabProps = DispatchProps & StateProps; type DispatchProps = { updateAssessment: (assessment: Assessment) => void; - handleRefreshLibrary: (library: Library) => void; + // handleRefreshLibrary: (library: Library) => void; }; type StateProps = { @@ -56,19 +51,19 @@ const DeploymentTab: React.FC = props => { )); - const resetLibrary = ( - props.handleRefreshLibrary(deployment)} - /> - ); + // const resetLibrary = ( + // props.handleRefreshLibrary(deployment)} + // /> + // ); const symbolsFragment = ( External Library:
- {externalSelect(deployment.external.name, handleExternalSelect)} + {/* {externalSelect(deployment.external.name, handleExternalSelect)} */}
Symbols:

@@ -94,8 +89,8 @@ const DeploymentTab: React.FC = props => {
{/* {deploymentDisp}
*/} - - {resetLibrary} + {/* + {resetLibrary} */} Interpreter:
@@ -180,13 +175,12 @@ const DeploymentTab: React.FC = props => { props.updateAssessment(assessment); }; - const handleExternalSelect = (i: External, _e?: React.SyntheticEvent) => { - const assessment = props.assessment; - const deployment = getValueFromPath(props.pathToLibrary, assessment) as Library; - deployment.external.name = i.name; - deployment.external.symbols = JSON.parse(JSON.stringify(externalLibraries.get(i.name)!)); - props.updateAssessment(assessment); - }; + // const handleExternalSelect = (i: External, _e?: React.SyntheticEvent) => { + // const assessment = props.assessment; + // const deployment = getValueFromPath(props.pathToLibrary, assessment) as Library; + // deployment.external.symbols = JSON.parse(JSON.stringify(externalLibraries.get(i.name)!)); + // props.updateAssessment(assessment); + // }; const handleSwitchDeployment = () => { const assessment = props.assessment; @@ -267,35 +261,35 @@ const chapterRenderer: ItemRenderer = (chap, { handleClick, modifier ); -const iExternals = Array.from(externalLibraries.entries()).map((entry, index) => ({ - name: entry[0] as ExternalLibraryName, - key: index, - symbols: entry[1] -})); - -const externalSelect = ( - currentExternal: string, - handleSelect: (i: External, e?: React.SyntheticEvent) => void -) => ( - -