listHeader.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { TAPi18n } from '/imports/i18n';
  3. let listsColors;
  4. Meteor.startup(() => {
  5. listsColors = Lists.simpleSchema()._schema.color.allowedValues;
  6. });
  7. BlazeComponent.extendComponent({
  8. canSeeAddCard() {
  9. const list = Template.currentData();
  10. return (
  11. (!list.getWipLimit('enabled') ||
  12. list.getWipLimit('soft') ||
  13. !this.reachedWipLimit()) &&
  14. !ReactiveCache.getCurrentUser().isWorker()
  15. );
  16. },
  17. isBoardAdmin() {
  18. return ReactiveCache.getCurrentUser().isBoardAdmin();
  19. },
  20. starred(check = undefined) {
  21. const list = Template.currentData();
  22. const status = list.isStarred();
  23. if (check === undefined) {
  24. // just check
  25. return status;
  26. } else {
  27. list.star(!status);
  28. return !status;
  29. }
  30. },
  31. listCollapsed(check = undefined) {
  32. const user = Meteor.user();
  33. const status = user.hasCollapsedList(this._id);
  34. if (check === undefined) {
  35. // just check
  36. return status;
  37. } else {
  38. user.toggleCollapseList(this._id);
  39. return !status;
  40. }
  41. },
  42. editTitle(event) {
  43. event.preventDefault();
  44. const newTitle = this.childComponents('inlinedForm')[0]
  45. .getValue()
  46. .trim();
  47. const list = this.currentData();
  48. if (newTitle) {
  49. list.rename(newTitle.trim());
  50. }
  51. },
  52. isWatching() {
  53. const list = this.currentData();
  54. return list.findWatcher(Meteor.userId());
  55. },
  56. limitToShowCardsCount() {
  57. const currentUser = ReactiveCache.getCurrentUser();
  58. if (currentUser) {
  59. return currentUser.getLimitToShowCardsCount();
  60. } else {
  61. return false;
  62. }
  63. },
  64. cardsCount() {
  65. const list = Template.currentData();
  66. let swimlaneId = '';
  67. if (Utils.boardView() === 'board-view-swimlanes')
  68. swimlaneId = this.parentComponent()
  69. .parentComponent()
  70. .data()._id;
  71. const ret = list.cards(swimlaneId).length;
  72. return ret;
  73. },
  74. reachedWipLimit() {
  75. const list = Template.currentData();
  76. return (
  77. list.getWipLimit('enabled') &&
  78. list.getWipLimit('value') <= list.cards().length
  79. );
  80. },
  81. exceededWipLimit() {
  82. const list = Template.currentData();
  83. return (
  84. list.getWipLimit('enabled') &&
  85. list.getWipLimit('value') < list.cards().length
  86. );
  87. },
  88. showCardsCountForList(count) {
  89. const limit = this.limitToShowCardsCount();
  90. return limit >= 0 && count >= limit;
  91. },
  92. cardsCountForListIsOne(count) {
  93. if (count === 1) {
  94. return TAPi18n.__('cards-count-one');
  95. } else {
  96. return TAPi18n.__('cards-count');
  97. }
  98. },
  99. events() {
  100. return [
  101. {
  102. 'click .js-list-star'(event) {
  103. event.preventDefault();
  104. this.starred(!this.starred());
  105. },
  106. 'click .js-collapse-list'(event) {
  107. event.preventDefault();
  108. this.listCollapsed(!this.listCollapsed());
  109. },
  110. 'click .js-open-list-menu': Popup.open('listAction'),
  111. 'click .js-add-card.list-header-plus-top'(event) {
  112. const listDom = $(event.target).parents(
  113. `#js-list-${this.currentData()._id}`,
  114. )[0];
  115. const listComponent = BlazeComponent.getComponentForElement(listDom);
  116. listComponent.openForm({
  117. position: 'top',
  118. });
  119. },
  120. 'click .js-unselect-list'() {
  121. Session.set('currentList', null);
  122. },
  123. submit: this.editTitle,
  124. },
  125. ];
  126. },
  127. }).register('listHeader');
  128. Template.listHeader.helpers({
  129. isBoardAdmin() {
  130. return ReactiveCache.getCurrentUser().isBoardAdmin();
  131. }
  132. });
  133. Template.listActionPopup.helpers({
  134. isBoardAdmin() {
  135. return ReactiveCache.getCurrentUser().isBoardAdmin();
  136. },
  137. isWipLimitEnabled() {
  138. return Template.currentData().getWipLimit('enabled');
  139. },
  140. isWatching() {
  141. return this.findWatcher(Meteor.userId());
  142. },
  143. });
  144. Template.listActionPopup.events({
  145. 'click .js-list-subscribe'() {},
  146. 'click .js-add-card.list-header-plus-bottom'(event) {
  147. const listDom = $(`#js-list-${this._id}`)[0];
  148. const listComponent = BlazeComponent.getComponentForElement(listDom);
  149. listComponent.openForm({
  150. position: 'bottom',
  151. });
  152. Popup.back();
  153. },
  154. 'click .js-set-list-width': Popup.open('setListWidth'),
  155. 'click .js-set-color-list': Popup.open('setListColor'),
  156. 'click .js-select-cards'() {
  157. const cardIds = this.allCards().map(card => card._id);
  158. MultiSelection.add(cardIds);
  159. Popup.back();
  160. },
  161. 'click .js-toggle-watch-list'() {
  162. const currentList = this;
  163. const level = currentList.findWatcher(Meteor.userId()) ? null : 'watching';
  164. Meteor.call('watch', 'list', currentList._id, level, (err, ret) => {
  165. if (!err && ret) Popup.back();
  166. });
  167. },
  168. 'click .js-close-list'(event) {
  169. event.preventDefault();
  170. this.archive();
  171. Popup.back();
  172. },
  173. 'click .js-set-wip-limit': Popup.open('setWipLimit'),
  174. 'click .js-more': Popup.open('listMore'),
  175. });
  176. BlazeComponent.extendComponent({
  177. applyWipLimit() {
  178. const list = Template.currentData();
  179. const limit = parseInt(
  180. Template.instance()
  181. .$('.wip-limit-value')
  182. .val(),
  183. 10,
  184. );
  185. if (limit < list.cards().length && !list.getWipLimit('soft')) {
  186. Template.instance()
  187. .$('.wip-limit-error')
  188. .click();
  189. } else {
  190. Meteor.call('applyWipLimit', list._id, limit);
  191. Popup.back();
  192. }
  193. },
  194. enableSoftLimit() {
  195. const list = Template.currentData();
  196. if (
  197. list.getWipLimit('soft') &&
  198. list.getWipLimit('value') < list.cards().length
  199. ) {
  200. list.setWipLimit(list.cards().length);
  201. }
  202. Meteor.call('enableSoftLimit', Template.currentData()._id);
  203. },
  204. enableWipLimit() {
  205. const list = Template.currentData();
  206. // Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list
  207. if (
  208. !list.getWipLimit('enabled') &&
  209. list.getWipLimit('value') < list.cards().length
  210. ) {
  211. list.setWipLimit(list.cards().length);
  212. }
  213. Meteor.call('enableWipLimit', list._id);
  214. },
  215. isWipLimitSoft() {
  216. return Template.currentData().getWipLimit('soft');
  217. },
  218. isWipLimitEnabled() {
  219. return Template.currentData().getWipLimit('enabled');
  220. },
  221. wipLimitValue() {
  222. return Template.currentData().getWipLimit('value');
  223. },
  224. events() {
  225. return [
  226. {
  227. 'click .js-enable-wip-limit': this.enableWipLimit,
  228. 'click .wip-limit-apply': this.applyWipLimit,
  229. 'click .wip-limit-error': Popup.open('wipLimitError'),
  230. 'click .materialCheckBox': this.enableSoftLimit,
  231. },
  232. ];
  233. },
  234. }).register('setWipLimitPopup');
  235. Template.listMorePopup.events({
  236. 'click .js-delete': Popup.afterConfirm('listDelete', function() {
  237. Popup.back();
  238. const allCards = this.allCards();
  239. const allCardIds = _.pluck(allCards, '_id');
  240. // it's okay if the linked cards are on the same list
  241. if (
  242. ReactiveCache.getCards({
  243. $and: [
  244. { listId: { $ne: this._id } },
  245. { linkedId: { $in: allCardIds } },
  246. ],
  247. }).length === 0
  248. ) {
  249. allCardIds.map(_id => Cards.remove(_id));
  250. Lists.remove(this._id);
  251. } else {
  252. // TODO: Figure out more informative message.
  253. // Popup with a hint that the list cannot be deleted as there are
  254. // linked cards. We can adapt the query above so we can list the linked
  255. // cards.
  256. // Related:
  257. // client/components/cards/cardDetails.js about line 969
  258. // https://github.com/wekan/wekan/issues/2785
  259. const message = `${TAPi18n.__(
  260. 'delete-linked-cards-before-this-list',
  261. )} linkedId: ${
  262. this._id
  263. } at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`;
  264. alert(message);
  265. }
  266. Utils.goBoardId(this.boardId);
  267. }),
  268. });
  269. Template.listHeader.helpers({
  270. isBoardAdmin() {
  271. return ReactiveCache.getCurrentUser().isBoardAdmin();
  272. },
  273. });
  274. BlazeComponent.extendComponent({
  275. onCreated() {
  276. this.currentList = this.currentData();
  277. this.currentColor = new ReactiveVar(this.currentList.color);
  278. },
  279. colors() {
  280. return listsColors.map(color => ({ color, name: '' }));
  281. },
  282. isSelected(color) {
  283. if (this.currentColor.get() === null) {
  284. return color === 'white';
  285. } else {
  286. return this.currentColor.get() === color;
  287. }
  288. },
  289. events() {
  290. return [
  291. {
  292. 'click .js-palette-color'() {
  293. this.currentColor.set(this.currentData().color);
  294. },
  295. 'click .js-submit'() {
  296. this.currentList.setColor(this.currentColor.get());
  297. Popup.close();
  298. },
  299. 'click .js-remove-color'() {
  300. this.currentList.setColor(null);
  301. Popup.close();
  302. },
  303. },
  304. ];
  305. },
  306. }).register('setListColorPopup');
  307. BlazeComponent.extendComponent({
  308. applyListWidth() {
  309. const list = Template.currentData();
  310. const board = list.boardId;
  311. const width = parseInt(
  312. Template.instance()
  313. .$('.list-width-value')
  314. .val(),
  315. 10,
  316. );
  317. // FIXME(mark-i-m): where do we put constants?
  318. if (width < 100 || !width) {
  319. Template.instance()
  320. .$('.list-width-error')
  321. .click();
  322. } else {
  323. Meteor.call('applyListWidth', board, list._id, width);
  324. Popup.back();
  325. }
  326. },
  327. listWidthValue() {
  328. const list = Template.currentData();
  329. const board = list.boardId;
  330. return Meteor.user().getListWidth(board, list._id);
  331. },
  332. events() {
  333. return [
  334. {
  335. 'click .list-width-apply': this.applyListWidth,
  336. 'click .list-width-error': Popup.open('listWidthError'),
  337. },
  338. ];
  339. },
  340. }).register('setListWidthPopup');