Skip to content

Commit

Permalink
Merge pull request #42 from openedx/bw/routes
Browse files Browse the repository at this point in the history
Bw/routes
  • Loading branch information
muselesscreator authored Sep 25, 2023
2 parents a254965 + c4504c0 commit 03f1b2f
Show file tree
Hide file tree
Showing 19 changed files with 1,105 additions and 868 deletions.
1,554 changes: 767 additions & 787 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"@edx/paragon": "^20.20.0",
"@edx/react-unit-test-utils": "1.7.0",
"@edx/tinymce-language-selector": "1.1.0",
"@edx/typescript-config": "^1.0.1",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-regular-svg-icons": "5.15.4",
Expand Down
33 changes: 28 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,41 @@ import { Spinner } from '@edx/paragon';

import { useIsORAConfigLoaded, useIsPageDataLoaded } from 'data/services/lms/hooks/selectors';

import AppContainer from 'views/AppContainer';
import ModalContainer from 'views/ModalContainer';
import PeerAssessmentView from 'views/PeerAssessmentView';
import SelfAssessmentView from 'views/SelfAssessmentView';
import StudentTrainingView from 'views/StudentTrainingView';
import SubmissionView from 'views/SubmissionView';
import XBlockView from 'views/XBlockView';
import messages from './messages';
import routes from './routes';

const RouterRoot = () => {
const { formatMessage } = useIntl();
const appRoute = (route, Component) => (
<Route path={route} element={<AppContainer Component={Component} />} />
);
const modalRoute = (route, Component, title) => (
<Route path={route} element={<ModalContainer {...{ title, Component }} />} />
);

const embeddedRoutes = [
<Route path={routes.embedded.xblock} element={<XBlockView />} />,
modalRoute(routes.embedded.peerAssessment, PeerAssessmentView, 'ORA Peer Assessment'),
modalRoute(routes.embedded.selfAssessment, SelfAssessmentView, 'ORA Self Assessment'),
modalRoute(routes.embedded.studentTraining, StudentTrainingView, 'ORA Student Training'),
modalRoute(routes.embedded.submission, SubmissionView, 'ORA Submission'),
<Route path={routes.embedded.root} element={<ErrorPage message={formatMessage(messages.error404Message)} />} />,
];
const baseRoutes = [
appRoute(routes.xblock, PeerAssessmentView),
appRoute(routes.peerAssessment, PeerAssessmentView),
appRoute(routes.selfAssessment, SelfAssessmentView),
appRoute(routes.studentTraining, StudentTrainingView),
appRoute(routes.submission, SubmissionView),
<Route path={routes.root} element={<ErrorPage message={formatMessage(messages.error404Message)} />} />,
];

const isConfigLoaded = useIsORAConfigLoaded();
const isPageLoaded = useIsPageDataLoaded();
Expand All @@ -33,11 +59,8 @@ const RouterRoot = () => {

return (
<Routes>
<Route path={routes.peerAssessment} element={<PeerAssessmentView />} />
<Route path={routes.selfAssessment} element={<SelfAssessmentView />} />
<Route path={routes.studentTraining} element={<StudentTrainingView />} />
<Route path={routes.submission} element={<SubmissionView />} />
<Route path={routes.root} element={<ErrorPage message={formatMessage(messages.error404Message)} />} />
{embeddedRoutes}
{baseRoutes}
</Routes>
);
};
Expand Down
138 changes: 138 additions & 0 deletions src/components/ProgressBar/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useLocation } from 'react-router-dom';
import classNames from 'classnames';

import { useIntl } from '@edx/frontend-platform/i18n';
import { Navbar, Nav, Icon } from '@edx/paragon';
import { Locked, CheckCircle, Error } from '@edx/paragon/icons';

import {
useAssessmentStepConfig,
useProgressData,
useIsPageDataLoaded,
} from 'data/services/lms/hooks/selectors';

import messages from './messages';
import './index.scss';

