Skip to content

Commit d8818ef

Browse files
author
Ben Warzeski
authored
Merge pull request #7 from openedx/bw/base_componentry
Bw/base componentry
2 parents 019ff34 + b0a3a27 commit d8818ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+21833
-1500
lines changed

package-lock.json

Lines changed: 19867 additions & 1440 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,30 @@
3838
"@edx/frontend-component-header": "4.2.3",
3939
"@edx/frontend-platform": "4.5.1",
4040
"@edx/paragon": "^20.20.0",
41-
"@edx/react-unit-test-utils": "npm:@edx/[email protected]",
41+
"@edx/react-unit-test-utils": "1.5.3",
42+
"@edx/tinymce-language-selector": "1.1.0",
4243
"@fortawesome/fontawesome-svg-core": "1.2.36",
4344
"@fortawesome/free-brands-svg-icons": "5.15.4",
4445
"@fortawesome/free-regular-svg-icons": "5.15.4",
4546
"@fortawesome/free-solid-svg-icons": "5.15.4",
4647
"@fortawesome/react-fontawesome": "0.2.0",
48+
"@tanstack/react-query": "^4.29.25",
49+
"@tinymce/tinymce-react": "3.8.4",
50+
"classnames": "^2.3.2",
4751
"core-js": "3.31.1",
52+
"filesize": "^8.0.6",
4853
"prop-types": "15.8.1",
4954
"query-string": "^8.1.0",
5055
"react": "16.14.0",
5156
"react-dom": "16.14.0",
52-
"react-query": "^3.39.3",
5357
"react-redux": "7.2.9",
5458
"react-router": "5.3.4",
5559
"react-router-dom": "5.3.4",
5660
"redux": "4.2.1",
5761
"redux-devtools-extension": "^2.13.9",
5862
"redux-logger": "^3.0.6",
59-
"regenerator-runtime": "0.13.11"
63+
"regenerator-runtime": "0.13.11",
64+
"tinymce": "5.10.7"
6065
},
6166
"devDependencies": {
6267
"@edx/browserslist-config": "^1.1.1",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
5+
import filesize from 'filesize';
6+
7+
import messages from './messages';
8+
9+
const FileMetaDisplay = ({ name, description, size }) => (
10+
<>
11+
{console.log({ name, description, size })}
12+
<div className="file-meta-option">
13+
<strong><FormattedMessage {...messages.filePopoverNameTitle} /></strong>
14+
<br />
15+
{name}
16+
</div>
17+
<div className="file-meta-option">
18+
<strong><FormattedMessage {...messages.filePopoverDescriptionTitle} /></strong>
19+
<br />
20+
{description}
21+
</div>
22+
<div className="file-meta-option">
23+
<strong><FormattedMessage {...messages.fileSizeTitle} /></strong>
24+
<br />
25+
{typeof (size) === 'number' ? filesize(size) : 'Unknown'}
26+
</div>
27+
</>
28+
);
29+
30+
FileMetaDisplay.defaultProps = {
31+
description: '',
32+
size: null,
33+
};
34+
35+
FileMetaDisplay.propTypes = {
36+
name: PropTypes.string.isRequired,
37+
description: PropTypes.string,
38+
size: PropTypes.number,
39+
};
40+
41+
export default FileMetaDisplay;

src/components/FileUpload/index.jsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import { DataTable, Dropzone } from '@edx/paragon';
3+
4+
import { useSubmissionResponse } from 'data/services/lms/hooks/selectors';
5+
6+
import { useIntl } from '@edx/frontend-platform/i18n';
7+
import filesize from 'filesize';
8+
9+
import messages from './messages';
10+
11+
const FileUpload = () => {
12+
const { uploadedFiles } = useSubmissionResponse();
13+
const { formatMessage } = useIntl();
14+
return (
15+
<div>
16+
<h3>File Upload</h3>
17+
{uploadedFiles && (
18+
<>
19+
<b>Uploaded Files</b>
20+
<DataTable
21+
itemCount={uploadedFiles.length}
22+
data={uploadedFiles.map(file => ({
23+
...file,
24+
size: typeof file.size === 'number' ? filesize(file.size) : 'Unknown',
25+
}))}
26+
columns={[
27+
{
28+
Header: formatMessage(messages.fileNameTitle),
29+
accessor: 'name',
30+
},
31+
{
32+
Header: formatMessage(messages.fileDescriptionTitle),
33+
accessor: 'description',
34+
},
35+
{
36+
Header: formatMessage(messages.fileSizeTitle),
37+
accessor: 'size',
38+
},
39+
]}
40+
/>
41+
</>
42+
)}
43+
<Dropzone />
44+
</div>
45+
);
46+
};
47+
48+
export default FileUpload;

src/components/FileUpload/messages.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { defineMessages } from '@edx/frontend-platform/i18n';
2+
3+
const messages = defineMessages({
4+
fileNameTitle: {
5+
id: 'ora-grading.FileContent.fileNameTitle',
6+
defaultMessage: 'File Name',
7+
description: ' title for file name',
8+
},
9+
fileDescriptionTitle: {
10+
id: 'ora-grading.FileCellContent.fileDescriptionTitle',
11+
defaultMessage: 'File Description',
12+
description: ' title for file description',
13+
},
14+
fileSizeTitle: {
15+
id: 'ora-grading.FileCellContent.fileSizeTitle',
16+
defaultMessage: 'File Size',
17+
description: ' title for file size',
18+
},
19+
});
20+
21+
export default messages;

src/components/InfoPopover/index.jsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import {
5+
OverlayTrigger,
6+
Popover,
7+
Icon,
8+
IconButton,
9+
} from '@edx/paragon';
10+
import { InfoOutline } from '@edx/paragon/icons';
11+
import { useIntl } from '@edx/frontend-platform/i18n';
12+
13+
import { nullMethod } from 'hooks';
14+
15+
import messages from './messages';
16+
17+
/**
18+
* <InfoPopover />
19+
*/
20+
export const InfoPopover = ({ onClick, children }) => {
21+
const { formatMessage } = useIntl();
22+
return (
23+
<OverlayTrigger
24+
trigger="focus"
25+
placement="right-end"
26+
flip
27+
overlay={(
28+
<Popover id="info-popover" className="overlay-help-popover">
29+
<Popover.Content>{children}</Popover.Content>
30+
</Popover>
31+
)}
32+
>
33+
<IconButton
34+
className="esg-help-icon"
35+
src={InfoOutline}
36+
alt={formatMessage(messages.altText)}
37+
iconAs={Icon}
38+
onClick={onClick}
39+
/>
40+
</OverlayTrigger>
41+
);
42+
};
43+
44+
InfoPopover.defaultProps = {
45+
onClick: nullMethod,
46+
};
47+
InfoPopover.propTypes = {
48+
onClick: PropTypes.func,
49+
children: PropTypes.oneOfType([
50+
PropTypes.arrayOf(PropTypes.node),
51+
PropTypes.node,
52+
]).isRequired,
53+
};
54+
55+
export default InfoPopover;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineMessages } from '@edx/frontend-platform/i18n';
2+
3+
const messages = defineMessages({
4+
altText: {
5+
id: 'ora-grading.InfoPopover.alt-text',
6+
defaultMessage: 'Display more info',
7+
description: 'Info popover trigger element alt-text',
8+
},
9+
});
10+
11+
export default messages;

