MoveSelect.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. /*
  2. * Copyright (C) 2017 - present Instructure, Inc.
  3. *
  4. * This file is part of Canvas.
  5. *
  6. * Canvas is free software: you can redistribute it and/or modify it under
  7. * the terms of the GNU Affero General Public License as published by the Free
  8. * Software Foundation, version 3 of the License.
  9. *
  10. * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
  11. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  13. * details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License along
  16. * with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. import I18n from 'i18n!move_select'
  19. import React from 'react'
  20. import { func } from 'prop-types'
  21. import Select from 'instructure-ui/lib/components/Select'
  22. import Button from 'instructure-ui/lib/components/Button'
  23. import Container from 'instructure-ui/lib/components/Container'
  24. import Typography from 'instructure-ui/lib/components/Typography'
  25. import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent'
  26. import ConnectorIcon from './ConnectorIcon'
  27. import { itemShape, moveOptionsType } from './propTypes'
  28. import { positions } from './positions'
  29. export default class MoveSelect extends React.Component {
  30. static propTypes = {
  31. item: itemShape.isRequired,
  32. moveOptions: moveOptionsType.isRequired,
  33. onSelect: func.isRequired,
  34. }
  35. state = {
  36. selectedGroup: null,
  37. selectedPosition: null,
  38. selectedSibling: null,
  39. }
  40. selectGroup = (e) => {
  41. this.setState({ selectedGroup: this.props.moveOptions.groups.find(group => group.id === e.target.value) || null })
  42. }
  43. selectPosition = (e) => {
  44. this.setState({ selectedPosition: positions[e.target.value] || null })
  45. }
  46. selectSibling = (e) => {
  47. this.setState({ selectedSibling: e.target.value === '' ? null : Number(e.target.value) })
  48. }
  49. submitSelection = () => {
  50. const { item, moveOptions } = this.props
  51. const { selectedGroup, selectedPosition, selectedSibling } = this.state
  52. let order = [item.id]
  53. if (selectedPosition) {
  54. const items = selectedGroup ? selectedGroup.items : moveOptions.siblings
  55. order = selectedPosition.apply({
  56. item: item.id,
  57. order: items.map(({ id }) => id),
  58. relativeTo: selectedSibling,
  59. })
  60. }
  61. this.props.onSelect({
  62. groupId: moveOptions.groups ? selectedGroup.id : null,
  63. itemId: item.id,
  64. order,
  65. })
  66. }
  67. hasSelectedPosition () {
  68. const { selectedSibling, selectedPosition } = this.state
  69. const isAbsolute = selectedPosition && selectedPosition.type === 'absolute'
  70. return !!selectedPosition && (isAbsolute || selectedSibling !== null)
  71. }
  72. isDoneSelecting () {
  73. const { selectedGroup } = this.state
  74. if (this.props.moveOptions.groups) {
  75. if (selectedGroup && selectedGroup.items && selectedGroup.items.length) {
  76. return this.hasSelectedPosition()
  77. } else {
  78. return !!selectedGroup
  79. }
  80. } else {
  81. return this.hasSelectedPosition()
  82. }
  83. }
  84. renderSelect ({ label, onChange, options, className }) {
  85. return (
  86. <Container margin="medium 0" display="block" className={className}>
  87. <Select
  88. label={<ScreenReaderContent>{label}</ScreenReaderContent>}
  89. onChange={onChange}>
  90. <option>{I18n.t('Select one')}</option>
  91. {options}
  92. </Select>
  93. </Container>
  94. )
  95. }
  96. renderSelectGroup () {
  97. const { selectedGroup } = this.state
  98. const selectPosition = !!(selectedGroup && selectedGroup.items && selectedGroup.items.length)
  99. const { moveOptions, item } = this.props
  100. let { groups } = moveOptions
  101. if (moveOptions.excludeCurrent && item.groupId) {
  102. groups = groups.filter(group => group.id !== item.groupId)
  103. }
  104. return (
  105. <div>
  106. {this.renderSelect({
  107. label: I18n.t('Group Select'),
  108. className: 'move-select__group',
  109. onChange: this.selectGroup,
  110. options: groups.map(group =>
  111. <option key={group.id} value={group.id}>{group.title}</option>)
  112. })}
  113. {selectPosition ? this.renderSelectPosition(selectedGroup.items) : null}
  114. </div>
  115. )
  116. }
  117. renderSelectPosition (items) {
  118. const { selectedPosition } = this.state
  119. const selectSibling = !!(selectedPosition && selectedPosition.type === 'relative')
  120. return (
  121. <div>
  122. {this.renderSelect({
  123. label: I18n.t('Position Select'),
  124. className: 'move-select__position',
  125. onChange: this.selectPosition,
  126. options: Object.keys(positions).map((pos) =>
  127. <option key={pos} value={pos}>{positions[pos].label}</option>)
  128. })}
  129. {selectSibling ? (
  130. <div>
  131. <ConnectorIcon aria-hidden style={{ position: 'absolute', transform: 'translate(-15px, -35px)' }} />
  132. {this.renderSelectSibling(items)}
  133. </div>) : null}
  134. </div>
  135. )
  136. }
  137. renderSelectSibling (items) {
  138. const filteredItems = items.filter(item => item.id !== this.props.item.id)
  139. return this.renderSelect({
  140. label: I18n.t('Item Select'),
  141. className: 'move-select__sibling',
  142. onChange: this.selectSibling,
  143. options: filteredItems.map((item, index) =>
  144. <option key={item.id} value={index}>{item.title}</option>)
  145. })
  146. }
  147. render () {
  148. const { siblings, groups } = this.props.moveOptions
  149. return (
  150. <div className="move-select">
  151. <Typography paragraphMargin="large 0" weight="bold">{I18n.t('Place "%{title}"', { title: this.props.item.title })}</Typography>
  152. {groups
  153. ? this.renderSelectGroup()
  154. : this.renderSelectPosition(siblings)}
  155. {this.isDoneSelecting() && (
  156. <Container textAlign="center" display="block">
  157. <hr />
  158. <Button variant="primary" onClick={this.submitSelection}>{I18n.t('Done')}</Button>
  159. </Container>
  160. )}
  161. </div>
  162. )
  163. }
  164. }