FileTreeController.js 17 KB

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