FileTreeController.js 18 KB

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