FeedbackPanel.jsx 4.4 KB
Newer Older
Thomas Roos's avatar
Thomas Roos committed
1
import React from 'react'
Thomas Roos's avatar
Thomas Roos committed
2

3 4
import Notification from 'react-bulma-notification'

Thomas Roos's avatar
Thomas Roos committed
5 6
import * as api from '../../api.jsx'

7
import withShortcuts from '../ShortcutBinder.jsx'
Thomas Roos's avatar
Thomas Roos committed
8
import FeedbackBlock from './FeedbackBlock.jsx'
Thomas Roos's avatar
Thomas Roos committed
9 10

class FeedbackPanel extends React.Component {
Hugo Kerstens's avatar
Hugo Kerstens committed
11 12
  feedbackBlock = React.createRef();

13 14 15
  state = {
    remark: '',
    problemID: null,
Hugo Kerstens's avatar
Hugo Kerstens committed
16 17 18 19 20
    submissionID: null,
    selectedFeedbackIndex: null
  }

  componentDidMount = () => {
Hugo Kerstens's avatar
Hugo Kerstens committed
21
    this.props.bindShortcut(['up', 'k'], (event) => {
Hugo Kerstens's avatar
Hugo Kerstens committed
22 23 24
      event.preventDefault()
      this.prevOption()
    })
Hugo Kerstens's avatar
Hugo Kerstens committed
25
    this.props.bindShortcut(['down', 'j'], (event) => {
Hugo Kerstens's avatar
Hugo Kerstens committed
26 27 28
      event.preventDefault()
      this.nextOption()
    })
Hugo Kerstens's avatar
Hugo Kerstens committed
29
    this.props.bindShortcut(['space'], (event) => {
Hugo Kerstens's avatar
Hugo Kerstens committed
30 31 32
      event.preventDefault()
      this.toggleSelectedOption()
    })
33
  }
Thomas Roos's avatar
Thomas Roos committed
34

35 36 37
  static getDerivedStateFromProps (nextProps, prevState) {
    if (prevState.problemID !== nextProps.problem.id || prevState.submissionID !== nextProps.submissionID) {
      return {
38
        remark: nextProps.grading && nextProps.solution.remark,
39
        problemID: nextProps.problem.id,
Hugo Kerstens's avatar
Hugo Kerstens committed
40 41
        submissionID: nextProps.submissionID,
        selectedFeedbackIndex: null
42 43 44
      }
    } else return null
  }
Thomas Roos's avatar
Thomas Roos committed
45

46
  setOptionIndex = (newIndex) => {
Hugo Kerstens's avatar
Hugo Kerstens committed
47
    if (this.props.problem.feedback.length === 0) return
48 49
    let length = this.props.problem.feedback.length
    newIndex = ((newIndex % length) + length) % length
Hugo Kerstens's avatar
Hugo Kerstens committed
50 51 52 53 54
    this.setState({
      selectedFeedbackIndex: newIndex
    })
  }

55 56 57 58 59
  prevOption = () => {
    let index = this.state.selectedFeedbackIndex !== null ? this.state.selectedFeedbackIndex : 0
    this.setOptionIndex(index - 1)
  }

Hugo Kerstens's avatar
Hugo Kerstens committed
60
  nextOption = () => {
61 62
    let index = this.state.selectedFeedbackIndex !== null ? this.state.selectedFeedbackIndex : -1
    this.setOptionIndex(index + 1)
Hugo Kerstens's avatar
Hugo Kerstens committed
63 64 65 66 67 68 69 70
  }

  toggleSelectedOption = () => {
    if (this.feedbackBlock.current) {
      this.feedbackBlock.current.toggle()
    }
  }

71
  saveRemark = () => {
72
    if (!this.props.solution.graded_at && this.state.remark.replace(/\s/g, '').length === 0) return
73 74 75 76
    api.post('solution/' + this.props.examID + '/' + this.props.submissionID + '/' + this.props.problem.id, {
      remark: this.state.remark,
      graderID: this.props.graderID
    })
Anton Akhmerov's avatar
Anton Akhmerov committed
77
      .then(success => {
78
        this.props.updateSubmission()
Anton Akhmerov's avatar
Anton Akhmerov committed
79
        if (!success) Notification.error('Remark not saved!')
Thomas Roos's avatar
Thomas Roos committed
80
      })
81
  }
Thomas Roos's avatar
Thomas Roos committed
82

83 84 85 86 87
  changeRemark = (event) => {
    this.setState({
      remark: event.target.value
    })
  }
88

89
  render () {
90 91 92
    const blockURI = this.props.examID + '/' + this.props.submissionID + '/' + this.props.problem.id

    let totalScore = 0
93
    if (this.props.grading) {
94 95 96 97
      for (let i = 0; i < this.props.solution.feedback.length; i++) {
        const probIndex = this.props.problem.feedback.findIndex(fb => fb.id === this.props.solution.feedback[i])
        if (probIndex >= 0) totalScore += this.props.problem.feedback[probIndex].score
      }
98 99
    }

Hugo Kerstens's avatar
Hugo Kerstens committed
100 101 102
    let selectedFeedbackId = this.state.selectedFeedbackIndex !== null &&
      this.props.problem.feedback[this.state.selectedFeedbackIndex].id

103
    return (
104
      <React.Fragment>
105 106 107 108
        {this.props.grading &&
          <p className='panel-heading'>
            Total:&nbsp;<b>{totalScore}</b>
          </p>}
109
        {this.props.problem.feedback.map((feedback, index) =>
Thomas Roos's avatar
Thomas Roos committed
110
          <FeedbackBlock key={feedback.id} uri={blockURI} graderID={this.props.graderID}
111
            feedback={feedback} checked={this.props.grading && this.props.solution.feedback.includes(feedback.id)}
Hugo Kerstens's avatar
Hugo Kerstens committed
112
            editFeedback={() => this.props.editFeedback(feedback)} updateSubmission={this.props.updateSubmission}
113
            ref={(selectedFeedbackId === feedback.id) ? this.feedbackBlock : null} grading={this.props.grading}
114
            selected={selectedFeedbackId === feedback.id} showIndex={this.props.showTooltips} index={index + 1} />
115
        )}
116 117 118 119 120
        {this.props.grading &&
          <div className='panel-block'>
            <textarea className='textarea' rows='2' placeholder='remark' value={this.state.remark} onBlur={this.saveRemark} onChange={this.changeRemark} />
          </div>
        }
121
        <div className='panel-block'>
122
          <button className='button is-link is-outlined is-fullwidth' onClick={() => this.props.editFeedback()}>
123 124 125 126 127 128
            <span className='icon is-small'>
              <i className='fa fa-plus' />
            </span>
            <span>option</span>
          </button>
        </div>
129
      </React.Fragment>
130 131
    )
  }
Thomas Roos's avatar
Thomas Roos committed
132
}
Hugo Kerstens's avatar
Hugo Kerstens committed
133
export default withShortcuts(FeedbackPanel)