export const ProgressStep = ({
children,
href,
isActive,
isEnabled,
isComplete,
isError,
number,
}) => {
let icon = <Icon className="nav-icon" src={Locked} />;
if (isComplete) {
icon = <Icon className="nav-icon" src={CheckCircle} />;
} else if (isError) {
icon = <Icon className="nav-icon text-danger-300" src={Error} />;
} else if (number) {
icon = <span className="nav-icon number-icon">{number}</span>;
}
return (
<Nav.Link
href={href}
disabled={!isEnabled}
className={classNames(
'ora-progress-nav',
'px-4',
{ 'is-active': isActive },
)}
>
{icon}
{children}
</Nav.Link>
);
};
ProgressStep.defaultProps = {
href: '#',
isActive: false,
isEnabled: false,
isComplete: false,
isError: false,
number: null,
};
ProgressStep.propTypes = {
children: PropTypes.node.isRequired,
href: PropTypes.string,
isActive: PropTypes.bool,
isEnabled: PropTypes.bool,
isError: PropTypes.bool,
isComplete: PropTypes.bool,
number: PropTypes.number,
};

export const SubmissionStep = () => {
const { formatMessage } = useIntl();
return (
<ProgressStep>{formatMessage(messages.createSubmission)}</ProgressStep>
);
};

export const TrainingStep = () => {
const { formatMessage } = useIntl();
return (
<ProgressStep>{formatMessage(messages.practice)}</ProgressStep>
);
};

export const SelfAssessStep = () => {
const { formatMessage } = useIntl();
return (
<ProgressStep>{formatMessage(messages.selfAssess)}</ProgressStep>
);
};

export const PeerAssessStep = () => {
const { formatMessage } = useIntl();
return (
<ProgressStep>{formatMessage(messages.peerAssess)}</ProgressStep>
);
};

export const MyGradeStep = () => {
const { formatMessage } = useIntl();
return (
<ProgressStep>{formatMessage(messages.myGrade)}</ProgressStep>
);
};

export const ProgressBar = () => {
const stepConfig = useAssessmentStepConfig();
const progress = useProgressData();
const isLoaded = useIsPageDataLoaded();
const location = useLocation();
if (!isLoaded) {
return null;
}
console.log({
stepConfig, progress, location,
});
return (
<Navbar>
<Navbar.Collapse className="ora-progress-nav-group">
<hr className="ora-progress-divider" />
<SubmissionStep />
{stepConfig.order.map(step => {
if (step === 'peer') {
return <PeerAssessStep />;
}
if (step === 'training') {
return <TrainingStep />;
}
if (step === 'self') {
return <SelfAssessStep />;
}
return null;
})}
<MyGradeStep />
</Navbar.Collapse>
</Navbar>
);
};

export default ProgressBar;
26 changes: 26 additions & 0 deletions src/components/ProgressBar/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.ora-progress-nav-group {
display: flex;
justify-content: space-between;
.ora-progress-divider {
position: absolute;
width: 95%;
}
.ora-progress-nav {
background-color: white;
z-index: 5;
font-size: 1.125rem;
&.is-active {
border-bottom: 2px solid black;
}
}
.nav-icon {
display: inline-block;
margin-inline-end: 0.5rem;
margin-left: -0.25rem;
}
.number-icon {
background-color: black;
color: white;
border-radius: 100%;
}
}
31 changes: 31 additions & 0 deletions src/components/ProgressBar/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
createSubmission: {
id: 'ora-grading.ProgressBar.createSubmission',
defaultMessage: 'Create your response',
description: 'Create response progress indicator',
},
practice: {
id: 'ora-grading.ProgressBar.practice',
defaultMessage: 'Practice grading',
description: 'Student training step progress indicator',
},
selfAssess: {
id: 'ora-grading.ProgressBar.selfAssess',
defaultMessage: 'Grade yourself',
description: 'Self assessment step progress indicator',
},
peerAssess: {
id: 'ora-grading.ProgressBar.peerAssess',
defaultMessage: 'Grade peers',
description: 'Peer assessment step progress indicator',
},
myGrade: {
id: 'ora-grading.ProgressBar.myGrade',
defaultMessage: 'My grade',
description: 'My Grade step progress indicator',
},
});

export default messages;
20 changes: 11 additions & 9 deletions src/data/services/lms/fakeData/oraConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,17 @@ export const createORAConfig = ({
leaderboard_config,
});