src/components/Prompt/index.jsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { useORAConfigData } from 'data/services/lms/hooks/selectors';
5+
6+
export const Prompt = ({ promptIndex }) => {
7+
const { prompts } = useORAConfigData();
8+
return (
9+
<div className="m-1 p-1">
10+
<h3>Prompt {promptIndex + 1}</h3>
11+
<div dangerouslySetInnerHTML={{ __html: prompts[promptIndex] }} />
12+
</div>
13+
);
14+
};
15+
16+
Prompt.propTypes = {
17+
promptIndex: PropTypes.number.isRequired,
18+
};
19+
20+
export default Prompt;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { Form } from '@edx/paragon';
5+
import { useIntl } from '@edx/frontend-platform/i18n';
6+
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
7+
8+
import { feedbackRequirement } from 'data/services/lms/constants';
9+
import messages from './messages';
10+
11+
export const stateKeys = StrictDict({
12+
value: 'value',
13+
});
14+
15+
/**
16+
* <CriterionFeedback />
17+
*/
18+
const CriterionFeedback = ({
19+
criterion,
20+
}) => {
21+
const [value, setValue] = useKeyedState(stateKeys.value, '');
22+
const { formatMessage } = useIntl();
23+
24+
if (
25+
!criterion.feedbackEnabled
26+
|| criterion.feedbackRequired === feedbackRequirement.disabled
27+
) {
28+
return null;
29+
}
30+
31+
const onChange = ({ target }) => { setValue(target.value); };
32+
let commentMessage = formatMessage(messages.addComments);
33+
if (criterion.feedbackRequired === feedbackRequirement.optional) {
34+
commentMessage += ` ${formatMessage(messages.optional)}`;
35+
}
36+
37+
const isInvalid = value === '';
38+
39+
return (
40+
<Form.Group isInvalid={isInvalid}>
41+
<Form.Control
42+
as="textarea"
43+
className="criterion-feedback feedback-input"
44+
floatingLabel={commentMessage}
45+
value={value}
46+
onChange={onChange}
47+
/>
48+
{isInvalid && (
49+
<Form.Control.Feedback type="invalid" className="feedback-error-msg">
50+
{formatMessage(messages.criterionFeedbackError)}
51+
</Form.Control.Feedback>
52+
)}
53+
</Form.Group>
54+
);
55+
};
56+
57+
CriterionFeedback.propTypes = {
58+
criterion: PropTypes.shape({
59+
feedbackEnabled: PropTypes.bool,
60+
feedbackRequired: PropTypes.string,
61+
}).isRequired,
62+
};
63+
64+
export default CriterionFeedback;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import { Form } from '@edx/paragon';
5+
import { useIntl } from '@edx/frontend-platform/i18n';
6+
import { StrictDict, useKeyedState } from '@edx/react-unit-test-utils';
7+
8+
import messages from './messages';
9+
10+
export const stateKeys = StrictDict({
11+
value: 'value',
12+
});
13+
14+
/**
15+
* <RadioCriterion />
16+
*/
17+
const RadioCriterion = ({
18+
isGrading,
19+
criterion,
20+
}) => {
21+
const [value, setValue] = useKeyedState(stateKeys.value, '');
22+
const { formatMessage } = useIntl();
23+
const onChange = ({ target }) => { setValue(target.value); };
24+
const isInvalid = value === '';
25+
26+
return (
27+
<Form.RadioSet name={criterion.name} value={value}>
28+
{criterion.options.map((option) => (
29+
<Form.Radio
30+
className="criteria-option"
31+
key={option.name}
32+
value={option.name}
33+
description={formatMessage(messages.optionPoints, { points: option.points })}
34+
onChange={onChange}
35+
disabled={!isGrading}
36+
>
37+
{option.name}
38+
</Form.Radio>
39+
))}
40+
{isInvalid && (
41+
<Form.Control.Feedback type="invalid" className="feedback-error-msg">
42+
{formatMessage(messages.rubricSelectedError)}
43+
</Form.Control.Feedback>
44+
)}
45+
</Form.RadioSet>
46+
);
47+
};
48+
49+
RadioCriterion.propTypes = {
50+
isGrading: PropTypes.bool.isRequired,
51+
criterion: PropTypes.shape({
52+
name: PropTypes.string,
53+
options: PropTypes.arrayOf(PropTypes.shape({
54+
name: PropTypes.string,
55+
description: PropTypes.string,
56+
points: PropTypes.number,
57+
})),
58+
}).isRequired,
59+
};
60+
61+
export default RadioCriterion;

0 commit comments

Comments
 (0)