FileTreeController.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  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 = 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/${
  253. ide.project_id
  254. }`)
  255. } else {
  256. decreseTimeout()
  257. return ($scope.secondsToRedirect = $scope.secondsToRedirect - 1)
  258. }
  259. }, 1000)
  260. return decreseTimeout()
  261. }
  262. $scope.max_files = 40
  263. $scope.onComplete = (error, name, response) =>
  264. $timeout(function() {
  265. uploadCount--
  266. if (response.success) {
  267. $rootScope.$broadcast('file:upload:complete', response)
  268. }
  269. if (uploadCount === 0 && response != null && response.success) {
  270. return $scope.$emit('done', { name: name })
  271. }
  272. }, 250)
  273. $scope.onValidateBatch = function(files) {
  274. if (files.length > $scope.max_files) {
  275. $timeout(() => ($scope.tooManyFiles = true), 1)
  276. return false
  277. } else {
  278. return true
  279. }
  280. }
  281. $scope.onError = function(id, name, reason) {
  282. console.log(id, name, reason)
  283. if (reason.indexOf('429') !== -1) {
  284. return ($scope.rateLimitHit = true)
  285. } else if (reason.indexOf('403') !== -1) {
  286. return needToLogBackIn()
  287. }
  288. }
  289. let _uploadTimer = null
  290. const uploadIfNoConflicts = function() {
  291. if ($scope.conflicts.length === 0) {
  292. return $scope.doUpload()
  293. }
  294. }
  295. var uploadCount = 0
  296. $scope.onSubmit = function(id, name) {
  297. uploadCount++
  298. if (ide.fileTreeManager.existsInFolder($scope.parent_folder_id, name)) {
  299. $scope.conflicts.push(name)
  300. $scope.$apply()
  301. }
  302. if (_uploadTimer == null) {
  303. _uploadTimer = setTimeout(function() {
  304. _uploadTimer = null
  305. return uploadIfNoConflicts()
  306. }, 0)
  307. }
  308. return true
  309. }
  310. $scope.onCancel = function(id, name) {
  311. uploadCount--
  312. const index = $scope.conflicts.indexOf(name)
  313. if (index > -1) {
  314. $scope.conflicts.splice(index, 1)
  315. }
  316. $scope.$apply()
  317. return uploadIfNoConflicts()
  318. }
  319. return ($scope.doUpload = () =>
  320. __guard__($scope.control != null ? $scope.control.q : undefined, x =>
  321. x.uploadStoredFiles()
  322. ))
  323. })
  324. App.controller('ProjectLinkedFileModalController', function(
  325. $scope,
  326. ide,
  327. $timeout
  328. ) {
  329. $scope.data = {
  330. projects: null, // or []
  331. selectedProjectId: null,
  332. projectEntities: null, // or []
  333. projectOutputFiles: null, // or []
  334. selectedProjectEntity: null,
  335. selectedProjectOutputFile: null,
  336. buildId: null,
  337. name: null
  338. }
  339. $scope.state.inFlight = {
  340. projects: false,
  341. entities: false,
  342. compile: false
  343. }
  344. $scope.state.isOutputFilesMode = false
  345. $scope.state.error = false
  346. $scope.$watch('data.selectedProjectId', function(newVal, oldVal) {
  347. if (!newVal) {
  348. return
  349. }
  350. $scope.data.selectedProjectEntity = null
  351. $scope.data.selectedProjectOutputFile = null
  352. if ($scope.state.isOutputFilesMode) {
  353. return $scope.compileProjectAndGetOutputFiles(
  354. $scope.data.selectedProjectId
  355. )
  356. } else {
  357. return $scope.getProjectEntities($scope.data.selectedProjectId)
  358. }
  359. })
  360. $scope.$watch('state.isOutputFilesMode', function(newVal, oldVal) {
  361. if (!newVal && !oldVal) {
  362. return
  363. }
  364. $scope.data.selectedProjectOutputFile = null
  365. if (newVal === true) {
  366. return $scope.compileProjectAndGetOutputFiles(
  367. $scope.data.selectedProjectId
  368. )
  369. } else {
  370. return $scope.getProjectEntities($scope.data.selectedProjectId)
  371. }
  372. })
  373. // auto-set filename based on selected file
  374. $scope.$watch('data.selectedProjectEntity', function(newVal, oldVal) {
  375. if (!newVal) {
  376. return
  377. }
  378. const fileName = newVal.split('/').reverse()[0]
  379. if (fileName) {
  380. $scope.data.name = fileName
  381. }
  382. })
  383. // auto-set filename based on selected file
  384. $scope.$watch('data.selectedProjectOutputFile', function(newVal, oldVal) {
  385. if (!newVal) {
  386. return
  387. }
  388. if (newVal === 'output.pdf') {
  389. const project = _.find(
  390. $scope.data.projects,
  391. p => p._id === $scope.data.selectedProjectId
  392. )
  393. $scope.data.name =
  394. (project != null ? project.name : undefined) != null
  395. ? `${project.name}.pdf`
  396. : 'output.pdf'
  397. } else {
  398. const fileName = newVal.split('/').reverse()[0]
  399. if (fileName) {
  400. $scope.data.name = fileName
  401. }
  402. }
  403. })
  404. const _setInFlight = type => ($scope.state.inFlight[type] = true)
  405. const _reset = function(opts) {
  406. const isError = opts.err === true
  407. const { inFlight } = $scope.state
  408. inFlight.projects = inFlight.entities = inFlight.compile = false
  409. $scope.state.inflight = false
  410. return ($scope.state.error = isError)
  411. }
  412. $scope.toggleOutputFilesMode = function() {
  413. if (!$scope.data.selectedProjectId) {
  414. return
  415. }
  416. return ($scope.state.isOutputFilesMode = !$scope.state.isOutputFilesMode)
  417. }
  418. $scope.shouldEnableProjectSelect = function() {
  419. const { state, data } = $scope
  420. return !state.inFlight.projects && data.projects
  421. }
  422. $scope.hasNoProjects = function() {
  423. const { state, data } = $scope
  424. return (
  425. !state.inFlight.projects &&
  426. (data.projects == null || data.projects.length === 0)
  427. )
  428. }
  429. $scope.shouldEnableProjectEntitySelect = function() {
  430. const { state, data } = $scope
  431. return (
  432. !state.inFlight.projects &&
  433. !state.inFlight.entities &&
  434. data.projects &&
  435. data.selectedProjectId
  436. )
  437. }
  438. $scope.shouldEnableProjectOutputFileSelect = function() {
  439. const { state, data } = $scope
  440. return (
  441. !state.inFlight.projects &&
  442. !state.inFlight.compile &&
  443. data.projects &&
  444. data.selectedProjectId
  445. )
  446. }
  447. const validate = function() {
  448. const { state } = $scope
  449. const { data } = $scope
  450. $scope.state.valid =
  451. !state.inFlight.projects &&
  452. !state.inFlight.entities &&
  453. data.projects &&
  454. data.selectedProjectId &&
  455. ((!$scope.state.isOutputFilesMode &&
  456. data.projectEntities &&
  457. data.selectedProjectEntity) ||
  458. ($scope.state.isOutputFilesMode &&
  459. data.projectOutputFiles &&
  460. data.selectedProjectOutputFile)) &&
  461. data.name
  462. }
  463. $scope.$watch('state', validate, true)
  464. $scope.$watch('data', validate, true)
  465. $scope.getUserProjects = function() {
  466. _setInFlight('projects')
  467. return ide.$http
  468. .get('/user/projects', {
  469. _csrf: window.csrfToken
  470. })
  471. .then(function(resp) {
  472. $scope.data.projectEntities = null
  473. $scope.data.projects = resp.data.projects.filter(
  474. p => p._id !== ide.project_id
  475. )
  476. return _reset({ err: false })
  477. })
  478. .catch(err => _reset({ err: true }))
  479. }
  480. $scope.getProjectEntities = project_id => {
  481. _setInFlight('entities')
  482. return ide.$http
  483. .get(`/project/${project_id}/entities`, {
  484. _csrf: window.csrfToken
  485. })
  486. .then(function(resp) {
  487. if ($scope.data.selectedProjectId === resp.data.project_id) {
  488. $scope.data.projectEntities = resp.data.entities
  489. return _reset({ err: false })
  490. }
  491. })
  492. .catch(err => _reset({ err: true }))
  493. }
  494. $scope.compileProjectAndGetOutputFiles = project_id => {
  495. _setInFlight('compile')
  496. return ide.$http
  497. .post(`/project/${project_id}/compile`, {
  498. check: 'silent',
  499. draft: false,
  500. incrementalCompilesEnabled: false,
  501. _csrf: window.csrfToken
  502. })
  503. .then(function(resp) {
  504. if (resp.data.status === 'success') {
  505. const filteredFiles = resp.data.outputFiles.filter(f =>
  506. f.path.match(/.*\.(pdf|png|jpeg|jpg|gif)/)
  507. )
  508. $scope.data.projectOutputFiles = filteredFiles
  509. $scope.data.buildId = __guard__(
  510. filteredFiles != null ? filteredFiles[0] : undefined,
  511. x => x.build
  512. )
  513. console.log('>> build_id', $scope.data.buildId)
  514. return _reset({ err: false })
  515. } else {
  516. $scope.data.projectOutputFiles = null
  517. return _reset({ err: true })
  518. }
  519. })
  520. .catch(function(err) {
  521. console.error(err)
  522. return _reset({ err: true })
  523. })
  524. }
  525. $scope.init = () => $scope.getUserProjects()
  526. $timeout($scope.init, 0)
  527. return $scope.$on('create', function() {
  528. let payload, provider
  529. const projectId = $scope.data.selectedProjectId
  530. const { name } = $scope.data
  531. if ($scope.state.isOutputFilesMode) {
  532. provider = 'project_output_file'
  533. payload = {
  534. source_project_id: projectId,
  535. source_output_file_path: $scope.data.selectedProjectOutputFile,
  536. build_id: $scope.data.buildId
  537. }
  538. } else {
  539. provider = 'project_file'
  540. payload = {
  541. source_project_id: projectId,
  542. source_entity_path: $scope.data.selectedProjectEntity
  543. }
  544. }
  545. _setInFlight('create')
  546. ide.fileTreeManager
  547. .createLinkedFile(name, $scope.parent_folder, provider, payload)
  548. .then(function() {
  549. _reset({ err: false })
  550. return $scope.$emit('done', { name: name })
  551. })
  552. .catch(function(response) {
  553. const { data } = response
  554. $scope.error = data
  555. })
  556. .finally(function() {
  557. if (!$scope.$$phase) {
  558. $scope.$apply()
  559. }
  560. })
  561. })
  562. })
  563. export default App.controller('UrlLinkedFileModalController', function(
  564. $scope,
  565. ide,
  566. $timeout
  567. ) {
  568. $scope.inputs = {
  569. name: '',
  570. url: ''
  571. }
  572. $scope.nameChangedByUser = false
  573. $timeout(() => $scope.$broadcast('open'), 200)
  574. const validate = function() {
  575. const { name, url } = $scope.inputs
  576. if (name == null || name.length === 0) {
  577. return ($scope.state.valid = false)
  578. } else if (url == null || url.length === 0) {
  579. return ($scope.state.valid = false)
  580. } else {
  581. return ($scope.state.valid = true)
  582. }
  583. }
  584. $scope.$watch('inputs.name', validate)
  585. $scope.$watch('inputs.url', validate)
  586. $scope.$watch('inputs.url', function(url) {
  587. if (url != null && url !== '' && !$scope.nameChangedByUser) {
  588. url = url.replace('://', '') // Ignore http:// etc
  589. const parts = url.split('/').reverse()
  590. if (parts.length > 1) {
  591. // Wait for at one /
  592. return ($scope.inputs.name = parts[0])
  593. }
  594. }
  595. })
  596. return $scope.$on('create', function() {
  597. const { name, url } = $scope.inputs
  598. if (name == null || name.length === 0) {
  599. return
  600. }
  601. if (url == null || url.length === 0) {
  602. return
  603. }
  604. $scope.state.inflight = true
  605. return ide.fileTreeManager
  606. .createLinkedFile(name, $scope.parent_folder, 'url', { url })
  607. .then(function() {
  608. $scope.state.inflight = false
  609. return $scope.$emit('done', { name: name })
  610. })
  611. .catch(function(response) {
  612. const { data } = response
  613. $scope.error = data
  614. return ($scope.state.inflight = false)
  615. })
  616. .finally(function() {
  617. if (!$scope.$$phase) {
  618. $scope.$apply()
  619. }
  620. })
  621. })
  622. })
  623. function __guard__(value, transform) {
  624. return typeof value !== 'undefined' && value !== null
  625. ? transform(value)
  626. : undefined
  627. }