FileTreeController.js 17 KB

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