FileTreeController.js 19 KB

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