FileTreeController.js 18 KB

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