export const tinyMCEConfig = {
submission_config: {
...submissionConfig,
text_response_config: {
...submissionConfig.text_response_config,
editor_type: 'tinymce',
},
},
};

export default {
assessmentText: createORAConfig(),
assessmentTinyMCE: createORAConfig({
submission_config: {
...submissionConfig,
text_response_config: {
...submissionConfig.text_response_config,
editor_type: 'tinymce',
},
},
}),
assessmentTinyMCE: createORAConfig(tinyMCEConfig),
};
2 changes: 1 addition & 1 deletion src/data/services/lms/fakeData/pageData/submission.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default {
has_received_grade: false,
}),
response: createSubmissionResponse({
text_responses: ['', ''],
text_responses: ['response 1', 'response 2'],
uploaded_files: [],
}),
}),
Expand Down
1 change: 0 additions & 1 deletion src/data/services/lms/hooks/data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ describe('lms data hooks', () => {
});

const mockUseQueryForORA = (hasData) => {
console.log({ useQuery });
when(useQuery)
.calledWith(expect.objectContaining({ queryKey: [queryKeys.oraConfig] }))
.mockImplementationOnce(mockUseQuery(hasData));
Expand Down
16 changes: 11 additions & 5 deletions src/data/services/lms/hooks/data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { useQuery, useMutation } from '@tanstack/react-query';

import { useMatch } from 'react-router-dom';
import { camelCaseObject } from '@edx/frontend-platform';
Expand Down Expand Up @@ -40,12 +40,11 @@ export const usePageData = (): types.QueryData<types.PageData> => {
const returnData = assessmentData ? {
...camelCaseObject(assessmentData),
rubric: {
optionsSelected: {...assessmentData.rubric.options_selected},
criterionFeedback: {...assessmentData.rubric.criterion_feedback},
optionsSelected: { ...assessmentData.rubric.options_selected },
criterionFeedback: { ...assessmentData.rubric.criterion_feedback },
overallFeedback: assessmentData.rubric.overall_feedback,
},
}: {};

} : {};
return Promise.resolve(returnData as any);
},
});
Expand All @@ -54,3 +53,10 @@ export const usePageData = (): types.QueryData<types.PageData> => {
data,
};
};

export const useSubmitResponse = () => useMutation({
mutationFn: (response) => {
console.log({ submit: response });
return Promise.resolve();
},
});
6 changes: 4 additions & 2 deletions src/data/services/lms/hooks/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ export const useIsPageDataLoaded = (): boolean => (
data.usePageData().status === 'success'
);

export const usePageData = (): types.PageData => data.usePageData().data;
export const usePageData = (): types.PageData => data.usePageData()?.data;

export const useSubmissionTeamInfo = (): types.SubmissionTeamData => usePageData().submission.teamInfo;
export const useProgressData = (): types.ProgressData => usePageData()?.progress;

export const useSubmissionTeamInfo = (): types.SubmissionTeamData => usePageData()?.submission.teamInfo;

export const useSubmissionStatus = (): types.SubmissionStatusData => {
const {
Expand Down
9 changes: 9 additions & 0 deletions src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export default {
embedded: {
xblock: '/embedded/xblock/:id',
peerAssessment: '/embedded/peer_assessment/:id',
selfAssessment: '/embedded/self_assessment/:id',
studentTraining: '/embedded/student_training/:id',
submission: '/embedded/submission/:id',
root: '/embedded/*',
},
xblock: '/xblock/:id',
peerAssessment: '/peer_assessment/:id',
selfAssessment: '/self_assessment/:id',
studentTraining: '/student_training/:id',
Expand Down
17 changes: 17 additions & 0 deletions src/views/AppContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';

/* The purpose of this component is to wrap views with a header/footer for situations
* where we need to run non-embedded
*/

const AppContainer = ({ Component }) => (
<div>
<Component />
</div>
);
AppContainer.propTypes = {
Component: PropTypes.elementType.isRequired,
};

export default AppContainer;
Loading

0 comments on commit 03f1b2f

Please sign in to comment.