FileTreeController.js 18 KB

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