FileTreeController.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. max-len,
  5. no-return-assign,
  6. no-undef,
  7. no-unused-vars,
  8. */
  9. // TODO: This file was created by bulk-decaffeinate.
  10. // Fix any style issues and re-enable lint.
  11. /*
  12. * decaffeinate suggestions:
  13. * DS102: Remove unnecessary code created because of implicit returns
  14. * DS103: Rewrite code to no longer use __guard__
  15. * DS207: Consider shorter variations of null checks
  16. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  17. */
  18. define(['base'], function(App) {
  19. App.controller('FileTreeController', function(
  20. $scope,
  21. $modal,
  22. ide,
  23. $rootScope
  24. ) {
  25. $scope.openNewDocModal = () =>
  26. $modal.open({
  27. templateUrl: 'newFileModalTemplate',
  28. controller: 'NewFileModalController',
  29. size: 'lg',
  30. resolve: {
  31. parent_folder() {
  32. return ide.fileTreeManager.getCurrentFolder()
  33. },
  34. projectFeatures() {
  35. return ide.$scope.project.features
  36. },
  37. type() {
  38. return 'doc'
  39. },
  40. userFeatures() {
  41. return ide.$scope.user.features
  42. }
  43. }
  44. })
  45. $scope.openNewFolderModal = () =>
  46. $modal.open({
  47. templateUrl: 'newFolderModalTemplate',
  48. controller: 'NewFolderModalController',
  49. resolve: {
  50. parent_folder() {
  51. return ide.fileTreeManager.getCurrentFolder()
  52. }
  53. }
  54. })
  55. $scope.openUploadFileModal = () =>
  56. $modal.open({
  57. templateUrl: 'newFileModalTemplate',
  58. controller: 'NewFileModalController',
  59. size: 'lg',
  60. resolve: {
  61. projectFeatures() {
  62. return ide.$scope.project.features
  63. },
  64. parent_folder() {
  65. return ide.fileTreeManager.getCurrentFolder()
  66. },
  67. type() {
  68. return 'upload'
  69. },
  70. userFeatures() {
  71. return ide.$scope.user.features
  72. }
  73. }
  74. })
  75. $scope.orderByFoldersFirst = function(entity) {
  76. if ((entity != null ? entity.type : undefined) === 'folder') {
  77. return '0'
  78. }
  79. return '1'
  80. }
  81. $scope.startRenamingSelected = () => $scope.$broadcast('rename:selected')
  82. return ($scope.openDeleteModalForSelected = () =>
  83. $scope.$broadcast('delete:selected'))
  84. })
  85. App.controller('NewFolderModalController', function(
  86. $scope,
  87. ide,
  88. $modalInstance,
  89. $timeout,
  90. parent_folder
  91. ) {
  92. $scope.inputs = { name: 'name' }
  93. $scope.state = { inflight: false }
  94. $modalInstance.opened.then(() =>
  95. $timeout(() => $scope.$broadcast('open'), 200)
  96. )
  97. $scope.create = function() {
  98. const { name } = $scope.inputs
  99. if (name == null || name.length === 0) {
  100. return
  101. }
  102. $scope.state.inflight = true
  103. return ide.fileTreeManager
  104. .createFolder(name, $scope.parent_folder)
  105. .then(function() {
  106. $scope.state.inflight = false
  107. return $modalInstance.dismiss('done')
  108. })
  109. .catch(function(response) {
  110. const { data } = response
  111. $scope.error = data
  112. return ($scope.state.inflight = false)
  113. })
  114. }
  115. return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
  116. })
  117. App.controller('DuplicateFileModalController', function(
  118. $scope,
  119. $modalInstance,
  120. fileName
  121. ) {
  122. $scope.fileName = fileName
  123. $scope.cancel = () => $modalInstance.dismiss('cancel')
  124. })
  125. App.controller('NewFileModalController', function(
  126. $scope,
  127. ide,
  128. type,
  129. parent_folder,
  130. $modalInstance,
  131. eventTracking,
  132. projectFeatures,
  133. userFeatures
  134. ) {
  135. $scope.type = type
  136. $scope.parent_folder = parent_folder
  137. $scope.state = {
  138. inflight: false,
  139. valid: true
  140. }
  141. $scope.cancel = () => $modalInstance.dismiss('cancel')
  142. $scope.create = () => $scope.$broadcast('create')
  143. const hasMendeleyFeature =
  144. (projectFeatures && projectFeatures.references) ||
  145. (projectFeatures && projectFeatures.mendeley) ||
  146. (userFeatures && userFeatures.references) ||
  147. (userFeatures && userFeatures.mendeley)
  148. const hasZoteroFeature =
  149. (projectFeatures && projectFeatures.references) ||
  150. (projectFeatures && projectFeatures.zotero) ||
  151. (userFeatures && userFeatures.references) ||
  152. (userFeatures && userFeatures.zotero)
  153. $scope.$watch('type', function() {
  154. if ($scope.type === 'mendeley' && !hasMendeleyFeature) {
  155. eventTracking.send(
  156. 'subscription-funnel',
  157. 'editor-click-feature',
  158. $scope.type
  159. )
  160. }
  161. if ($scope.type === 'zotero' && !hasZoteroFeature) {
  162. eventTracking.send(
  163. 'subscription-funnel',
  164. 'editor-click-feature',
  165. $scope.type
  166. )
  167. }
  168. })
  169. return $scope.$on('done', (e, opts = {}) => {
  170. isBibFile = opts.name && /^.*\.bib$/.test(opts.name)
  171. if (opts.shouldReindexReferences || isBibFile) {
  172. ide.$scope.$emit('references:should-reindex', {})
  173. }
  174. $modalInstance.dismiss('done')
  175. })
  176. })
  177. App.controller('NewDocModalController', function($scope, ide, $timeout) {
  178. $scope.inputs = { name: 'name.tex' }
  179. const validate = function() {
  180. const { name } = $scope.inputs
  181. $scope.state.valid = name != null && name.length > 0
  182. }
  183. $scope.$watch('inputs.name', validate)
  184. $timeout(() => $scope.$broadcast('open'), 200)
  185. return $scope.$on('create', function() {
  186. const { name } = $scope.inputs
  187. if (name == null || name.length === 0) {
  188. return
  189. }
  190. $scope.state.inflight = true
  191. return ide.fileTreeManager
  192. .createDoc(name, $scope.parent_folder)
  193. .then(function() {
  194. $scope.state.inflight = false
  195. return $scope.$emit('done')
  196. })
  197. .catch(function(response) {
  198. const { data } = response
  199. $scope.error = data
  200. $scope.state.inflight = false
  201. })
  202. })
  203. })
  204. App.controller('UploadFileModalController', function(
  205. $scope,
  206. $rootScope,
  207. ide,
  208. $timeout,
  209. $window
  210. ) {
  211. $scope.parent_folder_id =
  212. $scope.parent_folder != null ? $scope.parent_folder.id : undefined
  213. $scope.project_id = ide.project_id
  214. $scope.tooManyFiles = false
  215. $scope.rateLimitHit = false
  216. $scope.secondsToRedirect = 10
  217. $scope.notLoggedIn = false
  218. $scope.conflicts = []
  219. $scope.control = {}
  220. const needToLogBackIn = function() {
  221. $scope.notLoggedIn = true
  222. var decreseTimeout = () =>
  223. $timeout(function() {
  224. if ($scope.secondsToRedirect === 0) {
  225. return ($window.location.href = `/login?redir=/project/${
  226. ide.project_id
  227. }`)
  228. } else {
  229. decreseTimeout()
  230. return ($scope.secondsToRedirect = $scope.secondsToRedirect - 1)
  231. }
  232. }, 1000)
  233. return decreseTimeout()
  234. }
  235. $scope.max_files = 40
  236. $scope.onComplete = (error, name, response) =>
  237. $timeout(function() {
  238. uploadCount--
  239. if (response.success) {
  240. $rootScope.$broadcast('file:upload:complete', response)
  241. }
  242. if (uploadCount === 0 && response != null && response.success) {
  243. return $scope.$emit('done', { name: name })
  244. }
  245. }, 250)
  246. $scope.onValidateBatch = function(files) {
  247. if (files.length > $scope.max_files) {
  248. $timeout(() => ($scope.tooManyFiles = true), 1)
  249. return false
  250. } else {
  251. return true
  252. }
  253. }
  254. $scope.onError = function(id, name, reason) {
  255. console.log(id, name, reason)
  256. if (reason.indexOf('429') !== -1) {
  257. return ($scope.rateLimitHit = true)
  258. } else if (reason.indexOf('403') !== -1) {
  259. return needToLogBackIn()
  260. }
  261. }
  262. let _uploadTimer = null
  263. const uploadIfNoConflicts = function() {
  264. if ($scope.conflicts.length === 0) {
  265. return $scope.doUpload()
  266. }
  267. }
  268. var uploadCount = 0
  269. $scope.onSubmit = function(id, name) {
  270. uploadCount++
  271. if (ide.fileTreeManager.existsInFolder($scope.parent_folder_id, name)) {
  272. $scope.conflicts.push(name)
  273. $scope.$apply()
  274. }
  275. if (_uploadTimer == null) {
  276. _uploadTimer = setTimeout(function() {
  277. _uploadTimer = null
  278. return uploadIfNoConflicts()
  279. }, 0)
  280. }
  281. return true
  282. }
  283. $scope.onCancel = function(id, name) {
  284. uploadCount--
  285. const index = $scope.conflicts.indexOf(name)
  286. if (index > -1) {
  287. $scope.conflicts.splice(index, 1)
  288. }
  289. $scope.$apply()
  290. return uploadIfNoConflicts()
  291. }
  292. return ($scope.doUpload = () =>
  293. __guard__($scope.control != null ? $scope.control.q : undefined, x =>
  294. x.uploadStoredFiles()
  295. ))
  296. })
  297. App.controller('ProjectLinkedFileModalController', function(
  298. $scope,
  299. ide,
  300. $timeout
  301. ) {
  302. $scope.data = {
  303. projects: null, // or []
  304. selectedProjectId: null,
  305. projectEntities: null, // or []
  306. projectOutputFiles: null, // or []
  307. selectedProjectEntity: null,
  308. selectedProjectOutputFile: null,
  309. buildId: null,
  310. name: null
  311. }
  312. $scope.state.inFlight = {
  313. projects: false,
  314. entities: false,
  315. compile: false
  316. }
  317. $scope.state.isOutputFilesMode = false
  318. $scope.state.error = false
  319. $scope.$watch('data.selectedProjectId', function(newVal, oldVal) {
  320. if (!newVal) {
  321. return
  322. }
  323. $scope.data.selectedProjectEntity = null
  324. $scope.data.selectedProjectOutputFile = null
  325. if ($scope.state.isOutputFilesMode) {
  326. return $scope.compileProjectAndGetOutputFiles(
  327. $scope.data.selectedProjectId
  328. )
  329. } else {
  330. return $scope.getProjectEntities($scope.data.selectedProjectId)
  331. }
  332. })
  333. $scope.$watch('state.isOutputFilesMode', function(newVal, oldVal) {
  334. if (!newVal && !oldVal) {
  335. return
  336. }
  337. $scope.data.selectedProjectOutputFile = null
  338. if (newVal === true) {
  339. return $scope.compileProjectAndGetOutputFiles(
  340. $scope.data.selectedProjectId
  341. )
  342. } else {
  343. return $scope.getProjectEntities($scope.data.selectedProjectId)
  344. }
  345. })
  346. // auto-set filename based on selected file
  347. $scope.$watch('data.selectedProjectEntity', function(newVal, oldVal) {
  348. if (!newVal) {
  349. return
  350. }
  351. const fileName = newVal.split('/').reverse()[0]
  352. if (fileName) {
  353. $scope.data.name = fileName
  354. }
  355. })
  356. // auto-set filename based on selected file
  357. $scope.$watch('data.selectedProjectOutputFile', function(newVal, oldVal) {
  358. if (!newVal) {
  359. return
  360. }
  361. if (newVal === 'output.pdf') {
  362. const project = _.find(
  363. $scope.data.projects,
  364. p => p._id === $scope.data.selectedProjectId
  365. )
  366. $scope.data.name =
  367. (project != null ? project.name : undefined) != null
  368. ? `${project.name}.pdf`
  369. : 'output.pdf'
  370. } else {
  371. const fileName = newVal.split('/').reverse()[0]
  372. if (fileName) {
  373. $scope.data.name = fileName
  374. }
  375. }
  376. })
  377. const _setInFlight = type => ($scope.state.inFlight[type] = true)
  378. const _reset = function(opts) {
  379. const isError = opts.err === true
  380. const { inFlight } = $scope.state
  381. inFlight.projects = inFlight.entities = inFlight.compile = false
  382. $scope.state.inflight = false
  383. return ($scope.state.error = isError)
  384. }
  385. $scope.toggleOutputFilesMode = function() {
  386. if (!$scope.data.selectedProjectId) {
  387. return
  388. }
  389. return ($scope.state.isOutputFilesMode = !$scope.state.isOutputFilesMode)
  390. }
  391. $scope.shouldEnableProjectSelect = function() {
  392. const { state, data } = $scope
  393. return !state.inFlight.projects && data.projects
  394. }
  395. $scope.hasNoProjects = function() {
  396. const { state, data } = $scope
  397. return (
  398. !state.inFlight.projects &&
  399. (data.projects == null || data.projects.length === 0)
  400. )
  401. }
  402. $scope.shouldEnableProjectEntitySelect = function() {
  403. const { state, data } = $scope
  404. return (
  405. !state.inFlight.projects &&
  406. !state.inFlight.entities &&
  407. data.projects &&
  408. data.selectedProjectId
  409. )
  410. }
  411. $scope.shouldEnableProjectOutputFileSelect = function() {
  412. const { state, data } = $scope
  413. return (
  414. !state.inFlight.projects &&
  415. !state.inFlight.compile &&
  416. data.projects &&
  417. data.selectedProjectId
  418. )
  419. }
  420. const validate = function() {
  421. const { state } = $scope
  422. const { data } = $scope
  423. $scope.state.valid =
  424. !state.inFlight.projects &&
  425. !state.inFlight.entities &&
  426. data.projects &&
  427. data.selectedProjectId &&
  428. ((!$scope.state.isOutputFilesMode &&
  429. data.projectEntities &&
  430. data.selectedProjectEntity) ||
  431. ($scope.state.isOutputFilesMode &&
  432. data.projectOutputFiles &&
  433. data.selectedProjectOutputFile)) &&
  434. data.name
  435. }
  436. $scope.$watch('state', validate, true)
  437. $scope.$watch('data', validate, true)
  438. $scope.getUserProjects = function() {
  439. _setInFlight('projects')
  440. return ide.$http
  441. .get('/user/projects', {
  442. _csrf: window.csrfToken
  443. })
  444. .then(function(resp) {
  445. $scope.data.projectEntities = null
  446. $scope.data.projects = resp.data.projects.filter(
  447. p => p._id !== ide.project_id
  448. )
  449. return _reset({ err: false })
  450. })
  451. .catch(err => _reset({ err: true }))
  452. }
  453. $scope.getProjectEntities = project_id => {
  454. _setInFlight('entities')
  455. return ide.$http
  456. .get(`/project/${project_id}/entities`, {
  457. _csrf: window.csrfToken
  458. })
  459. .then(function(resp) {
  460. if ($scope.data.selectedProjectId === resp.data.project_id) {
  461. $scope.data.projectEntities = resp.data.entities
  462. return _reset({ err: false })
  463. }
  464. })
  465. .catch(err => _reset({ err: true }))
  466. }
  467. $scope.compileProjectAndGetOutputFiles = project_id => {
  468. _setInFlight('compile')
  469. return ide.$http
  470. .post(`/project/${project_id}/compile`, {
  471. check: 'silent',
  472. draft: false,
  473. incrementalCompilesEnabled: false,
  474. _csrf: window.csrfToken
  475. })
  476. .then(function(resp) {
  477. if (resp.data.status === 'success') {
  478. const filteredFiles = resp.data.outputFiles.filter(f =>
  479. f.path.match(/.*\.(pdf|png|jpeg|jpg|gif)/)
  480. )
  481. $scope.data.projectOutputFiles = filteredFiles
  482. $scope.data.buildId = __guard__(
  483. filteredFiles != null ? filteredFiles[0] : undefined,
  484. x => x.build
  485. )
  486. console.log('>> build_id', $scope.data.buildId)
  487. return _reset({ err: false })
  488. } else {
  489. $scope.data.projectOutputFiles = null
  490. return _reset({ err: true })
  491. }
  492. })
  493. .catch(function(err) {
  494. console.error(err)
  495. return _reset({ err: true })
  496. })
  497. }
  498. $scope.init = () => $scope.getUserProjects()
  499. $timeout($scope.init, 0)
  500. return $scope.$on('create', function() {
  501. let payload, provider
  502. const projectId = $scope.data.selectedProjectId
  503. const { name } = $scope.data
  504. if ($scope.state.isOutputFilesMode) {
  505. provider = 'project_output_file'
  506. payload = {
  507. source_project_id: projectId,
  508. source_output_file_path: $scope.data.selectedProjectOutputFile,
  509. build_id: $scope.data.buildId
  510. }
  511. } else {
  512. provider = 'project_file'
  513. payload = {
  514. source_project_id: projectId,
  515. source_entity_path: $scope.data.selectedProjectEntity
  516. }
  517. }
  518. _setInFlight('create')
  519. ide.fileTreeManager
  520. .createLinkedFile(name, $scope.parent_folder, provider, payload)
  521. .then(function() {
  522. _reset({ err: false })
  523. return $scope.$emit('done', { name: name })
  524. })
  525. .catch(function(response) {
  526. const { data } = response
  527. $scope.error = data
  528. })
  529. })
  530. })
  531. return App.controller('UrlLinkedFileModalController', function(
  532. $scope,
  533. ide,
  534. $timeout
  535. ) {
  536. $scope.inputs = {
  537. name: '',
  538. url: ''
  539. }
  540. $scope.nameChangedByUser = false
  541. $timeout(() => $scope.$broadcast('open'), 200)
  542. const validate = function() {
  543. const { name, url } = $scope.inputs
  544. if (name == null || name.length === 0) {
  545. return ($scope.state.valid = false)
  546. } else if (url == null || url.length === 0) {
  547. return ($scope.state.valid = false)
  548. } else {
  549. return ($scope.state.valid = true)
  550. }
  551. }
  552. $scope.$watch('inputs.name', validate)
  553. $scope.$watch('inputs.url', validate)
  554. $scope.$watch('inputs.url', function(url) {
  555. if (url != null && url !== '' && !$scope.nameChangedByUser) {
  556. url = url.replace('://', '') // Ignore http:// etc
  557. const parts = url.split('/').reverse()
  558. if (parts.length > 1) {
  559. // Wait for at one /
  560. return ($scope.inputs.name = parts[0])
  561. }
  562. }
  563. })
  564. return $scope.$on('create', function() {
  565. const { name, url } = $scope.inputs
  566. if (name == null || name.length === 0) {
  567. return
  568. }
  569. if (url == null || url.length === 0) {
  570. return
  571. }
  572. $scope.state.inflight = true
  573. return ide.fileTreeManager
  574. .createLinkedFile(name, $scope.parent_folder, 'url', { url })
  575. .then(function() {
  576. $scope.state.inflight = false
  577. return $scope.$emit('done', { name: name })
  578. })
  579. .catch(function(response) {
  580. const { data } = response
  581. $scope.error = data
  582. return ($scope.state.inflight = false)
  583. })
  584. })
  585. })
  586. })
  587. function __guard__(value, transform) {
  588. return typeof value !== 'undefined' && value !== null
  589. ? transform(value)
  590. : undefined
  591. }