-
Notifications
You must be signed in to change notification settings - Fork 174
Grading comments #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Grading comments #162
Changes from all commits
b024ae3
5275bdb
7fa8ef0
7cd2f97
a69951e
b45a7f9
ffb6eb3
6eb14fb
da96de0
8724bf1
de7fc50
0744368
5cc14c2
c4df92d
9663b27
8d49ea2
86bfa7f
759793d
e734b8c
a65ec55
ef89a07
33064f5
cdf5b63
b6fa3e1
78c76c0
e59a4e2
9106590
91720dd
43efb67
7407d1d
e457bc1
f11e3de
861ff5a
409e0cd
14196ff
705a2e3
b401b49
2e52bdc
4d563dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| import { ButtonGroup, Icon, NumericInput, Position } from '@blueprintjs/core' | ||
| import { IconNames } from '@blueprintjs/icons' | ||
| import * as React from 'react' | ||
| import ReactMde, { ReactMdeTypes } from 'react-mde' | ||
| import * as Showdown from 'showdown' | ||
| import { controlButton } from '../../commons' | ||
|
|
||
| type GradingEditorProps = DispatchProps & OwnProps & StateProps | ||
|
|
||
| export type DispatchProps = { | ||
| handleGradingCommentsChange: (s: string) => void | ||
| handleGradingXPChange: (i: number | undefined) => void | ||
| handleGradingInputSave: (s: string, i: number | undefined) => void | ||
| } | ||
|
|
||
| export type OwnProps = { | ||
| maximumXP: number | ||
| } | ||
|
|
||
| export type StateProps = { | ||
| gradingCommentsValue: string | ||
| gradingXP: number | undefined | ||
| } | ||
|
|
||
| /** | ||
| * Keeps track of the current editor state, | ||
| * as well as the XP in the numeric input. | ||
| * | ||
| * XP can be undefined to show the hint text. | ||
| */ | ||
| type State = { | ||
| mdeState: ReactMdeTypes.MdeState | ||
| XPInput: number | undefined | ||
| } | ||
|
|
||
| class GradingEditor extends React.Component<GradingEditorProps, State> { | ||
| private converter: Showdown.Converter | ||
|
|
||
| constructor(props: GradingEditorProps) { | ||
| super(props) | ||
| this.state = { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Local state is used to keep track of everything here, because the editor already uses it. Upon unmounting, the values are dispatched. |
||
| mdeState: { | ||
| markdown: this.props.gradingCommentsValue | ||
| }, | ||
| XPInput: this.props.gradingXP | ||
| } | ||
| /** | ||
| * The markdown-to-html converter for the editor. | ||
| */ | ||
| this.converter = new Showdown.Converter({ | ||
| tables: true, | ||
| simplifiedAutoLink: true, | ||
| strikethrough: true, | ||
| tasklists: true, | ||
| openLinksInNewWindow: true | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * Update the redux state's grading comments value, using the latest | ||
| * value in the local state. | ||
| */ | ||
| public componentWillUnmount() { | ||
| this.props.handleGradingCommentsChange(this.state.mdeState.markdown!) | ||
| this.props.handleGradingXPChange(this.state.XPInput) | ||
| } | ||
|
|
||
| public render() { | ||
| return ( | ||
| <> | ||
| <div className="grading-editor-input-parent"> | ||
| <ButtonGroup fill={true}> | ||
| <NumericInput | ||
| onValueChange={this.onXPInputChange} | ||
| value={this.state.XPInput} | ||
| buttonPosition={Position.LEFT} | ||
| placeholder="XP here" | ||
| min={0} | ||
| max={this.props.maximumXP} | ||
| /> | ||
| {controlButton('Save', IconNames.FLOPPY_DISK, this.onClickSaveButton)} | ||
| </ButtonGroup> | ||
| </div> | ||
| <div className="react-mde-parent"> | ||
| <ReactMde | ||
| buttonContentOptions={{ | ||
| iconProvider: this.blueprintIconProvider | ||
| }} | ||
| layout={'vertical'} | ||
| onChange={this.handleValueChange} | ||
| editorState={this.state.mdeState} | ||
| generateMarkdownPreview={this.generateMarkdownPreview} | ||
| /> | ||
| </div> | ||
| </> | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * A custom icons provider. It uses a bulky mapping function | ||
| * defined below. | ||
| * | ||
| * See {@link https://github.com/andrerpena/react-mde} | ||
| */ | ||
| private blueprintIconProvider(name: string) { | ||
| return <Icon icon={faToBlueprintIconMapping(name)} /> | ||
| } | ||
|
|
||
| private onClickSaveButton = () => { | ||
| this.props.handleGradingInputSave(this.state.mdeState.markdown!, this.state.XPInput) | ||
| } | ||
|
|
||
| private onXPInputChange = (newValue: number) => { | ||
| this.setState({ | ||
| ...this.state, | ||
| XPInput: newValue | ||
| }) | ||
| } | ||
|
|
||
| private handleValueChange = (mdeState: ReactMdeTypes.MdeState) => { | ||
| this.setState({ mdeState }) | ||
| } | ||
|
|
||
| private generateMarkdownPreview = (markdown: string) => | ||
| Promise.resolve(this.converter.makeHtml(markdown)) | ||
| } | ||
|
|
||
| /** | ||
| * Maps FontAwesome5 icon names to blueprintjs counterparts. | ||
| * This is to reduce the number of dependencies on icons, and | ||
| * keep a more consistent look. | ||
| */ | ||
| const faToBlueprintIconMapping = (name: string) => { | ||
| switch (name) { | ||
| case 'heading': | ||
| return IconNames.HEADER | ||
| case 'bold': | ||
| return IconNames.BOLD | ||
| case 'italic': | ||
| return IconNames.ITALIC | ||
| case 'strikethrough': | ||
| return IconNames.STRIKETHROUGH | ||
| case 'link': | ||
| return IconNames.LINK | ||
| case 'quote-right': | ||
| return IconNames.CITATION | ||
| case 'code': | ||
| return IconNames.CODE | ||
| case 'image': | ||
| return IconNames.MEDIA | ||
| case 'list-ul': | ||
| return IconNames.PROPERTIES | ||
| case 'list-ol': | ||
| return IconNames.NUMBERED_LIST | ||
| case 'tasks': | ||
| return IconNames.TICK | ||
| default: | ||
| return IconNames.HELP | ||
| } | ||
| } | ||
|
|
||
| export default GradingEditor | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import * as React from 'react' | ||
| import { NavLink } from 'react-router-dom' | ||
|
|
||
| import { GradingOverview } from './gradingShape' | ||
|
|
||
| type GradingNavLinkProps = { | ||
| data: GradingOverview | ||
| } | ||
|
|
||
| /** | ||
| * Used to render a link in the table that displays GradingOverviews. | ||
| * This is a fully fledged component (not SFC) by specification in | ||
| * ag-grid. | ||
| * | ||
| * See {@link https://www.ag-grid.com/example-react-dynamic} | ||
| */ | ||
| class GradingNavLink extends React.Component<GradingNavLinkProps, {}> { | ||
| constructor(props: GradingNavLinkProps) { | ||
| super(props) | ||
| } | ||
|
|
||
| public render() { | ||
| return ( | ||
| <NavLink to={`/academy/grading/${this.props.data.submissionId}`} activeClassName="pt-active"> | ||
| {this.props.data.graded ? 'Done' : 'Not Graded'} | ||
| </NavLink> | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| export default GradingNavLink |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,10 +44,10 @@ class Assessment extends React.Component<IAssessmentProps, {}> { | |
| */ | ||
| public componentWillMount() { | ||
| const assessmentId = stringParamToInt(this.props.match.params.assessmentId) | ||
| const questionId = stringParamToInt(this.props.match.params.questionId) | ||
| if (assessmentId === null || questionId === null) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fixes a bug that applies for Grading but not Assessment. I changed it for consistency. The original predicate fails when questionid is null but submissionId is non-null (because we have not assigned 0 to it yet). In the case of assessments, the 0 value is provided by the card, since it must provide a value in the link within. |
||
| if (assessmentId === null) { | ||
| return | ||
| } | ||
| const questionId = stringParamToInt(this.props.match.params.questionId)! | ||
|
|
||
| if ( | ||
| this.props.storedAssessmentId !== assessmentId || | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I change this to UPLOAD_GRADING_INPUT instead? It's only use is to call a saga.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think save is fine