ItemCog.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /*
  2. * Copyright (C) 2015 - 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!react_files'
  19. import React from 'react'
  20. import PropTypes from 'prop-types'
  21. import ReactDOM from 'react-dom'
  22. import preventDefault from 'compiled/fn/preventDefault'
  23. import customPropTypes from 'compiled/react_files/modules/customPropTypes'
  24. import filesEnv from 'compiled/react_files/modules/filesEnv'
  25. import File from 'compiled/models/File'
  26. import Folder from 'compiled/models/Folder'
  27. import UsageRightsDialog from 'jsx/files/UsageRightsDialog'
  28. import RestrictedDialogForm from 'jsx/files/RestrictedDialogForm'
  29. import openMoveDialog from 'jsx/files/utils/openMoveDialog'
  30. import downloadStuffAsAZip from 'compiled/react_files/utils/downloadStuffAsAZip'
  31. import deleteStuff from 'compiled/react_files/utils/deleteStuff'
  32. import $ from 'jquery'
  33. var ItemCog = React.createClass({
  34. displayName: 'ItemCog',
  35. propTypes: {
  36. model: customPropTypes.filesystemObject,
  37. modalOptions: PropTypes.object.isRequired,
  38. externalToolsForContext: PropTypes.arrayOf(PropTypes.object),
  39. userCanManageFilesForContext: PropTypes.bool,
  40. userCanRestrictFilesForContext: PropTypes.bool.isRequired,
  41. usageRightsRequiredForContext: PropTypes.bool
  42. },
  43. isMasterCourseRestricted () {
  44. return this.props.model.get('is_master_course_child_content') &&
  45. this.props.model.get('restricted_by_master_course')
  46. },
  47. downloadFile (file, args) {
  48. window.location = file[0].get('url');
  49. $(args.returnFocusTo).focus();
  50. },
  51. downloadZip (folder, args) {
  52. downloadStuffAsAZip(folder, args);
  53. $(args.returnFocusTo).focus();
  54. },
  55. deleteItem (item, args) {
  56. // Unfortunately, ars.returnFocusTo isn't really the one we want to focus,
  57. // because we want the previous one or the +Folder button
  58. // Also unfortunately, our state management in this app is a bit terrible
  59. // so we'll just handle all that via jQuery right here for now.
  60. // TODO: Make this less terrible when we have sane state management
  61. const allTriggers = $('.al-trigger').toArray();
  62. const hasMoreTriggers = allTriggers.length - 1 > 0;
  63. const toFocus = (hasMoreTriggers) ?
  64. allTriggers[allTriggers.indexOf(args.returnFocusTo) - 1] :
  65. $('.ef-name-col a').first();
  66. args.returnFocusTo = toFocus;
  67. deleteStuff(item, args);
  68. },
  69. openUsageRightsDialog (event) {
  70. var contents = (
  71. <UsageRightsDialog
  72. closeModal={this.props.modalOptions.closeModal}
  73. itemsToManage={[this.props.model]}
  74. userCanRestrictFilesForContext={this.props.userCanRestrictFilesForContext}
  75. />
  76. );
  77. this.props.modalOptions.openModal(contents, () => {
  78. ReactDOM.findDOMNode(this.refs.settingsCogBtn).focus();
  79. });
  80. },
  81. render () {
  82. var externalToolMenuItems;
  83. if (this.props.model instanceof File) {
  84. externalToolMenuItems = this.props.externalToolsForContext.map((tool) => {
  85. if (this.props.model.externalToolEnabled(tool)) {
  86. return (
  87. <li key={tool.title} role='presentation'>
  88. <a href={`${tool.base_url}&files[]=${this.props.model.id}`} role='menuitem' tabIndex='-1'>
  89. {tool.title}
  90. </a>
  91. </li>
  92. );
  93. } else {
  94. return (<li key={tool.title} role='presentation'><a href='#' className='disabled' role='menuitem' tabIndex='-1' aria-disabled='true'>{tool.title}</a></li>);
  95. }
  96. });
  97. } else {
  98. externalToolMenuItems = [];
  99. }
  100. var wrap = (fn, params = {}) => {
  101. return preventDefault((event) => {
  102. var singularContextType = (this.props.model.collection && this.props.model.collection.parentFolder) ?
  103. this.props.model.collection.parentFolder.get('context_type').toLowerCase() : null;
  104. var pluralContextType = (singularContextType) ? singularContextType + 's' : null
  105. var contextType = pluralContextType || filesEnv.contextType;
  106. var contextId = (this.props.model.collection && this.props.model.collection.parentFolder) ?
  107. this.props.model.collection.parentFolder.get('context_id') : filesEnv.contextId;
  108. var args = {
  109. contextType,
  110. contextId,
  111. returnFocusTo: ReactDOM.findDOMNode(this.refs.settingsCogBtn)
  112. };
  113. args = $.extend(args, params);
  114. return fn([this.props.model], args);
  115. });
  116. };
  117. var menuItems = [];
  118. // Download Link
  119. if (this.props.model instanceof Folder) {
  120. menuItems.push(<li key='folderDownload' role='presentation'><a href='#' onClick={wrap(this.downloadZip)} ref='download' role='menuitem' tabIndex='-1'>{I18n.t('Download')}</a></li>);
  121. } else {
  122. menuItems.push(<li key='download' role='presentation'><a onClick={wrap(this.downloadFile)} href={this.props.model.get('url')} ref='download' role='menuitem' tabIndex='-1'>{I18n.t('Download')}</a></li>);
  123. }
  124. if (this.props.userCanManageFilesForContext && !this.isMasterCourseRestricted()) {
  125. // Rename Link
  126. menuItems.push(<li key='rename' role='presentation'><a href='#' onClick={preventDefault(this.props.startEditingName)} ref='editName' role='menuitem' tabIndex='-1'>{I18n.t('Rename')}</a></li>);
  127. // Move Link
  128. menuItems.push(<li key='move' role='presentation'><a href='#' onClick={wrap(openMoveDialog, {clearSelectedItems: this.props.clearSelectedItems, onMove: this.props.onMove})} ref='move' role='menuitem' tabIndex='-1'>{I18n.t('Move')}</a></li>);
  129. if (this.props.usageRightsRequiredForContext) {
  130. // Manage Usage Rights Link
  131. menuItems.push(<li key='manageUsageRights' className='ItemCog__OpenUsageRights' role='presentation'><a href='#' onClick={preventDefault(this.openUsageRightsDialog)} ref='usageRights' role='menuitem' tabIndex='-1'>{I18n.t('Manage Usage Rights')}</a></li>);
  132. }
  133. // Delete Link
  134. menuItems.push(<li key='delete' role='presentation'><a href='#' onClick={wrap(this.deleteItem)} ref='deleteLink' role='menuitem' tabIndex='-1'>{I18n.t('Delete')}</a></li>);
  135. }
  136. return (
  137. // without the stopPropagation(), using the cog menu causes the file's invisible selection checkbox to be toggled as well
  138. <div className='al-dropdown__container' style={{minWidth: '45px', display: 'inline-block'}} onClick={(e) => e.stopPropagation()}>
  139. <button
  140. type='button'
  141. ref='settingsCogBtn'
  142. className='al-trigger al-trigger-gray btn btn-link'
  143. aria-label={I18n.t('Actions')}
  144. data-popup-within='#application'
  145. data-append-to-body={true}
  146. >
  147. <i className='icon-settings' />
  148. <i className='icon-mini-arrow-down' />
  149. </button>
  150. <ul className='al-options' role='menu' aria-hidden='true' aria-expanded='false' tabIndex='0'>
  151. {menuItems.concat(externalToolMenuItems)}
  152. </ul>
  153. </div>
  154. );
  155. }
  156. });
  157. export default ItemCog