FolderChild.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 FolderChild from 'compiled/react_files/components/FolderChild'
  21. import filesEnv from 'compiled/react_files/modules/filesEnv'
  22. import classnames from 'classnames'
  23. import ItemCog from 'jsx/files/ItemCog'
  24. import PublishCloud from 'jsx/shared/PublishCloud'
  25. import MasterCourseLock from 'jsx/shared/MasterCourseLock'
  26. import FilesystemObjectThumbnail from 'jsx/files/FilesystemObjectThumbnail'
  27. import UsageRightsIndicator from 'jsx/files/UsageRightsIndicator'
  28. import Folder from 'compiled/models/Folder'
  29. import preventDefault from 'compiled/fn/preventDefault'
  30. import FriendlyDatetime from 'jsx/shared/FriendlyDatetime'
  31. import friendlyBytes from 'compiled/util/friendlyBytes'
  32. FolderChild.isFolder = function () {
  33. return this.props.model instanceof Folder
  34. }
  35. FolderChild.renderItemCog = function (canManage) {
  36. if (!this.props.model.isNew() || this.props.model.get('locked_for_user')) {
  37. return (
  38. <ItemCog
  39. model= {this.props.model}
  40. startEditingName= {this.startEditingName}
  41. userCanManageFilesForContext= {canManage}
  42. userCanRestrictFilesForContext= {this.props.userCanRestrictFilesForContext}
  43. usageRightsRequiredForContext= {this.props.usageRightsRequiredForContext}
  44. externalToolsForContext= {this.props.externalToolsForContext}
  45. modalOptions= {this.props.modalOptions}
  46. clearSelectedItems= {this.props.clearSelectedItems}
  47. onMove={this.props.onMove}
  48. />
  49. );
  50. }
  51. }
  52. FolderChild.renderPublishCloud = function (canManage) {
  53. if (!this.props.model.isNew()){
  54. return (
  55. <PublishCloud
  56. model= {this.props.model}
  57. ref= 'publishButton'
  58. userCanManageFilesForContext= {canManage}
  59. usageRightsRequiredForContext= {this.props.usageRightsRequiredForContext}
  60. />
  61. );
  62. }
  63. }
  64. FolderChild.renderMasterCourseIcon = function (canManage) {
  65. // never show if not involved in blueprint courses
  66. if (!(this.props.model.get('is_master_course_master_content') || this.props.model.get('is_master_course_child_content'))) {
  67. return null;
  68. }
  69. // Never show the lock on master course folders
  70. // because we don't always have it's children to know if any are locked
  71. if (this.isFolder()) {
  72. if (this.props.model.get('is_master_course_master_content') || !this.props.model.get('restricted_by_master_course')) {
  73. return null
  74. }
  75. }
  76. return <MasterCourseLock model={this.props.model} canManage={canManage} />
  77. }
  78. FolderChild.renderEditingState = function () {
  79. if(this.state.editing) {
  80. return (
  81. <form className= 'ef-edit-name-form' onSubmit= {preventDefault(this.saveNameEdit)}>
  82. <div className="ic-Input-group">
  83. <input
  84. type='text'
  85. ref='newName'
  86. className='ic-Input ef-edit-name-form__input'
  87. placeholder={I18n.t('name', 'Name')}
  88. aria-label={this.isFolder() ? I18n.t('folder_name', 'Folder Name') : I18n.t('File Name')}
  89. defaultValue={this.props.model.displayName()}
  90. maxLength='255'
  91. onKeyUp={function (event){ if (event.keyCode === 27) {this.cancelEditingName()} }.bind(this)}
  92. />
  93. <button
  94. type="button"
  95. className="Button ef-edit-name-form__button ef-edit-name-accept"
  96. onClick={this.saveNameEdit}
  97. >
  98. <i className='icon-check' aria-hidden />
  99. <span className='screenreader-only'>{I18n.t('accept', 'Accept')}</span>
  100. </button>
  101. <button
  102. type="button"
  103. className="Button ef-edit-name-form__button ef-edit-name-cancel"
  104. onClick={this.cancelEditingName}
  105. >
  106. <i className='icon-x' aria-hidden />
  107. <span className='screenreader-only'>{I18n.t('cancel', 'Cancel')}</span>
  108. </button>
  109. </div>
  110. </form>
  111. );
  112. } else if (this.isFolder()) {
  113. return (
  114. <a
  115. ref= 'nameLink'
  116. href={`${filesEnv.baseUrl}/folder/${this.props.model.urlPath()}`}
  117. className= 'ef-name-col__link'
  118. params= {{splat: this.props.model.urlPath()}}
  119. >
  120. {/* we use an internal click wrapper span and handle a native js click event so we can
  121. intercept the link click event before page.js gets it. We want to prevent page.js from
  122. getting the click event if there is an error and we don't actually want to navigate.
  123. React's simulated events happen after the native event has been fully dispatched, so
  124. we can't use react events to intercept the event before page.js processes it. */}
  125. <span className="ef-name-col__click-wrapper" ref={elt => { if (elt) elt.addEventListener('click', this.checkForAccess) }}>
  126. <span className='ef-big-icon-container'>
  127. <FilesystemObjectThumbnail model= {this.props.model} />
  128. </span>
  129. <span className='ef-name-col__text'>
  130. {this.props.model.displayName()}
  131. </span>
  132. </span>
  133. </a>
  134. );
  135. } else{
  136. return (
  137. <a
  138. href={this.props.model.get('url')}
  139. onClick={preventDefault(this.handleFileLinkClick)}
  140. className='ef-name-col__link'
  141. ref='nameLink'
  142. >
  143. <span className='ef-big-icon-container'>
  144. <FilesystemObjectThumbnail model= {this.props.model} />
  145. </span>
  146. <span className='ef-name-col__text'>
  147. {this.props.model.displayName()}
  148. </span>
  149. </a>
  150. );
  151. }
  152. }
  153. FolderChild.renderUsageRightsIndicator = function () {
  154. if (this.props.usageRightsRequiredForContext) {
  155. return (
  156. <div className= 'ef-usage-rights-col' role= 'gridcell'>
  157. <UsageRightsIndicator
  158. model= {this.props.model}
  159. userCanManageFilesForContext= {this.props.userCanManageFilesForContext}
  160. userCanRestrictFilesForContext= {this.props.userCanRestrictFilesForContext}
  161. usageRightsRequiredForContext= {this.props.usageRightsRequiredForContext}
  162. modalOptions= {this.props.modalOptions}
  163. />
  164. </div>
  165. );
  166. }
  167. }
  168. FolderChild.render = function () {
  169. var user = this.props.model.get('user') || {};
  170. var selectCheckboxLabel = I18n.t('Select %{itemName}', {itemName: this.props.model.displayName()})
  171. var keyboardCheckboxClass = classnames({
  172. 'screenreader-only': this.state.hideKeyboardCheck,
  173. 'multiselectable-toggler': true
  174. })
  175. var keyboardLabelClass = classnames({
  176. 'screenreader-only': !this.state.hideKeyboardCheck
  177. })
  178. var parentFolder = this.props.model.collection && this.props.model.collection.parentFolder;
  179. var canManage = this.props.userCanManageFilesForContext && (!parentFolder || !parentFolder.get('for_submissions')) &&
  180. !this.props.model.get('for_submissions');
  181. return (
  182. <div {...this.getAttributesForRootNode()}>
  183. <label className= {keyboardCheckboxClass} role= 'gridcell'>
  184. <input
  185. type= 'checkbox'
  186. onFocus= {function(){ this.setState({hideKeyboardCheck: false})}.bind(this)}
  187. onBlur = {function () {this.setState({hideKeyboardCheck: true})}.bind(this)}
  188. className = {keyboardCheckboxClass}
  189. checked= {this.props.isSelected}
  190. onChange= {function () {}}
  191. />
  192. <span className= {keyboardLabelClass}>
  193. {selectCheckboxLabel}
  194. </span>
  195. </label>
  196. <div className='ef-name-col' role= 'rowheader'>
  197. { this.renderEditingState() }
  198. </div>
  199. <div className='ef-date-created-col' role= 'gridcell'>
  200. <FriendlyDatetime dateTime={this.props.model.get('created_at')} />
  201. </div>
  202. <div className='ef-date-modified-col' role= 'gridcell'>
  203. {!(this.isFolder()) && (
  204. <FriendlyDatetime dateTime={this.props.model.get('modified_at')} />
  205. )}
  206. </div>
  207. <div className='ef-modified-by-col ellipsis' role= 'gridcell'>
  208. <a href= {user.html_url} className= 'ef-plain-link'>
  209. {user.display_name}
  210. </a>
  211. </div>
  212. <div className='ef-size-col' role= 'gridcell'>
  213. {friendlyBytes(this.props.model.get('size'))}
  214. </div>
  215. { this.renderUsageRightsIndicator() }
  216. <div className= 'ef-links-col' role= 'gridcell'>
  217. { this.renderMasterCourseIcon(canManage) }
  218. { this.renderPublishCloud(canManage && this.props.userCanRestrictFilesForContext) }
  219. { this.renderItemCog(canManage) }
  220. </div>
  221. </div>
  222. );
  223. }
  224. export default React.createClass(FolderChild)