project-list.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. import _ from 'lodash'
  2. import App from '../../base'
  3. import './services/project-list'
  4. App.controller('ProjectPageController', function(
  5. $scope,
  6. $modal,
  7. $window,
  8. queuedHttp,
  9. eventTracking, // eslint-disable-line camelcase
  10. $timeout,
  11. localStorage,
  12. ProjectListService
  13. ) {
  14. $scope.projects = window.data.projects
  15. $scope.tags = window.data.tags
  16. $scope.notifications = window.data.notifications
  17. $scope.notificationsInstitution = window.data.notificationsInstitution
  18. $scope.allSelected = false
  19. $scope.selectedProjects = []
  20. $scope.filter = 'all'
  21. $scope.predicate = 'lastUpdated'
  22. $scope.nUntagged = 0
  23. $scope.reverse = true
  24. $scope.searchText = { value: '' }
  25. $scope.$watch('predicate', function(newValue) {
  26. $scope.comparator =
  27. newValue === 'ownerName' ? ownerNameComparator : defaultComparator
  28. })
  29. $timeout(() => recalculateProjectListHeight(), 10)
  30. $scope.$watch(
  31. () =>
  32. $scope.projects.filter(
  33. project =>
  34. (project.tags == null || project.tags.length === 0) &&
  35. !project.archived &&
  36. !project.trashed
  37. ).length,
  38. newVal => ($scope.nUntagged = newVal)
  39. )
  40. var recalculateProjectListHeight = function() {
  41. const $projListCard = $('.project-list-card')
  42. if (!$projListCard || !$projListCard.offset()) return
  43. const topOffset = $projListCard.offset().top
  44. const cardPadding = $projListCard.outerHeight() - $projListCard.height()
  45. const bottomOffset = $('footer').outerHeight()
  46. const height = $window.innerHeight - topOffset - bottomOffset - cardPadding
  47. $scope.projectListHeight = height
  48. }
  49. function defaultComparator(v1, v2) {
  50. var result = 0
  51. var type1 = v1.type
  52. var type2 = v2.type
  53. if ($scope.predicate === 'ownerName') {
  54. return
  55. }
  56. if (type1 === type2) {
  57. var value1 = v1.value
  58. var value2 = v2.value
  59. if (type1 === 'string') {
  60. // Compare strings case-insensitively
  61. value1 = value1.toLowerCase()
  62. value2 = value2.toLowerCase()
  63. } else if (type1 === 'object') {
  64. // For basic objects, use the position of the object
  65. // in the collection instead of the value
  66. if (angular.isObject(value1)) value1 = v1.index
  67. if (angular.isObject(value2)) value2 = v2.index
  68. }
  69. if (value1 !== value2) {
  70. result = value1 < value2 ? -1 : 1
  71. }
  72. } else {
  73. result = type1 < type2 ? -1 : 1
  74. }
  75. return result
  76. }
  77. function ownerNameComparator(v1, v2) {
  78. if ($scope.predicate !== 'ownerName') {
  79. return
  80. }
  81. if (v1.value === 'You') {
  82. if (v2.value === 'You') {
  83. return v1.index < v2.index ? -1 : 1
  84. } else {
  85. return 1
  86. }
  87. } else if (v1.value === 'An Overleaf v1 User' || v1.value === 'None') {
  88. if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
  89. return v1.index < v2.index ? -1 : 1
  90. } else {
  91. return -1
  92. }
  93. } else {
  94. if (v2.value === 'You') {
  95. return -1
  96. } else if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
  97. return 1
  98. } else {
  99. return v1.value > v2.value ? -1 : 1
  100. }
  101. }
  102. }
  103. angular.element($window).bind('resize', function() {
  104. recalculateProjectListHeight()
  105. $scope.$apply()
  106. })
  107. $scope.$on('project-list:notifications-received', () =>
  108. $scope.$applyAsync(() => recalculateProjectListHeight())
  109. )
  110. // Allow tags to be accessed on projects as well
  111. const projectsById = {}
  112. for (let project of $scope.projects) {
  113. projectsById[project.id] = project
  114. }
  115. $scope.getProjectById = id => projectsById[id]
  116. for (let tag of $scope.tags) {
  117. for (let projectId of tag.project_ids || []) {
  118. let project = projectsById[projectId]
  119. if (project) {
  120. if (!project.tags) {
  121. project.tags = []
  122. }
  123. project.tags.push(tag)
  124. }
  125. }
  126. }
  127. $scope.changePredicate = function(newPredicate) {
  128. if ($scope.predicate === newPredicate) {
  129. $scope.reverse = !$scope.reverse
  130. }
  131. $scope.predicate = newPredicate
  132. }
  133. $scope.getSortIconClass = function(column) {
  134. if (column === $scope.predicate && $scope.reverse) {
  135. return 'fa-caret-down'
  136. } else if (column === $scope.predicate && !$scope.reverse) {
  137. return 'fa-caret-up'
  138. } else {
  139. return ''
  140. }
  141. }
  142. $scope.searchProjects = function() {
  143. eventTracking.send(
  144. 'project-list-page-interaction',
  145. 'project-search',
  146. 'keydown'
  147. )
  148. $scope.updateVisibleProjects()
  149. }
  150. $scope.clearSearchText = function() {
  151. $scope.searchText.value = ''
  152. $scope.filter = 'all'
  153. $scope.$emit('search:clear')
  154. $scope.updateVisibleProjects()
  155. }
  156. $scope.setFilter = function(filter) {
  157. $scope.filter = filter
  158. $scope.updateVisibleProjects()
  159. }
  160. $scope.updateSelectedProjects = function() {
  161. $scope.selectedProjects = $scope.projects.filter(
  162. project => project.selected
  163. )
  164. }
  165. $scope.getSelectedProjects = () => $scope.selectedProjects
  166. $scope.getSelectedProjectIds = () =>
  167. $scope.selectedProjects.map(project => project.id)
  168. $scope.getFirstSelectedProject = () => $scope.selectedProjects[0]
  169. $scope.hasLeavableProjectsSelected = () =>
  170. _.some(
  171. $scope.getSelectedProjects(),
  172. project => project.accessLevel !== 'owner' && project.trashed
  173. )
  174. $scope.hasDeletableProjectsSelected = () =>
  175. _.some(
  176. $scope.getSelectedProjects(),
  177. project => project.accessLevel === 'owner' && project.trashed
  178. )
  179. $scope.updateVisibleProjects = function() {
  180. $scope.visibleProjects = []
  181. const selectedTag = $scope.getSelectedTag()
  182. for (let project of $scope.projects) {
  183. let visible = true
  184. // Only show if it matches any search text
  185. if ($scope.searchText.value !== '') {
  186. if (
  187. project.name
  188. .toLowerCase()
  189. .indexOf($scope.searchText.value.toLowerCase()) === -1
  190. ) {
  191. visible = false
  192. }
  193. }
  194. // Only show if it matches the selected tag
  195. if (
  196. $scope.filter === 'tag' &&
  197. selectedTag != null &&
  198. !selectedTag.project_ids.includes(project.id)
  199. ) {
  200. visible = false
  201. }
  202. // Hide tagged projects if we only want to see the uncategorized ones
  203. if (
  204. $scope.filter === 'untagged' &&
  205. (project.tags != null ? project.tags.length : undefined) > 0
  206. ) {
  207. visible = false
  208. }
  209. // Hide projects we own if we only want to see shared projects
  210. if ($scope.filter === 'shared' && project.accessLevel === 'owner') {
  211. visible = false
  212. }
  213. // Hide projects we don't own if we only want to see owned projects
  214. if ($scope.filter === 'owned' && project.accessLevel !== 'owner') {
  215. visible = false
  216. }
  217. if ($scope.filter === 'archived') {
  218. // Only show archived projects
  219. if (!project.archived) {
  220. visible = false
  221. }
  222. } else {
  223. // Only show non-archived projects
  224. if (project.archived) {
  225. visible = false
  226. }
  227. }
  228. if ($scope.filter === 'trashed') {
  229. // Only show trashed projects
  230. if (!project.trashed) {
  231. visible = false
  232. }
  233. } else {
  234. // Only show non-trashed projects
  235. if (project.trashed) {
  236. visible = false
  237. }
  238. }
  239. if (visible) {
  240. $scope.visibleProjects.push(project)
  241. } else {
  242. // We don't want hidden selections
  243. project.selected = false
  244. }
  245. }
  246. localStorage(
  247. 'project_list',
  248. JSON.stringify({
  249. filter: $scope.filter,
  250. selectedTagId: selectedTag != null ? selectedTag._id : undefined
  251. })
  252. )
  253. $scope.updateSelectedProjects()
  254. }
  255. $scope.getSelectedTag = function() {
  256. for (let tag of $scope.tags) {
  257. if (tag.selected) {
  258. return tag
  259. }
  260. }
  261. return null
  262. }
  263. $scope._removeProjectIdsFromTagArray = function(tag, removeProjectIds) {
  264. // Remove project_id from tag.project_ids
  265. const remainingProjectIds = []
  266. const removedProjectIds = []
  267. for (let projectId of tag.project_ids) {
  268. if (!removeProjectIds.includes(projectId)) {
  269. remainingProjectIds.push(projectId)
  270. } else {
  271. removedProjectIds.push(projectId)
  272. }
  273. }
  274. tag.project_ids = remainingProjectIds
  275. return removedProjectIds
  276. }
  277. $scope._removeProjectFromList = function(project) {
  278. const index = $scope.projects.indexOf(project)
  279. if (index > -1) {
  280. $scope.projects.splice(index, 1)
  281. }
  282. }
  283. $scope.removeSelectedProjectsFromTag = function(tag) {
  284. tag.showWhenEmpty = true
  285. const selectedProjectIds = $scope.getSelectedProjectIds()
  286. const selectedProjects = $scope.getSelectedProjects()
  287. const removedProjectIds = $scope._removeProjectIdsFromTagArray(
  288. tag,
  289. selectedProjectIds
  290. )
  291. // Remove tag from project.tags
  292. for (let project of selectedProjects) {
  293. if (!project.tags) {
  294. project.tags = []
  295. }
  296. const index = project.tags.indexOf(tag)
  297. if (index > -1) {
  298. project.tags.splice(index, 1)
  299. }
  300. }
  301. for (let projectId of removedProjectIds) {
  302. queuedHttp({
  303. method: 'DELETE',
  304. url: `/tag/${tag._id}/project/${projectId}`,
  305. headers: {
  306. 'X-CSRF-Token': window.csrfToken
  307. }
  308. })
  309. }
  310. // If we're filtering by this tag then we need to remove
  311. // the projects from view
  312. $scope.updateVisibleProjects()
  313. }
  314. $scope.removeProjectFromTag = function(project, tag) {
  315. tag.showWhenEmpty = true
  316. if (!project.tags) {
  317. project.tags = []
  318. }
  319. const index = project.tags.indexOf(tag)
  320. if (index > -1) {
  321. $scope._removeProjectIdsFromTagArray(tag, [project.id])
  322. project.tags.splice(index, 1)
  323. queuedHttp({
  324. method: 'DELETE',
  325. url: `/tag/${tag._id}/project/${project.id}`,
  326. headers: {
  327. 'X-CSRF-Token': window.csrfToken
  328. }
  329. })
  330. $scope.updateVisibleProjects()
  331. }
  332. }
  333. $scope.addSelectedProjectsToTag = function(tag) {
  334. const selectedProjects = $scope.getSelectedProjects()
  335. eventTracking.send(
  336. 'project-list-page-interaction',
  337. 'project action',
  338. 'addSelectedProjectsToTag'
  339. )
  340. // Add project_ids into tag.project_ids
  341. const addedProjectIds = []
  342. for (let projectId of $scope.getSelectedProjectIds()) {
  343. if (!tag.project_ids.includes(projectId)) {
  344. tag.project_ids.push(projectId)
  345. addedProjectIds.push(projectId)
  346. }
  347. }
  348. // Add tag into each project.tags
  349. for (let project of selectedProjects) {
  350. if (!project.tags) {
  351. project.tags = []
  352. }
  353. if (!project.tags.includes(tag)) {
  354. project.tags.push(tag)
  355. }
  356. }
  357. for (let projectId of addedProjectIds) {
  358. queuedHttp.post(`/tag/${tag._id}/project/${projectId}`, {
  359. _csrf: window.csrfToken
  360. })
  361. }
  362. }
  363. $scope.openNewTagModal = function(e) {
  364. const modalInstance = $modal.open({
  365. templateUrl: 'newTagModalTemplate',
  366. controller: 'NewTagModalController'
  367. })
  368. modalInstance.result.then(function(tag) {
  369. const tagIsDuplicate = $scope.tags.find(function(existingTag) {
  370. return tag.name === existingTag.name
  371. })
  372. if (!tagIsDuplicate) {
  373. $scope.tags.push(tag)
  374. $scope.addSelectedProjectsToTag(tag)
  375. }
  376. })
  377. }
  378. $scope.createProject = function(name, template) {
  379. if (template == null) {
  380. template = 'none'
  381. }
  382. return queuedHttp
  383. .post('/project/new', {
  384. _csrf: window.csrfToken,
  385. projectName: name,
  386. template
  387. })
  388. .then(function(response) {
  389. const { data } = response
  390. $scope.projects.push({
  391. name,
  392. _id: data.project_id,
  393. accessLevel: 'owner',
  394. owner: data.owner
  395. // TODO: Check access level if correct after adding it in
  396. // to the rest of the app
  397. })
  398. $scope.updateVisibleProjects()
  399. })
  400. }
  401. $scope.openCreateProjectModal = function(template) {
  402. if (template == null) {
  403. template = 'none'
  404. }
  405. eventTracking.send('project-list-page-interaction', 'new-project', template)
  406. const modalInstance = $modal.open({
  407. templateUrl: 'newProjectModalTemplate',
  408. controller: 'NewProjectModalController',
  409. resolve: {
  410. template() {
  411. return template
  412. }
  413. },
  414. scope: $scope
  415. })
  416. modalInstance.result.then(
  417. projectId => (window.location = `/project/${projectId}`)
  418. )
  419. }
  420. $scope.renameProject = (project, newName) =>
  421. queuedHttp
  422. .post(`/project/${project.id}/rename`, {
  423. newProjectName: newName,
  424. _csrf: window.csrfToken
  425. })
  426. .then(() => (project.name = newName))
  427. $scope.openRenameProjectModal = function() {
  428. let project = $scope.getFirstSelectedProject()
  429. if (!project || project.accessLevel !== 'owner') {
  430. return
  431. }
  432. eventTracking.send(
  433. 'project-list-page-interaction',
  434. 'project action',
  435. 'Rename'
  436. )
  437. $modal.open({
  438. templateUrl: 'renameProjectModalTemplate',
  439. controller: 'RenameProjectModalController',
  440. resolve: {
  441. project() {
  442. return project
  443. }
  444. },
  445. scope: $scope
  446. })
  447. }
  448. $scope.cloneProject = function(project, cloneName) {
  449. eventTracking.send(
  450. 'project-list-page-interaction',
  451. 'project action',
  452. 'Clone'
  453. )
  454. return queuedHttp
  455. .post(`/project/${project.id}/clone`, {
  456. _csrf: window.csrfToken,
  457. projectName: cloneName
  458. })
  459. .then(function(response) {
  460. const { data } = response
  461. $scope.projects.push({
  462. name: data.name,
  463. id: data.project_id,
  464. accessLevel: 'owner',
  465. owner: data.owner
  466. // TODO: Check access level if correct after adding it in
  467. // to the rest of the app
  468. })
  469. $scope.updateVisibleProjects()
  470. })
  471. }
  472. $scope.openCloneProjectModal = function(project) {
  473. if (!project) {
  474. return
  475. }
  476. $modal.open({
  477. templateUrl: 'cloneProjectModalTemplate',
  478. controller: 'CloneProjectModalController',
  479. resolve: {
  480. project() {
  481. return project
  482. }
  483. },
  484. scope: $scope
  485. })
  486. }
  487. // Methods to create modals for archiving, trashing, leaving and deleting projects
  488. const _createArchiveTrashLeaveOrDeleteProjectsModal = function(
  489. action,
  490. projects
  491. ) {
  492. eventTracking.send(
  493. 'project-list-page-interaction',
  494. 'project action',
  495. action
  496. )
  497. return $modal.open({
  498. templateUrl: 'archiveTrashLeaveOrDeleteProjectsModalTemplate',
  499. controller: 'ArchiveTrashLeaveOrDeleteProjectsModalController',
  500. resolve: {
  501. projects() {
  502. return projects
  503. },
  504. action() {
  505. return action
  506. }
  507. }
  508. })
  509. }
  510. $scope.createArchiveProjectsModal = function(projects) {
  511. return _createArchiveTrashLeaveOrDeleteProjectsModal('archive', projects)
  512. }
  513. $scope.createTrashProjectsModal = function(projects) {
  514. return _createArchiveTrashLeaveOrDeleteProjectsModal('trash', projects)
  515. }
  516. $scope.createLeaveProjectsModal = function(projects) {
  517. return _createArchiveTrashLeaveOrDeleteProjectsModal('leave', projects)
  518. }
  519. $scope.createDeleteProjectsModal = function(projects) {
  520. return _createArchiveTrashLeaveOrDeleteProjectsModal('delete', projects)
  521. }
  522. $scope.createLeaveOrDeleteProjectsModal = function(projects) {
  523. return _createArchiveTrashLeaveOrDeleteProjectsModal(
  524. 'leaveOrDelete',
  525. projects
  526. )
  527. }
  528. //
  529. $scope.openArchiveProjectsModal = function() {
  530. const modalInstance = $scope.createArchiveProjectsModal(
  531. $scope.getSelectedProjects()
  532. )
  533. modalInstance.result.then(() => $scope.archiveSelectedProjects())
  534. }
  535. $scope.openTrashProjectsModal = function() {
  536. const modalInstance = $scope.createTrashProjectsModal(
  537. $scope.getSelectedProjects()
  538. )
  539. modalInstance.result.then(() => $scope.trashSelectedProjects())
  540. }
  541. $scope.openLeaveProjectsModal = function() {
  542. const modalInstance = $scope.createLeaveProjectsModal(
  543. $scope.getSelectedProjects()
  544. )
  545. modalInstance.result.then(() => $scope.leaveSelectedProjects())
  546. }
  547. $scope.openDeleteProjectsModal = function() {
  548. const modalInstance = $scope.createDeleteProjectsModal(
  549. $scope.getSelectedProjects()
  550. )
  551. modalInstance.result.then(() => $scope.deleteSelectedProjects())
  552. }
  553. $scope.openLeaveOrDeleteProjectsModal = function() {
  554. const modalInstance = $scope.createLeaveOrDeleteProjectsModal(
  555. $scope.getSelectedProjects()
  556. )
  557. modalInstance.result.then(() => $scope.leaveOrDeleteSelectedProjects())
  558. }
  559. //
  560. $scope.archiveSelectedProjects = () =>
  561. $scope.archiveProjects($scope.getSelectedProjects())
  562. $scope.unarchiveSelectedProjects = () =>
  563. $scope.unarchiveProjects($scope.getSelectedProjects())
  564. $scope.trashSelectedProjects = () =>
  565. $scope.trashProjects($scope.getSelectedProjects())
  566. $scope.untrashSelectedProjects = () =>
  567. $scope.untrashProjects($scope.getSelectedProjects())
  568. $scope.leaveSelectedProjects = () =>
  569. $scope.leaveProjects($scope.getSelectedProjects())
  570. $scope.deleteSelectedProjects = () =>
  571. $scope.deleteProjects($scope.getSelectedProjects())
  572. $scope.leaveOrDeleteSelectedProjects = () =>
  573. $scope.leaveOrDeleteProjects($scope.getSelectedProjects())
  574. //
  575. $scope.archiveProjects = function(projects) {
  576. for (let project of projects) {
  577. project.archived = true
  578. project.trashed = false
  579. _archiveProject(project)
  580. }
  581. $scope.updateVisibleProjects()
  582. }
  583. $scope.unarchiveProjects = function(projects) {
  584. for (let project of projects) {
  585. project.archived = false
  586. _unarchiveProject(project)
  587. }
  588. $scope.updateVisibleProjects()
  589. }
  590. $scope.trashProjects = function(projects) {
  591. for (let project of projects) {
  592. project.trashed = true
  593. project.archived = false
  594. _trashProject(project)
  595. }
  596. $scope.updateVisibleProjects()
  597. }
  598. $scope.untrashProjects = function(projects) {
  599. for (let project of projects) {
  600. project.trashed = false
  601. _untrashProject(project)
  602. }
  603. $scope.updateVisibleProjects()
  604. }
  605. $scope.leaveProjects = function(projects) {
  606. _deleteOrLeaveProjectsLocally(projects)
  607. for (let project of projects) {
  608. _leaveProject(project)
  609. }
  610. $scope.updateVisibleProjects()
  611. }
  612. $scope.deleteProjects = function(projects) {
  613. _deleteOrLeaveProjectsLocally(projects)
  614. for (let project of projects) {
  615. _deleteProject(project)
  616. }
  617. $scope.updateVisibleProjects()
  618. }
  619. $scope.leaveOrDeleteProjects = function(projects) {
  620. _deleteOrLeaveProjectsLocally(projects)
  621. for (let project of projects) {
  622. if (project.accessLevel === 'owner') {
  623. _deleteProject(project)
  624. } else {
  625. _leaveProject(project)
  626. }
  627. }
  628. $scope.updateVisibleProjects()
  629. }
  630. // Actual interaction with the backend---we could move this into a service
  631. const _archiveProject = function(project) {
  632. return queuedHttp({
  633. method: 'POST',
  634. url: `/project/${project.id}/archive`,
  635. headers: {
  636. 'X-CSRF-Token': window.csrfToken
  637. }
  638. })
  639. }
  640. const _unarchiveProject = function(project) {
  641. return queuedHttp({
  642. method: 'DELETE',
  643. url: `/project/${project.id}/archive`,
  644. headers: {
  645. 'X-CSRF-Token': window.csrfToken
  646. }
  647. })
  648. }
  649. const _trashProject = function(project) {
  650. return queuedHttp({
  651. method: 'POST',
  652. url: `/project/${project.id}/trash`,
  653. headers: {
  654. 'X-CSRF-Token': window.csrfToken
  655. }
  656. })
  657. }
  658. const _untrashProject = function(project) {
  659. return queuedHttp({
  660. method: 'DELETE',
  661. url: `/project/${project.id}/trash`,
  662. headers: {
  663. 'X-CSRF-Token': window.csrfToken
  664. }
  665. })
  666. }
  667. const _leaveProject = function(project) {
  668. return queuedHttp({
  669. method: 'POST',
  670. url: `/project/${project.id}/leave`,
  671. headers: {
  672. 'X-CSRF-Token': window.csrfToken
  673. }
  674. })
  675. }
  676. const _deleteProject = function(project) {
  677. return queuedHttp({
  678. method: 'DELETE',
  679. url: `/project/${project.id}`,
  680. headers: {
  681. 'X-CSRF-Token': window.csrfToken
  682. }
  683. })
  684. }
  685. const _deleteOrLeaveProjectsLocally = function(projects) {
  686. const projectIds = projects.map(p => p.id)
  687. for (let tag of $scope.tags || []) {
  688. $scope._removeProjectIdsFromTagArray(tag, projectIds)
  689. }
  690. for (let project of projects || []) {
  691. $scope._removeProjectFromList(project)
  692. }
  693. }
  694. $scope.getValueForCurrentPredicate = function(project) {
  695. if ($scope.predicate === 'ownerName') {
  696. return ProjectListService.getOwnerName(project)
  697. } else {
  698. return project[$scope.predicate]
  699. }
  700. }
  701. $scope.openUploadProjectModal = function() {
  702. $modal.open({
  703. templateUrl: 'uploadProjectModalTemplate',
  704. controller: 'UploadProjectModalController'
  705. })
  706. }
  707. $scope.downloadSelectedProjects = () =>
  708. $scope.downloadProjectsById($scope.getSelectedProjectIds())
  709. $scope.downloadProjectsById = function(projectIds) {
  710. let path
  711. eventTracking.send(
  712. 'project-list-page-interaction',
  713. 'project action',
  714. 'Download Zip'
  715. )
  716. if (projectIds.length > 1) {
  717. path = `/project/download/zip?project_ids=${projectIds.join(',')}`
  718. } else {
  719. path = `/project/${projectIds[0]}/download/zip`
  720. }
  721. return (window.location = path)
  722. }
  723. const markTagAsSelected = id => {
  724. for (let tag of $scope.tags) {
  725. if (tag._id === id) {
  726. tag.selected = true
  727. } else {
  728. tag.selected = false
  729. }
  730. }
  731. }
  732. const storedUIOpts = JSON.parse(localStorage('project_list'))
  733. if (storedUIOpts && storedUIOpts.filter) {
  734. if (storedUIOpts.filter === 'tag' && storedUIOpts.selectedTagId) {
  735. markTagAsSelected(storedUIOpts.selectedTagId)
  736. }
  737. $scope.setFilter(storedUIOpts.filter)
  738. } else {
  739. $scope.updateVisibleProjects()
  740. }
  741. })
  742. App.controller('ProjectListItemController', function(
  743. $scope,
  744. $modal,
  745. queuedHttp,
  746. ProjectListService
  747. ) {
  748. $scope.projectLink = function(project) {
  749. return `/project/${project.id}`
  750. }
  751. $scope.isLinkSharingProject = project => project.source === 'token'
  752. $scope.hasGenericOwnerName = () => {
  753. /* eslint-disable camelcase */
  754. const { first_name, last_name, email } = $scope.project.owner
  755. return !first_name && !last_name && !email
  756. /* eslint-enable camelcase */
  757. }
  758. $scope.getOwnerName = ProjectListService.getOwnerName
  759. $scope.getUserName = ProjectListService.getUserName
  760. $scope.isOwner = () =>
  761. $scope.project.owner && window.user_id === $scope.project.owner._id
  762. $scope.$watch('project.selected', function(value) {
  763. if (value != null) {
  764. $scope.updateSelectedProjects()
  765. }
  766. })
  767. $scope.clone = function(e) {
  768. e.stopPropagation()
  769. $scope.openCloneProjectModal($scope.project)
  770. }
  771. $scope.download = function(e) {
  772. e.stopPropagation()
  773. $scope.downloadProjectsById([$scope.project.id])
  774. }
  775. $scope.archive = function(e) {
  776. e.stopPropagation()
  777. $scope.createArchiveProjectsModal([$scope.project]).result.then(() => {
  778. $scope.archiveProjects([$scope.project])
  779. })
  780. }
  781. $scope.unarchive = function(e) {
  782. e.stopPropagation()
  783. $scope.unarchiveProjects([$scope.project])
  784. }
  785. $scope.trash = function(e) {
  786. e.stopPropagation()
  787. $scope.createTrashProjectsModal([$scope.project]).result.then(() => {
  788. $scope.trashProjects([$scope.project])
  789. })
  790. }
  791. $scope.untrash = function(e) {
  792. e.stopPropagation()
  793. $scope.untrashProjects([$scope.project])
  794. }
  795. $scope.leave = function(e) {
  796. e.stopPropagation()
  797. $scope.createLeaveProjectsModal([$scope.project]).result.then(() => {
  798. $scope.leaveProjects([$scope.project])
  799. })
  800. }
  801. $scope.delete = function(e) {
  802. e.stopPropagation()
  803. $scope.createDeleteProjectsModal([$scope.project]).result.then(() => {
  804. $scope.deleteProjects([$scope.project])
  805. })
  806. }
  807. })