From db6c3e9ad19207ddc5725667c4206e3633252547 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 3 Feb 2024 23:52:46 +0800 Subject: [PATCH 01/20] Rename Dashboard component to match filename The component should be called `AchievementDashboard` instead. --- src/pages/achievement/subcomponents/AchievementDashboard.tsx | 4 ++-- .../subcomponents/AchievementDashboardContainer.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/achievement/subcomponents/AchievementDashboard.tsx b/src/pages/achievement/subcomponents/AchievementDashboard.tsx index 6b3d2bb492..3f35f5d622 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboard.tsx +++ b/src/pages/achievement/subcomponents/AchievementDashboard.tsx @@ -65,7 +65,7 @@ export const generateAchievementTasks = ( type DashboardProps = DispatchProps & StateProps; -const Dashboard: React.FC = props => { +const AchievementDashboard: React.FC = props => { const { getAchievements, getOwnGoals, @@ -192,4 +192,4 @@ const Dashboard: React.FC = props => { ); }; -export default Dashboard; +export default AchievementDashboard; diff --git a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts index c18c1ab475..6dec773a0d 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts +++ b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts @@ -12,7 +12,7 @@ import { getUsers, updateGoalProgress } from '../../../features/achievement/AchievementActions'; -import Dashboard, { DispatchProps, StateProps } from './AchievementDashboard'; +import AchievementDashboard, { DispatchProps, StateProps } from './AchievementDashboard'; const mapStateToProps: MapStateToProps = state => ({ group: state.session.group, @@ -39,6 +39,6 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis dispatch ); -const DashboardContainer = connect(mapStateToProps, mapDispatchToProps)(Dashboard); +const DashboardContainer = connect(mapStateToProps, mapDispatchToProps)(AchievementDashboard); export default DashboardContainer; From e802953128e5813d78a2a8cb5b461ed5fee43d41 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:08:41 +0800 Subject: [PATCH 02/20] Use `useDispatch` in achievement dashboard --- .../subcomponents/AchievementDashboard.tsx | 102 ++++++++++-------- .../AchievementDashboardContainer.ts | 26 +---- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/src/pages/achievement/subcomponents/AchievementDashboard.tsx b/src/pages/achievement/subcomponents/AchievementDashboard.tsx index 3f35f5d622..509a0036c5 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboard.tsx +++ b/src/pages/achievement/subcomponents/AchievementDashboard.tsx @@ -1,5 +1,6 @@ import { IconNames } from '@blueprintjs/icons'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useDispatch } from 'react-redux'; import { Role } from 'src/commons/application/ApplicationTypes'; import { AssessmentConfiguration, @@ -13,6 +14,15 @@ import AchievementTask from '../../../commons/achievement/AchievementTask'; import AchievementView from '../../../commons/achievement/AchievementView'; import AchievementInferencer from '../../../commons/achievement/utils/AchievementInferencer'; import insertFakeAchievements from '../../../commons/achievement/utils/InsertFakeAchievements'; +import { fetchAssessmentOverviews } from '../../../commons/application/actions/SessionActions'; +import { + getAchievements, + getGoals, + getOwnGoals, + getUserAssessmentOverviews, + getUsers, + updateGoalProgress +} from '../../../features/achievement/AchievementActions'; import { AchievementContext } from '../../../features/achievement/AchievementConstants'; import { AchievementUser, @@ -20,16 +30,6 @@ import { GoalProgress } from '../../../features/achievement/AchievementTypes'; -export type DispatchProps = { - fetchAssessmentOverviews: () => void; - getAchievements: () => void; - getGoals: (studentCourseRegId: number) => void; - getOwnGoals: () => void; - getUserAssessmentOverviews: (studentCourseRegId: number) => void; - getUsers: () => void; - updateGoalProgress: (studentCourseRegId: number, progress: GoalProgress) => void; -}; - export type StateProps = { group: string | null; inferencer: AchievementInferencer; @@ -63,49 +63,63 @@ export const generateAchievementTasks = ( /> )); -type DashboardProps = DispatchProps & StateProps; - -const AchievementDashboard: React.FC = props => { - const { - getAchievements, - getOwnGoals, - getGoals, - getUserAssessmentOverviews, - getUsers, - updateGoalProgress, - fetchAssessmentOverviews, - group, - inferencer, - name, - role, - assessmentConfigs, - assessmentOverviews, - achievementAssessmentOverviews, - users - } = props; - +type DashboardProps = StateProps; + +const AchievementDashboard: React.FC = ({ + group, + inferencer, + name, + role, + assessmentConfigs, + assessmentOverviews, + achievementAssessmentOverviews, + users +}) => { // default nothing selected const userIdState = useState(undefined); const [selectedUser] = userIdState; + const dispatch = useDispatch(); + const { + handleFetchAssessmentOverviews, + handleGetAchievements, + handleGetGoals, + handleGetOwnGoals, + handleGetUserAssessmentOverviews, + handleGetUsers, + handleUpdateGoalProgress + } = useMemo(() => { + return { + handleFetchAssessmentOverviews: () => dispatch(fetchAssessmentOverviews()), + handleGetAchievements: () => dispatch(getAchievements()), + handleGetGoals: (studentCourseRegId: number) => dispatch(getGoals(studentCourseRegId)), + handleGetOwnGoals: () => dispatch(getOwnGoals()), + handleGetUserAssessmentOverviews: (studentCourseRegId: number) => + dispatch(getUserAssessmentOverviews(studentCourseRegId)), + handleGetUsers: () => dispatch(getUsers()), + handleUpdateGoalProgress: (studentCourseRegId: number, progress: GoalProgress) => + dispatch(updateGoalProgress(studentCourseRegId, progress)) + }; + }, [dispatch]); + /** * Fetch the latest achievements and goals from backend when the page is rendered */ useEffect(() => { - selectedUser ? getGoals(selectedUser.courseRegId) : getOwnGoals(); + selectedUser ? handleGetGoals(selectedUser.courseRegId) : handleGetOwnGoals(); selectedUser - ? getUserAssessmentOverviews(selectedUser.courseRegId) - : fetchAssessmentOverviews(); + ? handleGetUserAssessmentOverviews(selectedUser.courseRegId) + : handleFetchAssessmentOverviews(); - getAchievements(); + handleGetAchievements(); }, [ - selectedUser, - getAchievements, - getGoals, - getOwnGoals, - getUserAssessmentOverviews, - fetchAssessmentOverviews + handleFetchAssessmentOverviews, + handleGetAchievements, + handleGetGoals, + handleGetOwnGoals, + handleGetUserAssessmentOverviews, + selectedUser ]); const userAssessmentOverviews = selectedUser @@ -149,8 +163,8 @@ const AchievementDashboard: React.FC = props => { hiddenState={hiddenState} studio={group || 'Staff'} users={users} - getUsers={getUsers} - updateGoalProgress={updateGoalProgress} + getUsers={handleGetUsers} + updateGoalProgress={handleUpdateGoalProgress} /> )} diff --git a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts index 6dec773a0d..3aff5d2826 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts +++ b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts @@ -2,17 +2,8 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import AchievementInferencer from '../../../commons/achievement/utils/AchievementInferencer'; -import { fetchAssessmentOverviews } from '../../../commons/application/actions/SessionActions'; import { OverallState } from '../../../commons/application/ApplicationTypes'; -import { - getAchievements, - getGoals, - getOwnGoals, - getUserAssessmentOverviews, - getUsers, - updateGoalProgress -} from '../../../features/achievement/AchievementActions'; -import AchievementDashboard, { DispatchProps, StateProps } from './AchievementDashboard'; +import AchievementDashboard, { StateProps } from './AchievementDashboard'; const mapStateToProps: MapStateToProps = state => ({ group: state.session.group, @@ -25,19 +16,8 @@ const mapStateToProps: MapStateToProps = state => assessmentConfigs: state.session.assessmentConfigurations }); -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - fetchAssessmentOverviews, - getAchievements, - getGoals, - getOwnGoals, - getUserAssessmentOverviews, - getUsers, - updateGoalProgress - }, - dispatch - ); +const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => + bindActionCreators({}, dispatch); const DashboardContainer = connect(mapStateToProps, mapDispatchToProps)(AchievementDashboard); From a79fdb14bf15448ddd42ae34e2f67ddfdbe17a4c Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:14:12 +0800 Subject: [PATCH 03/20] Use `useSelector` for achievement dashboard --- .../subcomponents/AchievementDashboard.tsx | 39 ++++++++----------- .../AchievementDashboardContainer.ts | 12 +----- 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/src/pages/achievement/subcomponents/AchievementDashboard.tsx b/src/pages/achievement/subcomponents/AchievementDashboard.tsx index 509a0036c5..c42cc8ee8e 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboard.tsx +++ b/src/pages/achievement/subcomponents/AchievementDashboard.tsx @@ -2,10 +2,7 @@ import { IconNames } from '@blueprintjs/icons'; import React, { useEffect, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Role } from 'src/commons/application/ApplicationTypes'; -import { - AssessmentConfiguration, - AssessmentOverview -} from 'src/commons/assessment/AssessmentTypes'; +import { useSession, useTypedSelector } from 'src/commons/utils/Hooks'; import AchievementFilter from '../../../commons/achievement/AchievementFilter'; import AchievementManualEditor from '../../../commons/achievement/AchievementManualEditor'; @@ -31,15 +28,7 @@ import { } from '../../../features/achievement/AchievementTypes'; export type StateProps = { - group: string | null; - inferencer: AchievementInferencer; id?: number; - name?: string; - role?: Role; - assessmentConfigs?: AssessmentConfiguration[]; - assessmentOverviews?: AssessmentOverview[]; - achievementAssessmentOverviews: AssessmentOverview[]; - users: AchievementUser[]; }; /** @@ -65,20 +54,26 @@ export const generateAchievementTasks = ( type DashboardProps = StateProps; -const AchievementDashboard: React.FC = ({ - group, - inferencer, - name, - role, - assessmentConfigs, - assessmentOverviews, - achievementAssessmentOverviews, - users -}) => { +const AchievementDashboard: React.FC = () => { // default nothing selected const userIdState = useState(undefined); const [selectedUser] = userIdState; + const { + group, + name, + role, + assessmentOverviews, + assessmentConfigurations: assessmentConfigs + } = useSession(); + + const { assessmentOverviews: achievementAssessmentOverviews, users } = useTypedSelector( + state => state.achievement + ); + const inferencer = useTypedSelector( + state => new AchievementInferencer(state.achievement.achievements, state.achievement.goals) + ); + const dispatch = useDispatch(); const { handleFetchAssessmentOverviews, diff --git a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts index 3aff5d2826..fc727e3a79 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts +++ b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts @@ -1,20 +1,10 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; -import AchievementInferencer from '../../../commons/achievement/utils/AchievementInferencer'; import { OverallState } from '../../../commons/application/ApplicationTypes'; import AchievementDashboard, { StateProps } from './AchievementDashboard'; -const mapStateToProps: MapStateToProps = state => ({ - group: state.session.group, - inferencer: new AchievementInferencer(state.achievement.achievements, state.achievement.goals), - name: state.session.name, - role: state.session.role, - assessmentOverviews: state.session.assessmentOverviews, - achievementAssessmentOverviews: state.achievement.assessmentOverviews, - users: state.achievement.users, - assessmentConfigs: state.session.assessmentConfigurations -}); +const mapStateToProps: MapStateToProps = state => ({}); const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => bindActionCreators({}, dispatch); From 62b4a24db508560e1f8f30e00a9e5e0a6be02898 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:18:27 +0800 Subject: [PATCH 04/20] Remove unused props and components --- src/pages/achievement/Achievement.tsx | 2 +- .../subcomponents/AchievementDashboard.tsx | 8 +------- .../subcomponents/AchievementDashboardContainer.ts | 14 -------------- 3 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 src/pages/achievement/subcomponents/AchievementDashboardContainer.ts diff --git a/src/pages/achievement/Achievement.tsx b/src/pages/achievement/Achievement.tsx index 26bfd2689d..fc21e00758 100644 --- a/src/pages/achievement/Achievement.tsx +++ b/src/pages/achievement/Achievement.tsx @@ -5,7 +5,7 @@ import { useTypedSelector } from 'src/commons/utils/Hooks'; import { Role } from '../../commons/application/ApplicationTypes'; import NotFound from '../notFound/NotFound'; import AchievementControl from './control/AchievementControlContainer'; -import AchievementDashboard from './subcomponents/AchievementDashboardContainer'; +import AchievementDashboard from './subcomponents/AchievementDashboard'; const Achievement: React.FC = () => { const role = useTypedSelector(state => state.session.role!); diff --git a/src/pages/achievement/subcomponents/AchievementDashboard.tsx b/src/pages/achievement/subcomponents/AchievementDashboard.tsx index c42cc8ee8e..688ad717ae 100644 --- a/src/pages/achievement/subcomponents/AchievementDashboard.tsx +++ b/src/pages/achievement/subcomponents/AchievementDashboard.tsx @@ -27,10 +27,6 @@ import { GoalProgress } from '../../../features/achievement/AchievementTypes'; -export type StateProps = { - id?: number; -}; - /** * Generates components * @@ -52,9 +48,7 @@ export const generateAchievementTasks = ( /> )); -type DashboardProps = StateProps; - -const AchievementDashboard: React.FC = () => { +const AchievementDashboard: React.FC = () => { // default nothing selected const userIdState = useState(undefined); const [selectedUser] = userIdState; diff --git a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts b/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts deleted file mode 100644 index fc727e3a79..0000000000 --- a/src/pages/achievement/subcomponents/AchievementDashboardContainer.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; - -import { OverallState } from '../../../commons/application/ApplicationTypes'; -import AchievementDashboard, { StateProps } from './AchievementDashboard'; - -const mapStateToProps: MapStateToProps = state => ({}); - -const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => - bindActionCreators({}, dispatch); - -const DashboardContainer = connect(mapStateToProps, mapDispatchToProps)(AchievementDashboard); - -export default DashboardContainer; From 16c54e64d31a80fe79c3bc9df35cd2742c09bdd5 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:21:05 +0800 Subject: [PATCH 05/20] Update typing of AchievementControl component --- src/pages/achievement/control/AchievementControl.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/achievement/control/AchievementControl.tsx b/src/pages/achievement/control/AchievementControl.tsx index 23e1c39854..e736d8d9e5 100644 --- a/src/pages/achievement/control/AchievementControl.tsx +++ b/src/pages/achievement/control/AchievementControl.tsx @@ -1,4 +1,4 @@ -import { useEffect, useReducer, useState } from 'react'; +import React, { useEffect, useReducer, useState } from 'react'; import AchievementEditor from '../../../commons/achievement/control/AchievementEditor'; import AchievementPreview from '../../../commons/achievement/control/AchievementPreview'; @@ -21,7 +21,7 @@ export type StateProps = { inferencer: AchievementInferencer; }; -function AchievementControl(props: DispatchProps & StateProps) { +const AchievementControl: React.FC = props => { const { bulkUpdateAchievements, bulkUpdateGoals, @@ -89,6 +89,6 @@ function AchievementControl(props: DispatchProps & StateProps) { ); -} +}; export default AchievementControl; From 2f0c6d8653bbaabaf9b707c7b60aa8d4f9a3b332 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:22:38 +0800 Subject: [PATCH 06/20] Refactor AchievementControl * Remove extra spaces * Destructure props directly in argument --- .../control/AchievementControl.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/pages/achievement/control/AchievementControl.tsx b/src/pages/achievement/control/AchievementControl.tsx index e736d8d9e5..403310dae6 100644 --- a/src/pages/achievement/control/AchievementControl.tsx +++ b/src/pages/achievement/control/AchievementControl.tsx @@ -21,17 +21,15 @@ export type StateProps = { inferencer: AchievementInferencer; }; -const AchievementControl: React.FC = props => { - const { - bulkUpdateAchievements, - bulkUpdateGoals, - getAchievements, - getOwnGoals, - removeAchievement, - removeGoal, - inferencer - } = props; - +const AchievementControl: React.FC = ({ + bulkUpdateAchievements, + bulkUpdateGoals, + getAchievements, + getOwnGoals, + removeAchievement, + removeGoal, + inferencer +}) => { /** * Fetch the latest achievements and goals from backend when the page is rendered */ @@ -82,9 +80,7 @@ const AchievementControl: React.FC = props => {
- -
From c5f15d9e6a12aa2730a6ae98a9f2cf9bcc64f2bb Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:28:53 +0800 Subject: [PATCH 07/20] Use `useDispatch` in AchievementControl --- .../control/AchievementControl.tsx | 65 +++++++++++-------- .../control/AchievementControlContainer.ts | 24 +------ 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/pages/achievement/control/AchievementControl.tsx b/src/pages/achievement/control/AchievementControl.tsx index 403310dae6..f86f3f2db7 100644 --- a/src/pages/achievement/control/AchievementControl.tsx +++ b/src/pages/achievement/control/AchievementControl.tsx @@ -1,42 +1,55 @@ -import React, { useEffect, useReducer, useState } from 'react'; +import React, { useEffect, useMemo, useReducer, useState } from 'react'; +import { useDispatch } from 'react-redux'; import AchievementEditor from '../../../commons/achievement/control/AchievementEditor'; import AchievementPreview from '../../../commons/achievement/control/AchievementPreview'; import GoalEditor from '../../../commons/achievement/control/GoalEditor'; import AchievementInferencer from '../../../commons/achievement/utils/AchievementInferencer'; import { Prompt } from '../../../commons/ReactRouterPrompt'; +import { + bulkUpdateAchievements, + bulkUpdateGoals, + getAchievements, + getOwnGoals, + removeAchievement, + removeGoal +} from '../../../features/achievement/AchievementActions'; import { AchievementContext } from '../../../features/achievement/AchievementConstants'; import { AchievementItem, GoalDefinition } from '../../../features/achievement/AchievementTypes'; -export type DispatchProps = { - bulkUpdateAchievements: (achievements: AchievementItem[]) => void; - bulkUpdateGoals: (goals: GoalDefinition[]) => void; - getAchievements: () => void; - getOwnGoals: () => void; - removeAchievement: (uuid: string) => void; - removeGoal: (uuid: string) => void; -}; - export type StateProps = { inferencer: AchievementInferencer; }; -const AchievementControl: React.FC = ({ - bulkUpdateAchievements, - bulkUpdateGoals, - getAchievements, - getOwnGoals, - removeAchievement, - removeGoal, - inferencer -}) => { +const AchievementControl: React.FC = ({ inferencer }) => { + const dispatch = useDispatch(); + const { + handleBulkUpdateAchievements, + handleBulkUpdateGoals, + handleGetAchievements, + handleGetOwnGoals, + handleRemoveAchievement, + handleRemoveGoal + } = useMemo( + () => ({ + handleBulkUpdateAchievements: (achievement: AchievementItem[]) => + dispatch(bulkUpdateAchievements(achievement)), + handleBulkUpdateGoals: (goals: GoalDefinition[]) => dispatch(bulkUpdateGoals(goals)), + handleGetAchievements: () => dispatch(getAchievements()), + handleGetOwnGoals: () => dispatch(getOwnGoals()), + handleRemoveAchievement: (uuid: string) => dispatch(removeAchievement(uuid)), + handleRemoveGoal: (uuid: string) => dispatch(removeGoal(uuid)) + }), + [dispatch] + ); + /** * Fetch the latest achievements and goals from backend when the page is rendered */ useEffect(() => { - getAchievements(); - getOwnGoals(); - }, [getAchievements, getOwnGoals]); + handleGetAchievements(); + handleGetOwnGoals(); + }, [handleGetAchievements, handleGetOwnGoals]); /** * Monitors changes that are awaiting publish @@ -44,10 +57,10 @@ const AchievementControl: React.FC = ({ const [awaitPublish, setAwaitPublish] = useState(false); const publishChanges = () => { // NOTE: Goals and achievements must exist in the backend before the association can be built - bulkUpdateGoals(inferencer.getAllGoals()); - bulkUpdateAchievements(inferencer.getAllAchievements()); - inferencer.getGoalsToDelete().forEach(removeGoal); - inferencer.getAchievementsToDelete().forEach(removeAchievement); + handleBulkUpdateGoals(inferencer.getAllGoals()); + handleBulkUpdateAchievements(inferencer.getAllAchievements()); + inferencer.getGoalsToDelete().forEach(handleRemoveGoal); + inferencer.getAchievementsToDelete().forEach(handleRemoveAchievement); inferencer.resetToDelete(); setAwaitPublish(false); }; diff --git a/src/pages/achievement/control/AchievementControlContainer.ts b/src/pages/achievement/control/AchievementControlContainer.ts index 10ee15c2e3..bbe985607a 100644 --- a/src/pages/achievement/control/AchievementControlContainer.ts +++ b/src/pages/achievement/control/AchievementControlContainer.ts @@ -3,32 +3,14 @@ import { bindActionCreators, Dispatch } from 'redux'; import AchievementInferencer from '../../../commons/achievement/utils/AchievementInferencer'; import { OverallState } from '../../../commons/application/ApplicationTypes'; -import { - bulkUpdateAchievements, - bulkUpdateGoals, - getAchievements, - getOwnGoals, - removeAchievement, - removeGoal -} from '../../../features/achievement/AchievementActions'; -import AchievementControl, { DispatchProps, StateProps } from './AchievementControl'; +import AchievementControl, { StateProps } from './AchievementControl'; const mapStateToProps: MapStateToProps = state => ({ inferencer: new AchievementInferencer(state.achievement.achievements, state.achievement.goals) }); -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - bulkUpdateAchievements, - bulkUpdateGoals, - getAchievements, - getOwnGoals, - removeAchievement, - removeGoal - }, - dispatch - ); +const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => + bindActionCreators({}, dispatch); const AchievementControlContainer = connect( mapStateToProps, From d1ddaeaf4ea6cd965d74aed51aa09269c84834a7 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:30:39 +0800 Subject: [PATCH 08/20] Use `useSelector` in AchievementControl --- src/pages/achievement/control/AchievementControl.tsx | 11 +++++++---- .../control/AchievementControlContainer.ts | 5 +---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pages/achievement/control/AchievementControl.tsx b/src/pages/achievement/control/AchievementControl.tsx index f86f3f2db7..249be99b99 100644 --- a/src/pages/achievement/control/AchievementControl.tsx +++ b/src/pages/achievement/control/AchievementControl.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useMemo, useReducer, useState } from 'react'; import { useDispatch } from 'react-redux'; +import { useTypedSelector } from 'src/commons/utils/Hooks'; import AchievementEditor from '../../../commons/achievement/control/AchievementEditor'; import AchievementPreview from '../../../commons/achievement/control/AchievementPreview'; @@ -17,11 +18,9 @@ import { import { AchievementContext } from '../../../features/achievement/AchievementConstants'; import { AchievementItem, GoalDefinition } from '../../../features/achievement/AchievementTypes'; -export type StateProps = { - inferencer: AchievementInferencer; -}; +export type StateProps = {}; -const AchievementControl: React.FC = ({ inferencer }) => { +const AchievementControl: React.FC = () => { const dispatch = useDispatch(); const { handleBulkUpdateAchievements, @@ -43,6 +42,10 @@ const AchievementControl: React.FC = ({ inferencer }) => { [dispatch] ); + const inferencer = useTypedSelector( + state => new AchievementInferencer(state.achievement.achievements, state.achievement.goals) + ); + /** * Fetch the latest achievements and goals from backend when the page is rendered */ diff --git a/src/pages/achievement/control/AchievementControlContainer.ts b/src/pages/achievement/control/AchievementControlContainer.ts index bbe985607a..194476e030 100644 --- a/src/pages/achievement/control/AchievementControlContainer.ts +++ b/src/pages/achievement/control/AchievementControlContainer.ts @@ -1,13 +1,10 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; -import AchievementInferencer from '../../../commons/achievement/utils/AchievementInferencer'; import { OverallState } from '../../../commons/application/ApplicationTypes'; import AchievementControl, { StateProps } from './AchievementControl'; -const mapStateToProps: MapStateToProps = state => ({ - inferencer: new AchievementInferencer(state.achievement.achievements, state.achievement.goals) -}); +const mapStateToProps: MapStateToProps = state => ({}); const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => bindActionCreators({}, dispatch); From 61f2bd2140db84a750437036f4f25956b30b4cfd Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:31:31 +0800 Subject: [PATCH 09/20] Remove unused types and components --- src/pages/achievement/Achievement.tsx | 2 +- .../achievement/control/AchievementControl.tsx | 4 +--- .../control/AchievementControlContainer.ts | 17 ----------------- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 src/pages/achievement/control/AchievementControlContainer.ts diff --git a/src/pages/achievement/Achievement.tsx b/src/pages/achievement/Achievement.tsx index fc21e00758..08ad713e80 100644 --- a/src/pages/achievement/Achievement.tsx +++ b/src/pages/achievement/Achievement.tsx @@ -4,7 +4,7 @@ import { useTypedSelector } from 'src/commons/utils/Hooks'; import { Role } from '../../commons/application/ApplicationTypes'; import NotFound from '../notFound/NotFound'; -import AchievementControl from './control/AchievementControlContainer'; +import AchievementControl from './control/AchievementControl'; import AchievementDashboard from './subcomponents/AchievementDashboard'; const Achievement: React.FC = () => { diff --git a/src/pages/achievement/control/AchievementControl.tsx b/src/pages/achievement/control/AchievementControl.tsx index 249be99b99..c83b00162b 100644 --- a/src/pages/achievement/control/AchievementControl.tsx +++ b/src/pages/achievement/control/AchievementControl.tsx @@ -18,9 +18,7 @@ import { import { AchievementContext } from '../../../features/achievement/AchievementConstants'; import { AchievementItem, GoalDefinition } from '../../../features/achievement/AchievementTypes'; -export type StateProps = {}; - -const AchievementControl: React.FC = () => { +const AchievementControl: React.FC = () => { const dispatch = useDispatch(); const { handleBulkUpdateAchievements, diff --git a/src/pages/achievement/control/AchievementControlContainer.ts b/src/pages/achievement/control/AchievementControlContainer.ts deleted file mode 100644 index 194476e030..0000000000 --- a/src/pages/achievement/control/AchievementControlContainer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; - -import { OverallState } from '../../../commons/application/ApplicationTypes'; -import AchievementControl, { StateProps } from './AchievementControl'; - -const mapStateToProps: MapStateToProps = state => ({}); - -const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => - bindActionCreators({}, dispatch); - -const AchievementControlContainer = connect( - mapStateToProps, - mapDispatchToProps -)(AchievementControl); - -export default AchievementControlContainer; From 088e6b6978717fb26a8131b78bac5e77ff7272ad Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:36:55 +0800 Subject: [PATCH 10/20] Use `useDispatch` in GradingEditor --- .../grading/subcomponents/GradingEditor.tsx | 43 ++++++++++++++----- .../subcomponents/GradingEditorContainer.ts | 18 ++------ 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/pages/academy/grading/subcomponents/GradingEditor.tsx b/src/pages/academy/grading/subcomponents/GradingEditor.tsx index 54ac9edb7d..5c37e9d0a1 100644 --- a/src/pages/academy/grading/subcomponents/GradingEditor.tsx +++ b/src/pages/academy/grading/subcomponents/GradingEditor.tsx @@ -10,9 +10,15 @@ import { Pre } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import ReactMde, { ReactMdeProps } from 'react-mde'; +import { useDispatch } from 'react-redux'; +import { + reautogradeAnswer, + submitGrading, + submitGradingAndContinue +} from '../../../../commons/application/actions/SessionActions'; import ControlButton from '../../../../commons/ControlButton'; import Markdown from '../../../../commons/Markdown'; import { Prompt } from '../../../../commons/ReactRouterPrompt'; @@ -24,7 +30,7 @@ import { } from '../../../../commons/utils/notifications/NotificationsHelper'; import { convertParamToInt } from '../../../../commons/utils/ParamParseHelper'; -type GradingEditorProps = DispatchProps & OwnProps; +type GradingEditorProps = OwnProps; type GradingSaveFunction = ( submissionId: number, @@ -33,12 +39,6 @@ type GradingSaveFunction = ( comments?: string ) => void; -export type DispatchProps = { - handleGradingSave: GradingSaveFunction; - handleGradingSaveAndContinue: GradingSaveFunction; - handleReautogradeAnswer: (submissionId: number, questionId: number) => void; -}; - type OwnProps = { solution: number | string | null; questionId: number; @@ -56,6 +56,27 @@ type OwnProps = { const gradingEditorButtonClass = 'grading-editor-button'; const GradingEditor: React.FC = props => { + const dispatch = useDispatch(); + const { handleGradingSave, handleGradingSaveAndContinue, handleReautogradeAnswer } = useMemo( + () => ({ + handleGradingSave: ( + submissionId: number, + questionId: number, + xpAdjustment: number | undefined, + comments?: string + ) => dispatch(submitGrading(submissionId, questionId, xpAdjustment, comments)), + handleGradingSaveAndContinue: ( + submissionId: number, + questionId: number, + xpAdjustment: number | undefined, + comments?: string + ) => dispatch(submitGradingAndContinue(submissionId, questionId, xpAdjustment, comments)), + handleReautogradeAnswer: (submissionId: number, questionId: number) => + dispatch(reautogradeAnswer(submissionId, questionId)) + }), + [dispatch] + ); + /** * A potentially null string which defines the * result for the number XP input. This property being null @@ -140,7 +161,7 @@ const GradingEditor: React.FC = props => { comments?: string ) => { const callback = (): void => { - props.handleGradingSaveAndContinue(submissionId, questionId, xpAdjustment, comments!); + handleGradingSaveAndContinue(submissionId, questionId, xpAdjustment, comments!); }; setCurrentlySaving(true); // TODO: Check (not sure how) if this results in a regression. @@ -159,7 +180,7 @@ const GradingEditor: React.FC = props => { positiveIntent: 'danger' }); if (confirm) { - props.handleReautogradeAnswer(props.submissionId, props.questionId); + handleReautogradeAnswer(props.submissionId, props.questionId); } }; @@ -300,7 +321,7 @@ const GradingEditor: React.FC = props => { diff --git a/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts b/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts index 1a4b8d3e21..07a50e73b3 100644 --- a/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts +++ b/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts @@ -1,22 +1,10 @@ import { connect, MapDispatchToProps } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; -import { - reautogradeAnswer, - submitGrading, - submitGradingAndContinue -} from '../../../../commons/application/actions/SessionActions'; -import GradingEditor, { DispatchProps } from './GradingEditor'; +import GradingEditor from './GradingEditor'; -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - handleGradingSave: submitGrading, - handleGradingSaveAndContinue: submitGradingAndContinue, - handleReautogradeAnswer: reautogradeAnswer - }, - dispatch - ); +const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => + bindActionCreators({}, dispatch); const GradingEditorContainer = connect(null, mapDispatchToProps)(GradingEditor); From d07460db7166607139547e457d4af308083d6edf Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:40:26 +0800 Subject: [PATCH 11/20] Remove unused types and components Also refactored typings for dispatch handlers for readability. --- .../grading/subcomponents/GradingEditor.tsx | 32 +++++++------------ .../subcomponents/GradingEditorContainer.ts | 11 ------- .../subcomponents/GradingWorkspace.tsx | 2 +- 3 files changed, 13 insertions(+), 32 deletions(-) delete mode 100644 src/pages/academy/grading/subcomponents/GradingEditorContainer.ts diff --git a/src/pages/academy/grading/subcomponents/GradingEditor.tsx b/src/pages/academy/grading/subcomponents/GradingEditor.tsx index 5c37e9d0a1..57dc6a69ca 100644 --- a/src/pages/academy/grading/subcomponents/GradingEditor.tsx +++ b/src/pages/academy/grading/subcomponents/GradingEditor.tsx @@ -30,8 +30,6 @@ import { } from '../../../../commons/utils/notifications/NotificationsHelper'; import { convertParamToInt } from '../../../../commons/utils/ParamParseHelper'; -type GradingEditorProps = OwnProps; - type GradingSaveFunction = ( submissionId: number, questionId: number, @@ -39,7 +37,7 @@ type GradingSaveFunction = ( comments?: string ) => void; -type OwnProps = { +type Props = { solution: number | string | null; questionId: number; submissionId: number; @@ -55,25 +53,19 @@ type OwnProps = { const gradingEditorButtonClass = 'grading-editor-button'; -const GradingEditor: React.FC = props => { +const GradingEditor: React.FC = props => { const dispatch = useDispatch(); const { handleGradingSave, handleGradingSaveAndContinue, handleReautogradeAnswer } = useMemo( - () => ({ - handleGradingSave: ( - submissionId: number, - questionId: number, - xpAdjustment: number | undefined, - comments?: string - ) => dispatch(submitGrading(submissionId, questionId, xpAdjustment, comments)), - handleGradingSaveAndContinue: ( - submissionId: number, - questionId: number, - xpAdjustment: number | undefined, - comments?: string - ) => dispatch(submitGradingAndContinue(submissionId, questionId, xpAdjustment, comments)), - handleReautogradeAnswer: (submissionId: number, questionId: number) => - dispatch(reautogradeAnswer(submissionId, questionId)) - }), + () => + ({ + handleGradingSave: (...args) => dispatch(submitGrading(...args)), + handleGradingSaveAndContinue: (...args) => dispatch(submitGradingAndContinue(...args)), + handleReautogradeAnswer: (...args) => dispatch(reautogradeAnswer(...args)) + }) satisfies { + handleGradingSave: GradingSaveFunction; + handleGradingSaveAndContinue: GradingSaveFunction; + handleReautogradeAnswer: (submissionId: number, questionId: number) => void; + }, [dispatch] ); diff --git a/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts b/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts deleted file mode 100644 index 07a50e73b3..0000000000 --- a/src/pages/academy/grading/subcomponents/GradingEditorContainer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { connect, MapDispatchToProps } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; - -import GradingEditor from './GradingEditor'; - -const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => - bindActionCreators({}, dispatch); - -const GradingEditorContainer = connect(null, mapDispatchToProps)(GradingEditor); - -export default GradingEditorContainer; diff --git a/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx b/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx index 8c15ef3c1d..3797712db1 100644 --- a/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx +++ b/src/pages/academy/grading/subcomponents/GradingWorkspace.tsx @@ -57,7 +57,7 @@ import { SideContentTab, SideContentType } from '../../../../commons/sideContent import Workspace, { WorkspaceProps } from '../../../../commons/workspace/Workspace'; import { WorkspaceLocation, WorkspaceState } from '../../../../commons/workspace/WorkspaceTypes'; import { AnsweredQuestion } from '../../../../features/grading/GradingTypes'; -import GradingEditor from './GradingEditorContainer'; +import GradingEditor from './GradingEditor'; type GradingWorkspaceProps = { submissionId: number; From d1cedfac4a8e6c71d30a95d3bd425b49bb0c00d2 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:44:03 +0800 Subject: [PATCH 12/20] Refactor `DefaultChapterSelect` Improves typing and consistency with the rest of the codebase. --- .../subcomponents/DefaultChapterSelect.tsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx index d7e876a0c1..ff82918223 100644 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx +++ b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx @@ -2,7 +2,7 @@ import { Button, Classes, Dialog, Intent, Menu, MenuItem } from '@blueprintjs/co import { IconNames } from '@blueprintjs/icons'; import { ItemListRenderer, ItemRenderer, Select } from '@blueprintjs/select'; import { Chapter, Variant } from 'js-slang/dist/types'; -import * as React from 'react'; +import React, { useCallback, useState } from 'react'; import { SALanguage, @@ -11,7 +11,7 @@ import { } from '../../../../commons/application/ApplicationTypes'; import ControlButton from '../../../../commons/ControlButton'; -export type DefaultChapterSelectProps = DispatchProps & StateProps; +type Props = DispatchProps & StateProps; export type DispatchProps = { handleUpdateSublanguage: (sublang: SALanguage) => void; @@ -22,36 +22,37 @@ export type StateProps = { sourceVariant: Variant; }; -const DefaultChapterSelect: React.FunctionComponent = props => { - const { handleUpdateSublanguage } = props; - const { sourceChapter, sourceVariant } = props; +const DefaultChapterSelect: React.FC = ({ + handleUpdateSublanguage, + sourceChapter, + sourceVariant +}) => { + const [chosenSublang, setSublanguage] = useState(sourceLanguages[0]); + const [isDialogOpen, setDialogState] = useState(false); - const [chosenSublang, setSublanguage] = React.useState(sourceLanguages[0]); - const [isDialogOpen, setDialogState] = React.useState(false); - - const handleOpenDialog = React.useCallback( + const handleOpenDialog = useCallback( (choice: SALanguage) => { setDialogState(true); setSublanguage(choice); }, [setDialogState, setSublanguage] ); - const handleCloseDialog = React.useCallback(() => { + const handleCloseDialog = useCallback(() => { setDialogState(false); }, [setDialogState]); - const handleConfirmDialog = React.useCallback(() => { + const handleConfirmDialog = useCallback(() => { setDialogState(false); handleUpdateSublanguage(chosenSublang); }, [chosenSublang, setDialogState, handleUpdateSublanguage]); - const chapterRenderer: ItemRenderer = React.useCallback( + const chapterRenderer: ItemRenderer = useCallback( (lang, { handleClick }) => ( ), [] ); - const chapterListRenderer: ItemListRenderer = React.useCallback( + const chapterListRenderer: ItemListRenderer = useCallback( ({ itemsParentRef, renderItem, items }) => { const defaultChoices = items.filter(({ variant }) => variant === Variant.DEFAULT); const variantChoices = items.filter(({ variant }) => variant !== Variant.DEFAULT); From a11d6eac120fc0ab6b964a9b8d7b2b700ac15c31 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:46:42 +0800 Subject: [PATCH 13/20] Use `useDispatch` in DefaultChapterSelect --- .../subcomponents/DefaultChapterSelect.tsx | 20 +++++++++---------- .../DefaultChapterSelectContainer.ts | 12 +++-------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx index ff82918223..05911790c3 100644 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx +++ b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx @@ -3,6 +3,7 @@ import { IconNames } from '@blueprintjs/icons'; import { ItemListRenderer, ItemRenderer, Select } from '@blueprintjs/select'; import { Chapter, Variant } from 'js-slang/dist/types'; import React, { useCallback, useState } from 'react'; +import { useDispatch } from 'react-redux'; import { SALanguage, @@ -10,26 +11,25 @@ import { styliseSublanguage } from '../../../../commons/application/ApplicationTypes'; import ControlButton from '../../../../commons/ControlButton'; +import { changeSublanguage } from '../../../../commons/workspace/WorkspaceActions'; -type Props = DispatchProps & StateProps; - -export type DispatchProps = { - handleUpdateSublanguage: (sublang: SALanguage) => void; -}; +type Props = StateProps; export type StateProps = { sourceChapter: Chapter; sourceVariant: Variant; }; -const DefaultChapterSelect: React.FC = ({ - handleUpdateSublanguage, - sourceChapter, - sourceVariant -}) => { +const DefaultChapterSelect: React.FC = ({ sourceChapter, sourceVariant }) => { const [chosenSublang, setSublanguage] = useState(sourceLanguages[0]); const [isDialogOpen, setDialogState] = useState(false); + const dispatch = useDispatch(); + const handleUpdateSublanguage = useCallback( + (sublang: SALanguage) => dispatch(changeSublanguage(sublang)), + [dispatch] + ); + const handleOpenDialog = useCallback( (choice: SALanguage) => { setDialogState(true); diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts index d1b6116973..5779136d20 100644 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts +++ b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts @@ -3,8 +3,7 @@ import { bindActionCreators, Dispatch } from 'redux'; import Constants from 'src/commons/utils/Constants'; import { OverallState } from '../../../../commons/application/ApplicationTypes'; -import { changeSublanguage } from '../../../../commons/workspace/WorkspaceActions'; -import DefaultChapterSelect, { DispatchProps, StateProps } from './DefaultChapterSelect'; +import DefaultChapterSelect, { StateProps } from './DefaultChapterSelect'; const mapStateToProps: MapStateToProps = state => ({ // Temporarily load the defaults when the course configuration fetch has yet to return @@ -12,13 +11,8 @@ const mapStateToProps: MapStateToProps = state => sourceVariant: state.session.sourceVariant || Constants.defaultSourceVariant }); -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - handleUpdateSublanguage: changeSublanguage - }, - dispatch - ); +const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => + bindActionCreators({}, dispatch); const DefaultChapterSelectContainer = connect( mapStateToProps, From eb05830193a8ff4ab43bbf9ddc03d1171a284601 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:49:34 +0800 Subject: [PATCH 14/20] Use `useSession` in DefaultChapterSelect It encompasses all the selector needs of the component. --- .../subcomponents/DefaultChapterSelect.tsx | 17 +++++++++++------ .../DefaultChapterSelectContainer.ts | 7 +------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx index 05911790c3..54d23ae2cc 100644 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx +++ b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx @@ -1,9 +1,11 @@ import { Button, Classes, Dialog, Intent, Menu, MenuItem } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { ItemListRenderer, ItemRenderer, Select } from '@blueprintjs/select'; -import { Chapter, Variant } from 'js-slang/dist/types'; +import { Variant } from 'js-slang/dist/types'; import React, { useCallback, useState } from 'react'; import { useDispatch } from 'react-redux'; +import Constants from 'src/commons/utils/Constants'; +import { useSession } from 'src/commons/utils/Hooks'; import { SALanguage, @@ -15,15 +17,18 @@ import { changeSublanguage } from '../../../../commons/workspace/WorkspaceAction type Props = StateProps; -export type StateProps = { - sourceChapter: Chapter; - sourceVariant: Variant; -}; +export type StateProps = {}; -const DefaultChapterSelect: React.FC = ({ sourceChapter, sourceVariant }) => { +const DefaultChapterSelect: React.FC = () => { const [chosenSublang, setSublanguage] = useState(sourceLanguages[0]); const [isDialogOpen, setDialogState] = useState(false); + const { + // Temporarily load the defaults when the course configuration fetch has yet to return + sourceChapter = Constants.defaultSourceChapter, + sourceVariant = Constants.defaultSourceVariant + } = useSession(); + const dispatch = useDispatch(); const handleUpdateSublanguage = useCallback( (sublang: SALanguage) => dispatch(changeSublanguage(sublang)), diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts index 5779136d20..8feb61c26b 100644 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts +++ b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts @@ -1,15 +1,10 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; -import Constants from 'src/commons/utils/Constants'; import { OverallState } from '../../../../commons/application/ApplicationTypes'; import DefaultChapterSelect, { StateProps } from './DefaultChapterSelect'; -const mapStateToProps: MapStateToProps = state => ({ - // Temporarily load the defaults when the course configuration fetch has yet to return - sourceChapter: state.session.sourceChapter || Constants.defaultSourceChapter, - sourceVariant: state.session.sourceVariant || Constants.defaultSourceVariant -}); +const mapStateToProps: MapStateToProps = state => ({}); const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => bindActionCreators({}, dispatch); From 96eb49530359a1f981182bac366c69c8ff2ea8aa Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:54:06 +0800 Subject: [PATCH 15/20] Remove unused types and components --- .../academy/groundControl/GroundControl.tsx | 2 +- .../subcomponents/DefaultChapterSelect.tsx | 6 +----- .../DefaultChapterSelectContainer.ts | 17 ----------------- 3 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts diff --git a/src/pages/academy/groundControl/GroundControl.tsx b/src/pages/academy/groundControl/GroundControl.tsx index 06a09c2af8..3f0b31577a 100644 --- a/src/pages/academy/groundControl/GroundControl.tsx +++ b/src/pages/academy/groundControl/GroundControl.tsx @@ -12,7 +12,7 @@ import { AssessmentOverview } from '../../../commons/assessment/AssessmentTypes'; import ContentDisplay from '../../../commons/ContentDisplay'; -import DefaultChapterSelect from './subcomponents/DefaultChapterSelectContainer'; +import DefaultChapterSelect from './subcomponents/DefaultChapterSelect'; import DeleteCell from './subcomponents/GroundControlDeleteCell'; import Dropzone from './subcomponents/GroundControlDropzone'; import EditCell from './subcomponents/GroundControlEditCell'; diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx index 54d23ae2cc..e0ba2022a2 100644 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx +++ b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelect.tsx @@ -15,11 +15,7 @@ import { import ControlButton from '../../../../commons/ControlButton'; import { changeSublanguage } from '../../../../commons/workspace/WorkspaceActions'; -type Props = StateProps; - -export type StateProps = {}; - -const DefaultChapterSelect: React.FC = () => { +const DefaultChapterSelect: React.FC = () => { const [chosenSublang, setSublanguage] = useState(sourceLanguages[0]); const [isDialogOpen, setDialogState] = useState(false); diff --git a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts b/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts deleted file mode 100644 index 8feb61c26b..0000000000 --- a/src/pages/academy/groundControl/subcomponents/DefaultChapterSelectContainer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; - -import { OverallState } from '../../../../commons/application/ApplicationTypes'; -import DefaultChapterSelect, { StateProps } from './DefaultChapterSelect'; - -const mapStateToProps: MapStateToProps = state => ({}); - -const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => - bindActionCreators({}, dispatch); - -const DefaultChapterSelectContainer = connect( - mapStateToProps, - mapDispatchToProps -)(DefaultChapterSelect); - -export default DefaultChapterSelectContainer; From 4f43bac2166d3d3c210ee6a1c01d02c864b6d5ed Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 00:57:40 +0800 Subject: [PATCH 16/20] Fix incorrect `sourceChapter` type in app state --- src/commons/application/types/SessionTypes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commons/application/types/SessionTypes.ts b/src/commons/application/types/SessionTypes.ts index fbf069b11f..f2806898ef 100644 --- a/src/commons/application/types/SessionTypes.ts +++ b/src/commons/application/types/SessionTypes.ts @@ -104,7 +104,7 @@ export type SessionState = { readonly enableAchievements?: boolean; readonly enableSourcecast?: boolean; readonly enableStories?: boolean; - readonly sourceChapter?: number; + readonly sourceChapter?: Chapter; readonly sourceVariant?: Variant; readonly moduleHelpText?: string; readonly assetsPrefix?: string; From bafe1ac2ce7e4fe4a1bf2dfec41134e73be7d7cc Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:02:17 +0800 Subject: [PATCH 17/20] Refactor `MissionCreator` Improves typing and consistency with the rest of the codebase. --- src/commons/missionCreator/MissionCreator.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commons/missionCreator/MissionCreator.tsx b/src/commons/missionCreator/MissionCreator.tsx index 57250b6bb7..46f285126f 100644 --- a/src/commons/missionCreator/MissionCreator.tsx +++ b/src/commons/missionCreator/MissionCreator.tsx @@ -1,6 +1,6 @@ import { FileInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import * as React from 'react'; +import React, { useEffect, useState } from 'react'; import { parseString } from 'xml2js'; import { @@ -27,11 +27,11 @@ type OwnProps = { updateEditingOverview: (overview: AssessmentOverview) => void; }; -function MissionCreator(props: MissionCreatorProps) { - const [fileInputText, setFileInputText] = React.useState('Import XML'); +const MissionCreator: React.FC = props => { + const [fileInputText, setFileInputText] = useState('Import XML'); let fileReader: FileReader | undefined = undefined; - React.useEffect(() => { + useEffect(() => { const assessment = retrieveLocalAssessment(); if (assessment) { props.newAssessment(assessment); @@ -95,6 +95,6 @@ function MissionCreator(props: MissionCreatorProps) { ); -} +}; export default MissionCreator; From bebe6f9ee5d3fd5f92f88d7453e2dc4b68a818d2 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:05:28 +0800 Subject: [PATCH 18/20] Use `useDispatch` in MissionCreator --- src/commons/missionCreator/MissionCreator.tsx | 25 +++++++++++-------- .../missionCreator/MissionCreatorContainer.ts | 12 +++------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/commons/missionCreator/MissionCreator.tsx b/src/commons/missionCreator/MissionCreator.tsx index 46f285126f..19351d0e8d 100644 --- a/src/commons/missionCreator/MissionCreator.tsx +++ b/src/commons/missionCreator/MissionCreator.tsx @@ -1,8 +1,10 @@ import { FileInput } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; import { parseString } from 'xml2js'; +import { updateAssessment } from '../application/actions/SessionActions'; import { Assessment, AssessmentOverview, @@ -17,11 +19,7 @@ import { storeLocalAssessmentOverview } from '../XMLParser/XMLParserHelper'; -type MissionCreatorProps = DispatchProps & OwnProps; - -export type DispatchProps = { - newAssessment: (assessment: Assessment) => void; -}; +type MissionCreatorProps = OwnProps; type OwnProps = { updateEditingOverview: (overview: AssessmentOverview) => void; @@ -31,13 +29,18 @@ const MissionCreator: React.FC = props => { const [fileInputText, setFileInputText] = useState('Import XML'); let fileReader: FileReader | undefined = undefined; + const dispatch = useDispatch(); + const newAssessment = useCallback( + (assessment: Assessment) => dispatch(updateAssessment(assessment)), + [dispatch] + ); + useEffect(() => { const assessment = retrieveLocalAssessment(); if (assessment) { - props.newAssessment(assessment); + newAssessment(assessment); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.newAssessment]); + }, [newAssessment]); const handleFileRead = (file: any) => (e: any) => { if (!fileReader) { @@ -54,7 +57,7 @@ const MissionCreator: React.FC = props => { props.updateEditingOverview(entireAssessment[0]); storeLocalAssessment(entireAssessment[1]); - props.newAssessment(entireAssessment[1]); + newAssessment(entireAssessment[1]); setFileInputText('Success!'); } catch (err) { console.log(err); @@ -77,7 +80,7 @@ const MissionCreator: React.FC = props => { storeLocalAssessmentOverview(overviewTemplate()); props.updateEditingOverview(overviewTemplate()); storeLocalAssessment(assessmentTemplate()); - props.newAssessment(assessmentTemplate()); + newAssessment(assessmentTemplate()); }; return ( diff --git a/src/commons/missionCreator/MissionCreatorContainer.ts b/src/commons/missionCreator/MissionCreatorContainer.ts index f6726f6411..dd53f447dd 100644 --- a/src/commons/missionCreator/MissionCreatorContainer.ts +++ b/src/commons/missionCreator/MissionCreatorContainer.ts @@ -1,18 +1,12 @@ import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; -import { updateAssessment } from '../application/actions/SessionActions'; -import MissionCreator, { DispatchProps } from './MissionCreator'; +import MissionCreator from './MissionCreator'; const mapStateToProps: MapStateToProps<{}, any, {}> = (state, ownProps) => ownProps; -const mapDispatchToProps: MapDispatchToProps = (dispatch: Dispatch) => - bindActionCreators( - { - newAssessment: updateAssessment - }, - dispatch - ); +const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => + bindActionCreators({}, dispatch); const MissionCreatorContainer = connect(mapStateToProps, mapDispatchToProps)(MissionCreator); From bd3e973f1d44804ad170e75426986e34eabda216 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:05:59 +0800 Subject: [PATCH 19/20] Remove console log statements --- src/commons/missionCreator/MissionCreator.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/commons/missionCreator/MissionCreator.tsx b/src/commons/missionCreator/MissionCreator.tsx index 19351d0e8d..08361642ef 100644 --- a/src/commons/missionCreator/MissionCreator.tsx +++ b/src/commons/missionCreator/MissionCreator.tsx @@ -49,7 +49,6 @@ const MissionCreator: React.FC = props => { const content = fileReader.result; if (content) { parseString(content, (err: any, result: any) => { - console.dir(file); try { const entireAssessment: [AssessmentOverview, Assessment] = makeEntireAssessment(result); entireAssessment[0].fileName = file.name.slice(0, -4); @@ -60,7 +59,6 @@ const MissionCreator: React.FC = props => { newAssessment(entireAssessment[1]); setFileInputText('Success!'); } catch (err) { - console.log(err); setFileInputText('Invalid XML!'); } }); From 19009572b6afc8cee47ff99989b90b337f15bbf8 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sun, 4 Feb 2024 01:07:41 +0800 Subject: [PATCH 20/20] Remove unused types and components --- src/commons/missionCreator/MissionCreator.tsx | 6 ++---- .../missionCreator/MissionCreatorContainer.ts | 13 ------------- src/pages/missionControl/MissionControl.tsx | 2 +- 3 files changed, 3 insertions(+), 18 deletions(-) delete mode 100644 src/commons/missionCreator/MissionCreatorContainer.ts diff --git a/src/commons/missionCreator/MissionCreator.tsx b/src/commons/missionCreator/MissionCreator.tsx index 08361642ef..3d6da510bb 100644 --- a/src/commons/missionCreator/MissionCreator.tsx +++ b/src/commons/missionCreator/MissionCreator.tsx @@ -19,13 +19,11 @@ import { storeLocalAssessmentOverview } from '../XMLParser/XMLParserHelper'; -type MissionCreatorProps = OwnProps; - -type OwnProps = { +type Props = { updateEditingOverview: (overview: AssessmentOverview) => void; }; -const MissionCreator: React.FC = props => { +const MissionCreator: React.FC = props => { const [fileInputText, setFileInputText] = useState('Import XML'); let fileReader: FileReader | undefined = undefined; diff --git a/src/commons/missionCreator/MissionCreatorContainer.ts b/src/commons/missionCreator/MissionCreatorContainer.ts deleted file mode 100644 index dd53f447dd..0000000000 --- a/src/commons/missionCreator/MissionCreatorContainer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux'; -import { bindActionCreators, Dispatch } from 'redux'; - -import MissionCreator from './MissionCreator'; - -const mapStateToProps: MapStateToProps<{}, any, {}> = (state, ownProps) => ownProps; - -const mapDispatchToProps: MapDispatchToProps<{}, {}> = (dispatch: Dispatch) => - bindActionCreators({}, dispatch); - -const MissionCreatorContainer = connect(mapStateToProps, mapDispatchToProps)(MissionCreator); - -export default MissionCreatorContainer; diff --git a/src/pages/missionControl/MissionControl.tsx b/src/pages/missionControl/MissionControl.tsx index 6098769f10..40270bb2ab 100644 --- a/src/pages/missionControl/MissionControl.tsx +++ b/src/pages/missionControl/MissionControl.tsx @@ -11,7 +11,7 @@ import { EditingOverviewCard } from '../../commons/editingOverviewCard/EditingOv import EditingWorkspace, { EditingWorkspaceProps } from '../../commons/editingWorkspace/EditingWorkspace'; -import MissionCreator from '../../commons/missionCreator/MissionCreatorContainer'; +import MissionCreator from '../../commons/missionCreator/MissionCreator'; import Constants from '../../commons/utils/Constants'; import { convertParamToInt } from '../../commons/utils/ParamParseHelper'; import { retrieveLocalAssessmentOverview } from '../../commons/XMLParser/XMLParserHelper';