@instructure/quiz-grading
Version:
The Quiz React SDK by Instructure Inc.
178 lines (154 loc) • 5.59 kB
JavaScript
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import ImmutablePropTypes from 'react-immutable-proptypes'
import extractTextFromHtml from '@instructure/quiz-interactions/util/extractTextFromHtml'
import {substituteVars} from '@instructure/quiz-interactions/components/formula/common/util'
import {generateDisplayPositions} from '@instructure/quiz-core/common/util/generateDisplayPositions'
import {Stimulus} from '@instructure/quiz-core/common/components/layout/sidebar/Stimulus/index'
import {Sidebar} from '@instructure/quiz-core/common/components/layout/sidebar/Sidebar/index'
import {onPhone} from '@instructure/quiz-core/common/util/windowChecks'
import SidebarItem from './SidebarItem'
import {SIDEBAR_ITEM_REF, FORMULA_SLUG} from '@instructure/quiz-common/constants'
import {withI18nSupport} from '@instructure/quiz-common/with-i18n-support'
export class GradingSidebar extends withI18nSupport(Component) {
static propTypes = {
quizTitle: PropTypes.string.isRequired,
role: PropTypes.string,
screenreaderNotification: PropTypes.func.isRequired,
scrollToItem: PropTypes.func.isRequired,
sessionItems: ImmutablePropTypes.list.isRequired,
sessionNotLoaded: PropTypes.bool.isRequired,
sidebarOpen: PropTypes.bool,
toggleSidebar: PropTypes.func.isRequired,
locale: PropTypes.string,
}
static defaultProps = {
role: 'navigation',
sidebarOpen: false,
locale: null,
}
static contextTypes = {
locale: PropTypes.string,
}
generateItemKey(id) {
return `${SIDEBAR_ITEM_REF}_${id}`
}
setRef(key) {
return node => {
this[key] = node
}
}
scrollToItem = position => itemId => {
this.props.scrollToItem(itemId)
if (onPhone() && this.props.sidebarOpen) {
this.props.toggleSidebar()
}
}
getItemBody(item, locale) {
let itemBody = item.getItemBody()
if (item.getInteractionType().slug === FORMULA_SLUG) {
const variables = item.toJS().interactionData.variables
itemBody = substituteVars(itemBody, variables, locale)
}
return extractTextFromHtml(itemBody || '')
}
// Helper to determine if a sessionItem is a textblock
isTextblock(sessionItem) {
// Prefer interactionType if available
const item = sessionItem.getItem ? sessionItem.getItem() : sessionItem.item
if (item && typeof item.getInteractionType === 'function') {
const type = item.getInteractionType()
if (type && type.slug === 'textblock') return true
}
// Fallback to questionNumber === 0
return sessionItem.questionNumber === 0
}
renderSidebarItem = (sessionItem, displayPosition) => {
const item = sessionItem.getItem()
const key = this.generateItemKey(item.id)
const sidebarOpen = this.props.sidebarOpen
// Use helper for textblock detection
const isTextblock = this.isTextblock(sessionItem)
const position = isTextblock
? null
: displayPosition !== null
? displayPosition
: sessionItem.position
return (
<SidebarItem
inStimulus={sessionItem.isStimulus}
isGraded={sessionItem.sessionItemResult.get('score') !== null}
itemBody={this.getItemBody(item, this.locale)}
itemId={item.id}
itemName={item.getInteractionType().name}
key={key}
pointsPossible={sessionItem.pointsPossible}
position={position}
sessionItemId={sessionItem.id}
ref={this.generateItemKey(item.id)}
scrollToItem={this.scrollToItem}
sidebarOpen={sidebarOpen}
/>
)
}
renderSidebarItems() {
const {sessionItems, sidebarOpen} = this.props
const allItems = generateDisplayPositions(sessionItems)
// Recalculate display positions starting from 1, skipping textblocks
let displayPosition = 1
return allItems.map(si => {
const sessionItem = si.record
// Use helper for textblock detection
const isTextblock = this.isTextblock(sessionItem)
if (sessionItem.isStimulus) {
const currentDisplayPosition = isTextblock ? null : displayPosition
const nestedCount = sessionItem.getNestedEntries().size
// Only increment position for non-textblock stimuli
if (!isTextblock) {
displayPosition += nestedCount
}
const key = this.generateItemKey(si.record.id)
return (
<Stimulus
displayPosition={currentDisplayPosition}
itemId={sessionItem.id}
key={key}
ref={this.setRef(key)}
renderSidebarItem={this.renderSidebarItem}
scrollToItem={this.scrollToItem(sessionItem.position)}
sidebarOpen={sidebarOpen}
stimulusParent={sessionItem}
/>
)
}
const currentDisplayPosition = isTextblock ? null : displayPosition
// Only increment position for non-textblock items
if (!isTextblock) {
displayPosition++
}
return (
<div key={sessionItem.id}>
{this.renderSidebarItem(sessionItem, currentDisplayPosition)}
</div>
)
})
}
render() {
if (this.props.sessionNotLoaded) {
return null
}
return (
<Sidebar
grading
role={this.props.role}
scrollToItem={this.scrollToItem}
sidebarOpen={this.props.sidebarOpen}
toggleSidebar={this.props.toggleSidebar}
screenreaderNotification={this.props.screenreaderNotification}
>
{this.renderSidebarItems()}
</Sidebar>
)
}
}
export default GradingSidebar