project-list.js 26 KB

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