UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

276 lines (248 loc) • 7.29 kB
/** @jsx jsx */ import {Component} from 'react' import PropTypes from 'prop-types' import minBy from 'lodash/fp/minBy' import maxBy from 'lodash/fp/maxBy' import {jsx} from '@instructure/emotion' import t from '@instructure/quiz-i18n/format-message' import {ItemBodyWrapper} from '@instructure/quiz-rce/components/ItemBodyWrapper/index' import {NoResponse} from '@instructure/quiz-results-feedback' import Oval from '../common/Oval' import Square from '../common/Square' import Polygon from '../common/Polygon' import TargetContainer from '../common/TargetContainer' import generateStyle from './styles' import generateComponentTheme from './theme' import FeedbackPopover from './FeedbackPopover/index' import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides' const CORRECT = 'correct' const INCORRECT = 'incorrect' const UNKNOWN = 'unknown' const coordinates = PropTypes.shape({ x: PropTypes.number, y: PropTypes.number, }) function getDrawingType(type) { switch (type) { case 'oval': return Oval case 'polygon': return Polygon default: return Square } } /** --- category: HotSpot --- HotSpot Result component ```jsx_example <SettingsSwitcher locales={LOCALES}> <HotSpotResult itemBody='Which of these players is David Ferrer' interactionData={{ imageUrl: 'https://i.ytimg.com/vi/LgkAebhJ7iE/maxresdefault.jpg' }} scoredData={{ value: { type: 'square', resultScore: 0, userResponse: { x: 0.99, y: 0.5 }, correctAnswer: [ { x: 0.1, y: 0.3 }, { x: 0.2, y: 0.2 } ] } }} /> </SettingsSwitcher> ``` **/ @withStyleOverrides(generateStyle, generateComponentTheme) export default class HotSpotResult extends Component { static displayName = 'HotSpotResult' static componentId = `Quizzes${this.displayName}` static propTypes = { interactionData: PropTypes.shape({ imageUrl: PropTypes.string.isRequired, }).isRequired, itemBody: PropTypes.string.isRequired, scoredData: PropTypes.shape({ value: PropTypes.shape({ resultScore: PropTypes.number, type: PropTypes.string, correctAnswer: PropTypes.oneOfType([ PropTypes.shape({ value: PropTypes.arrayOf( PropTypes.shape({ type: PropTypes.string, coordinates: PropTypes.arrayOf(coordinates), }), ), }), PropTypes.arrayOf(coordinates), ]), userResponse: PropTypes.oneOfType([ PropTypes.arrayOf( PropTypes.shape({ x: PropTypes.number, y: PropTypes.number, correct: PropTypes.bool, }), ), coordinates, ]), }), }).isRequired, makeStyles: PropTypes.func, styles: PropTypes.object, } componentDidMount() { this.props.makeStyles() } componentDidUpdate() { this.props.makeStyles() } get correctAnswers() { const {correctAnswer} = this.props.scoredData.value || {} if (!correctAnswer) { return [] } if (Array.isArray(correctAnswer) && !correctAnswer?.value) { return [ { type: this.props.scoredData.value.type, coordinates: correctAnswer, }, ] } return correctAnswer?.value && !Array.isArray(correctAnswer.value) ? [correctAnswer?.value] : correctAnswer?.value } get status() { const {resultScore} = this.props.scoredData.value || {} if (resultScore === 1) { return CORRECT } if (resultScore <= 0) { return INCORRECT } return UNKNOWN } get userResponse() { const {userResponse, resultScore} = this.props.scoredData.value || {} const userResponseArray = Array.isArray(userResponse) ? userResponse : [userResponse] return userResponseArray?.map(response => { if (response?.correct == null) { return { ...response, correct: this.status, } } return { ...response, correct: resultScore != null ? (response.correct ? CORRECT : INCORRECT) : UNKNOWN, } }) } getFeedbackRendering(hotspot) { const {type} = hotspot if (!this.correctAnswers.length) { return Function.prototype } switch (type) { case 'oval': return this.renderScoringDataFeedbackOval case 'polygon': return this.renderScoringDataFeedbackPolygon default: return this.renderScoringDataFeedbackSquare } } renderScoringDataFeedbackPolygon = (currentCoordinates, flipVertically) => ( <FeedbackPopover coordinates={(flipVertically ? maxBy : minBy)('y', currentCoordinates)} flipVertically={flipVertically} /> ) renderScoringDataFeedbackOval = ([{x: x1, y: y1}, {x: x2, y: y2}], flipVertically) => ( <FeedbackPopover coordinates={{ x: (x1 + x2) * 0.5, y: (flipVertically ? Math.max : Math.min)(y1, y2), }} flipVertically={flipVertically} /> ) renderScoringDataFeedbackSquare = ([{x: x1, y: y1}, {x: x2, y: y2}], flipVertically) => ( <FeedbackPopover coordinates={{ x: (x1 + x2) * 0.5, y: (flipVertically ? Math.max : Math.min)(y1, y2), }} flipVertically={flipVertically} /> ) renderUserResponseFeedback = (coordinates, flipVertically, status) => { let text if (!coordinates) { const {userResponse} = this.props.scoredData.value || {} const responseHidden = typeof userResponse === 'undefined' return <NoResponse responseHidden={responseHidden} status={this.status} /> } switch (status) { case CORRECT: text = t('Selected Answer - Correct') break case INCORRECT: text = t('Selected Answer - Incorrect') break case UNKNOWN: text = t('Selected Answer') } return ( <FeedbackPopover coordinates={coordinates} flipVertically={flipVertically} text={text} status={status} targetContent={ <div css={this.props.styles.userResponseWrapper}> <div css={this.props.styles.userResponseTarget} /> </div> } /> ) } prepareHotspotsWithFeedback = () => { if (!this.correctAnswers?.length) { return this.userResponse?.map(() => ({ coordinates: [], drawingType: getDrawingType(), renderScoringDataFeedback: Function.prototype, })) } return this.correctAnswers?.map(hotspot => ({ coordinates: hotspot.coordinates, drawingType: getDrawingType(hotspot.type), renderScoringDataFeedback: this.getFeedbackRendering(hotspot), })) } render() { const hotspots = this.prepareHotspotsWithFeedback() || [] return ( <ItemBodyWrapper itemBody={this.props.itemBody}> <TargetContainer readOnly results hotspots={hotspots} url={this.props.interactionData.imageUrl} userResponseCoordinate={this.userResponse} renderUserResponseFeedback={this.renderUserResponseFeedback} /> </ItemBodyWrapper> ) } }