BinaryFileController.js 5.6 KB

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