project-list.js 26 KB

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