UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

218 lines (193 loc) 5.99 kB
/** @jsx jsx */ import {Component} from 'react' import PropTypes from 'prop-types' import ReactDOM from 'react-dom' import minBy from 'lodash/fp/minBy' import {jsx} from '@instructure/emotion' import generateStyle from './styles' import generateComponentTheme from './theme' import t from '@instructure/quiz-i18n/format-message' import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides' @withStyleOverrides(generateStyle, generateComponentTheme) export default class TargetContainer extends Component { static displayName = 'TargetContainer' static componentId = `Quizzes${this.displayName}` static propTypes = { hotspots: PropTypes.arrayOf( PropTypes.shape({ coordinates: PropTypes.array, drawingType: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), renderScoringDataFeedback: PropTypes.func, }), ), readOnly: PropTypes.bool, handleSetCoordinates: PropTypes.func, justifyContent: PropTypes.oneOf(['left', 'center']), renderUserResponseFeedback: PropTypes.func, results: PropTypes.bool, url: PropTypes.string.isRequired, userResponseCoordinate: PropTypes.arrayOf( PropTypes.shape({ x: PropTypes.number, y: PropTypes.number, correct: PropTypes.string, }), ), makeStyles: PropTypes.func, styles: PropTypes.object, onFocus: PropTypes.func, onBlur: PropTypes.func, } static defaultProps = { justifyContent: 'center', readOnly: false, results: false, handleSetCoordinates: void 0, renderUserResponseFeedback: void 0, userResponseCoordinate: [], } state = { imageWidth: 0, imageHeight: 0, } canvas = null image = null componentDidMount() { window.addEventListener('resize', this.updateImageDimensions) this.props.makeStyles() } componentWillUnmount() { window.removeEventListener('resize', this.updateImageDimensions) this.props.makeStyles() } // =========== // HELPERS // =========== convertCoordinate = item => ({ x: item.x * this.state.imageWidth, y: item.y * this.state.imageHeight, }) currentCoordinates = coordinates => { if (!coordinates || !coordinates.length) return null return coordinates.map(this.convertCoordinate) } validCoordinates(coordinates) { if (!coordinates) return false const {x, y, correct} = coordinates return x != null && y != null && correct != null } getResponseDetails = (index, feedbackCoordinates) => { let flipResponse = false let responseCoordinates = null let isCorrect = null const userResponse = this.props.userResponseCoordinate?.[index] if (this.validCoordinates(userResponse)) { responseCoordinates = this.convertCoordinate(userResponse) isCorrect = userResponse.correct flipResponse = this.shouldFlipResponse(responseCoordinates, feedbackCoordinates) } return {responseCoordinates, isCorrect, flipResponse} } shouldFlipResponse = (responseCoordinates, feedbackCoordinates) => { if (feedbackCoordinates) { const min = minBy('y', feedbackCoordinates) return min && responseCoordinates.y > min.y } return true } // =========== // HANDLERS // =========== handleCanvasRef = node => { this.canvas = node } handleImageRef = node => { this.image = node } updateImageDimensions = () => { const rectObject = ReactDOM.findDOMNode(this.image).getBoundingClientRect() this.setState({ imageWidth: rectObject.width, imageHeight: rectObject.height, }) } handleImageLoad = event => { this.setState({ imageWidth: event.target.offsetWidth, imageHeight: event.target.offsetHeight, }) } // =========== // RENDER // =========== renderCanvas = () => { const {hotspots} = this.props if (!hotspots?.length) return null if (hotspots?.length > 0) { return hotspots.map(hotspot => { const DrawingType = hotspot.drawingType const convertedCoordinates = this.currentCoordinates(hotspot.coordinates) return ( <DrawingType key={JSON.stringify(hotspot.coordinates)} coordinates={convertedCoordinates || []} readOnly={this.props.readOnly} height={this.state.imageHeight} handleSetCoordinates={this.props.handleSetCoordinates} width={this.state.imageWidth} /> ) }) } } renderImage = () => { return ( <img alt={t('Target Image')} css={this.props.styles.image} onLoad={this.handleImageLoad} ref={this.handleImageRef} src={this.props.url} /> ) } renderFeedbackComponents = () => { if (!this.props.results) { return null } return this.props.hotspots.map((hotspot, index) => { const feedbackCoordinates = this.currentCoordinates(hotspot.coordinates) const renderFeedback = hotspot.renderScoringDataFeedback const {responseCoordinates, isCorrect, flipResponse} = this.getResponseDetails( index, feedbackCoordinates, ) return ( <div key={hotspot.id || index}> {this.props.renderUserResponseFeedback(responseCoordinates, flipResponse, isCorrect)} {renderFeedback(feedbackCoordinates, !flipResponse)} </div> ) }) } render() { return ( <div className="fs-mask" css={this.props.styles.targetContainerWrapper} onFocus={this.props.onFocus} onBlur={this.props.onBlur} > <div css={this.props.styles.targetContainerMain}> <div css={this.props.styles.mainContainerContentWrapper}> <div css={this.props.styles.mainContainerContent}> {this.renderImage()} {this.renderCanvas()} </div> </div> {this.renderFeedbackComponents()} </div> </div> ) } }