FileTreeController.js 18 KB

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