BinaryFileController.js 5.6 KB

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