123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- /*
- * Copyright (C) 2016 - present Instructure, Inc.
- *
- * This file is part of Canvas.
- *
- * Canvas is free software: you can redistribute it and/or modify it under
- * the terms of the GNU Affero General Public License as published by the Free
- * Software Foundation, version 3 of the License.
- *
- * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
- * details.
- *
- * You should have received a copy of the GNU Affero General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- import React from 'react'
- import PropTypes from 'prop-types'
- import I18n from 'i18n!student_context_tray'
- import FriendlyDatetime from 'jsx/shared/FriendlyDatetime'
- import Avatar from './Avatar'
- import LastActivity from './LastActivity'
- import MetricsList from './MetricsList'
- import Rating from './Rating'
- import SectionInfo from './SectionInfo'
- import SubmissionProgressBars from './SubmissionProgressBars'
- import MessageStudents from 'jsx/shared/MessageStudents'
- import Heading from 'instructure-ui/lib/components/Heading'
- import Button from 'instructure-ui/lib/components/Button'
- import Link from 'instructure-ui/lib/components/Link'
- import Typography from 'instructure-ui/lib/components/Typography'
- import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent'
- import Spinner from 'instructure-ui/lib/components/Spinner'
- import Tray from 'instructure-ui/lib/components/Tray'
- const courseShape = PropTypes.shape({
- permissions: PropTypes.shape({}).isRequired,
- submissionsConnection: PropTypes.shape({
- edges: PropTypes.arrayOf(PropTypes.shape({}))
- }).isRequired
- });
- const userShape = PropTypes.shape({
- enrollments: PropTypes.arrayOf(PropTypes.object).isRequired
- });
- const dataShape = PropTypes.shape({
- loading: PropTypes.bool.isRequired,
- course: courseShape,
- user: userShape
- });
- export default class StudentContextTray extends React.Component {
- static propTypes = {
- courseId: PropTypes.string.isRequired,
- studentId: PropTypes.string.isRequired,
- returnFocusTo: PropTypes.func.isRequired,
- data: dataShape.isRequired,
- }
- static renderQuickLink (label, srLabel, url, showIf) {
- return showIf() ? (
- <div className="StudentContextTray-QuickLinks__Link">
- <Button
- href={url}
- variant="ghost"
- size="small"
- fluidWidth
- aria-label={srLabel}
- >
- <span className="StudentContextTray-QuickLinks__Link-text">{label}</span>
- </Button>
- </div>
- ) : null
- }
- constructor (props) {
- super(props)
- this.state = {
- isOpen: true,
- messageFormOpen: false,
- }
- }
- /**
- * Lifecycle
- */
- componentWillReceiveProps (nextProps) {
- if (!this.state.isOpen) {
- this.setState({isOpen: true})
- }
- }
- /**
- * Handlers
- */
- getCloseButtonRef = (ref) => {
- this.closeButtonRef = ref
- }
- handleRequestClose = (e) => {
- e.preventDefault()
- this.setState({
- isOpen: false
- })
- if (this.props.returnFocusTo) {
- const focusableItems = this.props.returnFocusTo();
- // Because of the way native focus calls return undefined, all focus
- // objects should be wrapped in something that will return truthy like
- // jQuery wrappers do... and it should be able to check visibility like a
- // jQuery wrapper... so just use jQuery.
- focusableItems.some($itemToFocus => $itemToFocus.is(':visible') && $itemToFocus.focus())
- }
- }
- handleMessageButtonClick = (e) => {
- e.preventDefault()
- this.setState({
- messageFormOpen: true
- })
- }
- handleMessageFormClose = (e) => {
- e.preventDefault()
- this.setState({
- messageFormOpen: false
- }, () => {
- this.messageStudentsButton.focus()
- })
- }
- /**
- * Renderers
- */
- renderQuickLinks (user, course) {
- return (user.short_name && (
- course.permissions.manage_grades ||
- course.permissions.view_all_grades ||
- course.permissions.view_analytics
- )) ? (
- <section
- className="StudentContextTray__Section StudentContextTray-QuickLinks"
- >
- {StudentContextTray.renderQuickLink(
- I18n.t('Grades'),
- I18n.t('View grades for %{name}', { name: user.short_name }),
- `/courses/${this.props.courseId}/grades/${this.props.studentId}`,
- () =>
- course.permissions.manage_grades ||
- course.permissions.view_all_grades
- )}
- {StudentContextTray.renderQuickLink(
- I18n.t('Analytics'),
- I18n.t('View analytics for %{name}', { name: user.short_name }),
- `/courses/${this.props.courseId}/analytics/users/${this.props.studentId}`,
- () => course.permissions.view_analytics && user.analytics
- )}
- </section>
- ) : null
- }
- render () {
- const { data: { loading, course, user } } = this.props
- return (
- <div>
- {this.state.messageFormOpen ? (
- <MessageStudents
- contextCode={`course_${course._id}`}
- onRequestClose={this.handleMessageFormClose}
- open={this.state.messageFormOpen}
- recipients={[{
- id: user._id,
- displayName: user.short_name
- }]}
- title='Send a message'
- />
- ) : null}
- <Tray
- label={I18n.t('Student Details')}
- closeButtonLabel={I18n.t('Close')}
- closeButtonRef={this.getCloseButtonRef}
- applicationElement={() => document.getElementById('application')}
- open={this.state.isOpen}
- onDismiss={this.handleRequestClose}
- placement='end'
- zIndex='1000'
- >
- <aside
- className={user && user.avatar_url
- ? 'StudentContextTray StudentContextTray--withAvatar'
- : 'StudentContextTray'
- }
- >
- {loading ? (
- <div className='StudentContextTray__Spinner'>
- <Spinner title={I18n.t('Loading')}
- size='large'
- />
- </div>
- ) : (
- <div>
- <header className="StudentContextTray-Header">
- <Avatar user={user}
- canMasquerade={course.permissions.become_user}
- courseId={this.props.courseId}
- />
- <div className="StudentContextTray-Header__Layout">
- <div className="StudentContextTray-Header__Content">
- {user.short_name ? (
- <div className="StudentContextTray-Header__Name">
- <Heading level="h3" as="h2">
- <span className="StudentContextTray-Header__NameLink">
- <Link
- href={`/courses/${this.props.courseId}/users/${this.props.studentId}`}
- aria-label={I18n.t('Go to %{name}\'s profile', {name: user.short_name})}
- >
- {user.short_name}
- </Link>
- </span>
- </Heading>
- </div>
- ) : null}
- <div className="StudentContextTray-Header__CourseName">
- <Typography size="medium" as="div" lineHeight="condensed">
- {course.name}
- </Typography>
- </div>
- <Typography size="x-small" color="secondary" as="div">
- <SectionInfo user={user} />
- </Typography>
- <Typography size="x-small" color="secondary" as="div">
- <LastActivity user={user} />
- </Typography>
- </div>
- {course.permissions.send_messages ? (
- <div className="StudentContextTray-Header__Actions">
- <Button
- ref={ (b) => this.messageStudentsButton = b }
- variant="icon" size="small"
- onClick={this.handleMessageButtonClick}
- >
- <ScreenReaderContent>
- {I18n.t('Send a message to %{student}', {student: user.short_name})}
- </ScreenReaderContent>
- {/* Note: replace with instructure-icon */}
- <i className="icon-email" aria-hidden="true" />
- </Button>
- </div>
- ) : null }
- </div>
- </header>
- {this.renderQuickLinks(user, course)}
- <MetricsList user={user} analytics={user.analytics} />
- <SubmissionProgressBars submissions={course.submissionsConnection.edges.map(n => n.submission)} />
- {user.analytics ? (
- <section
- className="StudentContextTray__Section StudentContextTray-Ratings">
- <Heading level="h4" as="h3" border="bottom">
- {I18n.t("Activity Compared to Class")}
- </Heading>
- <div className="StudentContextTray-Ratings__Layout">
- <Rating metric={user.analytics.participations}
- label={I18n.t('Participation')} />
- <Rating metric={user.analytics.page_views}
- label={I18n.t('Page Views')} />
- </div>
- </section>
- ) : null}
- </div>
- )}
- </aside>
- </Tray>
- </div>
- );
- }
- }
|