BinaryFileController.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. define(['base', 'moment'], (App, moment) =>
  2. App.controller('BinaryFileController', function(
  3. $scope,
  4. $rootScope,
  5. $http,
  6. $timeout,
  7. $element,
  8. ide,
  9. waitFor
  10. ) {
  11. const MAX_FILE_SIZE = 2 * 1024 * 1024
  12. const MAX_URL_LENGTH = 60
  13. const FRONT_OF_URL_LENGTH = 35
  14. const FILLER = '...'
  15. const TAIL_OF_URL_LENGTH =
  16. MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
  17. const textExtensions = ['bib', 'tex', 'txt', 'cls', 'sty']
  18. const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
  19. const previewableExtensions = []
  20. const extension = file =>
  21. file.name
  22. .split('.')
  23. .pop()
  24. .toLowerCase()
  25. $scope.isTextFile = () =>
  26. textExtensions.indexOf(extension($scope.openFile)) > -1
  27. $scope.isImageFile = () =>
  28. imageExtensions.indexOf(extension($scope.openFile)) > -1
  29. $scope.isPreviewableFile = () =>
  30. previewableExtensions.indexOf(extension($scope.openFile)) > -1
  31. $scope.isUnpreviewableFile = () =>
  32. !$scope.isTextFile() &&
  33. !$scope.isImageFile() &&
  34. !$scope.isPreviewableFile()
  35. $scope.textPreview = {
  36. loading: false,
  37. shouldShowDots: false,
  38. error: false,
  39. data: null
  40. }
  41. $scope.refreshing = false
  42. $scope.refreshError = null
  43. $scope.displayUrl = function(url) {
  44. if (url == null) {
  45. return
  46. }
  47. if (url.length > MAX_URL_LENGTH) {
  48. const front = url.slice(0, FRONT_OF_URL_LENGTH)
  49. const tail = url.slice(url.length - TAIL_OF_URL_LENGTH)
  50. return front + FILLER + tail
  51. }
  52. return url
  53. }
  54. $scope.refreshFile = function(file) {
  55. $scope.refreshing = true
  56. $scope.refreshError = null
  57. ide.fileTreeManager
  58. .refreshLinkedFile(file)
  59. .then(function(response) {
  60. const { data } = response
  61. const newFileId = data.new_file_id
  62. $timeout(
  63. () =>
  64. waitFor(() => ide.fileTreeManager.findEntityById(newFileId), 5000)
  65. .then(newFile => ide.binaryFilesManager.openFile(newFile))
  66. .catch(err => console.warn(err)),
  67. 0
  68. )
  69. $scope.refreshError = null
  70. })
  71. .catch(response => ($scope.refreshError = response.data))
  72. .finally(() => {
  73. $scope.refreshing = false
  74. const provider = file.linkedFileData.provider
  75. if (
  76. provider === 'mendeley' ||
  77. provider === 'zotero' ||
  78. file.name.match(/^.*\.bib$/)
  79. ) {
  80. ide.$scope.$emit('references:should-reindex', {})
  81. }
  82. })
  83. }
  84. // Callback fired when the `img` tag fails to load,
  85. // `failedLoad` used to show the "No Preview" message
  86. $scope.failedLoad = false
  87. window.sl_binaryFilePreviewError = () => {
  88. $scope.failedLoad = true
  89. $scope.$apply()
  90. }
  91. // Callback fired when the `img` tag is done loading,
  92. // `imgLoaded` is used to show the spinner gif while loading
  93. $scope.imgLoaded = false
  94. window.sl_binaryFilePreviewLoaded = () => {
  95. $scope.imgLoaded = true
  96. $scope.$apply()
  97. }
  98. if ($scope.isTextFile()) {
  99. loadTextFilePreview()
  100. }
  101. function loadTextFilePreview() {
  102. const url = `/project/${window.project_id}/file/${$scope.openFile.id}`
  103. let truncated = false
  104. displayPreviewLoading()
  105. getFileSize(url)
  106. .then(fileSize => {
  107. const opts = {}
  108. if (fileSize > MAX_FILE_SIZE) {
  109. truncated = true
  110. opts.maxSize = MAX_FILE_SIZE
  111. }
  112. return getFileContents(url, opts)
  113. })
  114. .then(contents => {
  115. const displayedContents = truncated
  116. ? truncateFileContents(contents)
  117. : contents
  118. displayPreview(displayedContents, truncated)
  119. })
  120. .catch(err => {
  121. console.error(err)
  122. displayPreviewError()
  123. })
  124. }
  125. function getFileSize(url) {
  126. return $http.head(url).then(response => {
  127. const size = parseInt(response.headers('Content-Length'), 10)
  128. if (isNaN(size)) {
  129. throw new Error('Could not parse Content-Length header')
  130. }
  131. return size
  132. })
  133. }
  134. function getFileContents(url, opts = {}) {
  135. const { maxSize } = opts
  136. if (maxSize != null) {
  137. url += `?range=0-${maxSize}`
  138. }
  139. return $http
  140. .get(url, {
  141. transformResponse: null // Don't parse JSON
  142. })
  143. .then(response => {
  144. return response.data
  145. })
  146. }
  147. function truncateFileContents(contents) {
  148. return contents.replace(/\n.*$/, '')
  149. }
  150. function displayPreviewLoading() {
  151. $scope.textPreview.data = null
  152. $scope.textPreview.loading = true
  153. $scope.textPreview.shouldShowDots = false
  154. $scope.$apply()
  155. }
  156. function displayPreview(contents, truncated) {
  157. $scope.textPreview.error = false
  158. $scope.textPreview.data = contents
  159. $scope.textPreview.shouldShowDots = truncated
  160. $timeout(setPreviewHeight, 0)
  161. }
  162. function displayPreviewError() {
  163. $scope.textPreview.error = true
  164. $scope.textPreview.loading = false
  165. }
  166. function setPreviewHeight() {
  167. const $preview = $element.find('.text-preview .scroll-container')
  168. const $header = $element.find('.binary-file-header')
  169. const maxHeight = $element.height() - $header.height() - 14 // borders + margin
  170. $preview.css({ 'max-height': maxHeight })
  171. // Don't show the preview until we've set the height, otherwise we jump around
  172. $scope.textPreview.loading = false
  173. }
  174. }))