import.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /// Abstract root for all import popup screens.
  2. /// Descendants must define:
  3. /// - getMethodName(): return the Meteor method to call for import, passing json
  4. /// data decoded as object and additional data (see below);
  5. /// - getAdditionalData(): return object containing additional data passed to
  6. /// Meteor method (like list ID and position for a card import);
  7. /// - getLabel(): i18n key for the text displayed in the popup, usually to
  8. /// explain how to get the data out of the source system.
  9. const ImportPopup = BlazeComponent.extendComponent({
  10. template() {
  11. return 'importPopup';
  12. },
  13. jsonText() {
  14. return Session.get('import.text');
  15. },
  16. membersMapping() {
  17. return Session.get('import.membersToMap');
  18. },
  19. onCreated() {
  20. this.error = new ReactiveVar('');
  21. this.dataToImport = '';
  22. },
  23. onFinish() {
  24. Popup.close();
  25. },
  26. onShowMapping(evt) {
  27. this._storeText(evt);
  28. Popup.open('mapMembers')(evt);
  29. },
  30. onSubmit(evt){
  31. evt.preventDefault();
  32. const dataJson = this._storeText(evt);
  33. let dataObject;
  34. try {
  35. dataObject = JSON.parse(dataJson);
  36. this.setError('');
  37. } catch (e) {
  38. this.setError('error-json-malformed');
  39. return;
  40. }
  41. if(this._hasAllNeededData(dataObject)) {
  42. this._import(dataObject);
  43. } else {
  44. this._prepareAdditionalData(dataObject);
  45. Popup.open(this._screenAdditionalData())(evt);
  46. }
  47. },
  48. events() {
  49. return [{
  50. submit: this.onSubmit,
  51. 'click .show-mapping': this.onShowMapping,
  52. }];
  53. },
  54. setError(error) {
  55. this.error.set(error);
  56. },
  57. _import(dataObject) {
  58. const additionalData = this.getAdditionalData();
  59. const membersMapping = this.membersMapping();
  60. if (membersMapping) {
  61. const mappingById = {};
  62. membersMapping.forEach((member) => {
  63. if (member.wekan) {
  64. mappingById[member.id] = member.wekan._id;
  65. }
  66. });
  67. additionalData.membersMapping = mappingById;
  68. }
  69. Session.set('import.membersToMap', null);
  70. Session.set('import.text', null);
  71. Meteor.call(this.getMethodName(), dataObject, additionalData,
  72. (error, response) => {
  73. if (error) {
  74. this.setError(error.error);
  75. } else {
  76. // ensure will display what we just imported
  77. Filter.addException(response);
  78. this.onFinish(response);
  79. }
  80. }
  81. );
  82. },
  83. _hasAllNeededData(dataObject) {
  84. // import has no members or they are already mapped
  85. return dataObject.members.length === 0 || this.membersMapping();
  86. },
  87. _prepareAdditionalData(dataObject) {
  88. // we will work on the list itself (an ordered array of objects)
  89. // when a mapping is done, we add a 'wekan' field to the object representing the imported member
  90. const membersToMap = dataObject.members;
  91. // auto-map based on username
  92. membersToMap.forEach((importedMember) => {
  93. const wekanUser = Users.findOne({username: importedMember.username});
  94. if(wekanUser) {
  95. importedMember.wekan = wekanUser;
  96. }
  97. });
  98. // store members data and mapping in Session
  99. // (we go deep and 2-way, so storing in data context is not a viable option)
  100. Session.set('import.membersToMap', membersToMap);
  101. return membersToMap;
  102. },
  103. _screenAdditionalData() {
  104. return 'mapMembers';
  105. },
  106. _storeText() {
  107. const dataJson = this.$('.js-import-json').val();
  108. Session.set('import.text', dataJson);
  109. return dataJson;
  110. },
  111. });
  112. ImportPopup.extendComponent({
  113. getAdditionalData() {
  114. const listId = this.currentData()._id;
  115. const selector = `#js-list-${this.currentData()._id} .js-minicard:first`;
  116. const firstCardDom = $(selector).get(0);
  117. const sortIndex = Utils.calculateIndex(null, firstCardDom).base;
  118. const result = {listId, sortIndex};
  119. return result;
  120. },
  121. getMethodName() {
  122. return 'importTrelloCard';
  123. },
  124. getLabel() {
  125. return 'import-card-trello-instruction';
  126. },
  127. }).register('listImportCardPopup');
  128. ImportPopup.extendComponent({
  129. getAdditionalData() {
  130. const result = {};
  131. return result;
  132. },
  133. getMethodName() {
  134. return 'importTrelloBoard';
  135. },
  136. getLabel() {
  137. return 'import-board-trello-instruction';
  138. },
  139. onFinish(response) {
  140. Utils.goBoardId(response);
  141. },
  142. }).register('boardImportBoardPopup');
  143. const ImportMapMembers = BlazeComponent.extendComponent({
  144. members() {
  145. return Session.get('import.membersToMap');
  146. },
  147. _refreshMembers(listOfMembers) {
  148. Session.set('import.membersToMap', listOfMembers);
  149. },
  150. /**
  151. * Will look into the list of members to import for the specified memberId,
  152. * then set its property to the supplied value.
  153. * If unset is true, it will remove the property from the rest of the list as well.
  154. *
  155. * use:
  156. * - memberId = null to use selected member
  157. * - value = null to unset a property
  158. * - unset = true to ensure property is only set on 1 member at a time
  159. */
  160. _setPropertyForMember(property, value, memberId, unset = false) {
  161. const listOfMembers = this.members();
  162. let finder = null;
  163. if(memberId) {
  164. finder = (member) => member.id === memberId;
  165. } else {
  166. finder = (member) => member.selected;
  167. }
  168. listOfMembers.forEach((member) => {
  169. if(finder(member)) {
  170. if(value !== null) {
  171. member[property] = value;
  172. } else {
  173. delete member[property];
  174. }
  175. if(!unset) {
  176. // we shortcut if we don't care about unsetting the others
  177. return false;
  178. }
  179. } else if(unset) {
  180. delete member[property];
  181. }
  182. return true;
  183. });
  184. // Session.get gives us a copy, we have to set it back so it sticks
  185. this._refreshMembers(listOfMembers);
  186. },
  187. setSelectedMember(memberId) {
  188. return this._setPropertyForMember('selected', true, memberId, true);
  189. },
  190. /**
  191. * returns the member with specified id,
  192. * or the selected member if memberId is not specified
  193. */
  194. getMember(memberId = null) {
  195. const allMembers = Session.get('import.membersToMap');
  196. let finder = null;
  197. if(memberId) {
  198. finder = (user) => user.id === memberId;
  199. } else {
  200. finder = (user) => user.selected;
  201. }
  202. return allMembers.find(finder);
  203. },
  204. mapSelectedMember(wekan) {
  205. return this._setPropertyForMember('wekan', wekan, null);
  206. },
  207. unmapMember(memberId){
  208. return this._setPropertyForMember('wekan', null, memberId);
  209. },
  210. });
  211. ImportMapMembers.extendComponent({
  212. onMapMember(evt) {
  213. const memberToMap = this.currentData();
  214. if(memberToMap.wekan) {
  215. // todo xxx ask for confirmation?
  216. this.unmapMember(memberToMap.id);
  217. } else {
  218. this.setSelectedMember(memberToMap.id);
  219. Popup.open('mapMembersAdd')(evt);
  220. }
  221. },
  222. onSubmit(evt) {
  223. evt.preventDefault();
  224. Popup.back();
  225. },
  226. events() {
  227. return [{
  228. 'submit': this.onSubmit,
  229. 'click .mapping': this.onMapMember,
  230. }];
  231. },
  232. }).register('mapMembersPopup');
  233. ImportMapMembers.extendComponent({
  234. onSelectUser(){
  235. this.mapSelectedMember(this.currentData());
  236. Popup.back();
  237. },
  238. events() {
  239. return [{
  240. 'click .js-select-import': this.onSelectUser,
  241. }];
  242. },
  243. onRendered() {
  244. // todo XXX why do I not get the focus??
  245. this.find('.js-map-member input').focus();
  246. },
  247. }).register('mapMembersAddPopup');