TrelloClient.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. TrelloClient = function(editorUi)
  6. {
  7. DrawioClient.call(this, editorUi, 'tauth');
  8. Trello.setKey(this.key);
  9. };
  10. // Extends DrawioClient
  11. mxUtils.extend(TrelloClient, DrawioClient);
  12. TrelloClient.prototype.key = (window.location.hostname == 'test.draw.io') ?
  13. 'e73615c79cf7e381aef91c85936e9553' : 'e73615c79cf7e381aef91c85936e9553';
  14. TrelloClient.prototype.baseUrl = 'https://api.trello.com/1/';
  15. TrelloClient.prototype.SEPARATOR = '|$|';
  16. /**
  17. * Default extension for new files.
  18. */
  19. TrelloClient.prototype.extension = '.xml'; //TODO export to png
  20. /**
  21. *
  22. */
  23. TrelloClient.prototype.getLibrary = function(id, success, error)
  24. {
  25. this.getFile(id, success, error, false, true);
  26. };
  27. /**
  28. *
  29. */
  30. TrelloClient.prototype.getFile = function(id, success, error, denyConvert, asLibrary)
  31. {
  32. asLibrary = (asLibrary != null) ? asLibrary : false;
  33. Trello.authorize({
  34. type: 'popup',
  35. name: 'Draw.io',
  36. scope: {
  37. read: 'true',
  38. write: 'true' },
  39. expiration: 'never',
  40. success: mxUtils.bind(this, function() {
  41. var ids = id.split(this.SEPARATOR);
  42. Trello.cards.get(ids[0] + '/attachments/' + ids[1], mxUtils.bind(this, function(meta)
  43. {
  44. //TODO Trello doesn't allow CORS requests to load attachments. Confirm that and make sure that only a proxy technique can work!
  45. // Handles .vsdx, Gliffy and PNG+XML files by creating a temporary file
  46. if ((/\.vsdx$/i.test(meta.name) || /\.gliffy$/i.test(meta.name) || /\.png$/i.test(meta.name)))
  47. {
  48. this.ui.convertFile(PROXY_URL + '?url=' + encodeURIComponent(meta.url), meta.name, meta.mimeType,
  49. this.extension, success, error);
  50. }
  51. else
  52. {
  53. var acceptResponse = true;
  54. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  55. {
  56. acceptResponse = false;
  57. error({code: App.ERROR_TIMEOUT})
  58. }), this.ui.timeout);
  59. this.ui.loadUrl(PROXY_URL + '?url=' + encodeURIComponent(meta.url), mxUtils.bind(this, function(data)
  60. {
  61. window.clearTimeout(timeoutThread);
  62. if (acceptResponse)
  63. {
  64. //keep our id which includes the cardId
  65. meta.compoundId = id;
  66. if (asLibrary)
  67. {
  68. success(new TrelloLibrary(this.ui, data, meta));
  69. }
  70. else
  71. {
  72. success(new TrelloFile(this.ui, data, meta));
  73. }
  74. }
  75. }), mxUtils.bind(this, function(req)
  76. {
  77. window.clearTimeout(timeoutThread);
  78. if (acceptResponse)
  79. {
  80. error();
  81. }
  82. }), meta.mimeType != null &&
  83. meta.mimeType.substring(0, 6) == 'image/');
  84. }
  85. }));
  86. }),
  87. error: error
  88. });
  89. };
  90. /**
  91. *
  92. */
  93. TrelloClient.prototype.insertLibrary = function(filename, data, success, error, cardId)
  94. {
  95. this.insertFile(filename, data, success, error, true, cardId);
  96. };
  97. /**
  98. *
  99. */
  100. TrelloClient.prototype.insertFile = function(filename, data, success, error, asLibrary, cardId)
  101. {
  102. asLibrary = (asLibrary != null) ? asLibrary : false;
  103. this.writeFile(filename, data, cardId, mxUtils.bind(this, function(meta)
  104. {
  105. if (asLibrary)
  106. {
  107. success(new TrelloLibrary(this.ui, data, meta));
  108. }
  109. else
  110. {
  111. success(new TrelloFile(this.ui, data, meta));
  112. }
  113. }), error);
  114. };
  115. /**
  116. *
  117. */
  118. TrelloClient.prototype.saveFile = function(file, success, error)
  119. {
  120. //delete file first, then write it again
  121. var ids = file.meta.compoundId.split(this.SEPARATOR);
  122. Trello.authorize({
  123. type: 'popup',
  124. name: 'Draw.io',
  125. scope: {
  126. read: 'true',
  127. write: 'true'
  128. },
  129. expiration: 'never',
  130. success: mxUtils.bind(this, function()
  131. {
  132. Trello.del('cards/' + ids[0] + '/attachments/' + ids[1], mxUtils.bind(this, function()
  133. {
  134. this.writeFile(file.meta.name, file.getData(), ids[0], success, error);
  135. }), error);
  136. }),
  137. error: error
  138. });
  139. };
  140. /**
  141. *
  142. */
  143. TrelloClient.prototype.writeFile = function(filename, data, cardId, success, error)
  144. {
  145. if (filename != null && data != null)
  146. {
  147. Trello.authorize({
  148. type: 'popup',
  149. name: 'Draw.io',
  150. scope: {
  151. read: 'true',
  152. write: 'true'
  153. },
  154. expiration: 'never',
  155. success: mxUtils.bind(this, function()
  156. {
  157. var acceptResponse = true;
  158. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  159. {
  160. acceptResponse = false;
  161. error({code: App.ERROR_TIMEOUT, retry: fn});
  162. }), this.ui.timeout);
  163. var formData = new FormData();
  164. formData.append("key", Trello.key());
  165. formData.append("token", Trello.token());
  166. formData.append("file", data);
  167. formData.append("name", filename);
  168. var request = new XMLHttpRequest();
  169. request.responseType = "json";
  170. request.onreadystatechange = mxUtils.bind(this, function()
  171. {
  172. if (request.readyState === 4)
  173. {
  174. window.clearTimeout(timeoutThread);
  175. if (acceptResponse)
  176. {
  177. if (request.status == 200)
  178. {
  179. var fileMeta = request.response;
  180. fileMeta.compoundId = cardId + this.SEPARATOR + fileMeta.id
  181. success(fileMeta);
  182. }
  183. else
  184. {
  185. error();
  186. }
  187. }
  188. }
  189. });
  190. request.open("POST", this.baseUrl + 'cards/' + cardId + '/attachments');
  191. request.send(formData);
  192. }),
  193. error: error
  194. });
  195. }
  196. else
  197. {
  198. error({message: mxResources.get('unknownError')});
  199. }
  200. };
  201. /**
  202. * Checks if the client is authorized and calls the next step.
  203. */
  204. TrelloClient.prototype.pickLibrary = function(fn)
  205. {
  206. this.pickFile(fn);
  207. };
  208. /**
  209. *
  210. */
  211. TrelloClient.prototype.pickFolder = function(fn)
  212. {
  213. Trello.authorize({
  214. type: 'popup',
  215. name: 'Draw.io',
  216. scope: {
  217. read: 'true',
  218. write: 'true' },
  219. expiration: 'never',
  220. success: mxUtils.bind(this, function() {
  221. //show file select
  222. this.showTrelloDialog(false, fn);
  223. }),
  224. error: mxUtils.bind(this, function(e)
  225. {
  226. this.ui.showError(mxResources.get('error'), e);
  227. })
  228. });
  229. };
  230. /**
  231. * Checks if the client is authorized and calls the next step.
  232. */
  233. TrelloClient.prototype.pickFile = function(fn, returnObject)
  234. {
  235. fn = (fn != null) ? fn : mxUtils.bind(this, function(id)
  236. {
  237. this.ui.loadFile('T' + encodeURIComponent(id));
  238. });
  239. Trello.authorize({
  240. type: 'popup',
  241. name: 'Draw.io',
  242. scope: {
  243. read: 'true',
  244. write: 'true' },
  245. expiration: 'never',
  246. success: mxUtils.bind(this, function() {
  247. //show file select
  248. this.showTrelloDialog(true, fn);
  249. }),
  250. error: mxUtils.bind(this, function(e)
  251. {
  252. this.ui.showError(mxResources.get('error'), e);
  253. })
  254. });
  255. };
  256. /**
  257. *
  258. */
  259. TrelloClient.prototype.showTrelloDialog = function(showFiles, fn)
  260. {
  261. var cardId = null;
  262. var filter = null;
  263. var linkCounter = 0;
  264. var content = document.createElement('div');
  265. content.style.whiteSpace = 'nowrap';
  266. content.style.overflow = 'hidden';
  267. content.style.height = '224px';
  268. var hd = document.createElement('h3');
  269. mxUtils.write(hd, showFiles? mxResources.get('selectFile') : mxResources.get('selectCard'));
  270. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  271. content.appendChild(hd);
  272. var div = document.createElement('div');
  273. div.style.whiteSpace = 'nowrap';
  274. div.style.overflow = 'auto';
  275. div.style.height = '194px';
  276. content.appendChild(div);
  277. var dlg = new CustomDialog(this.ui, content);
  278. this.ui.showDialog(dlg.container, 340, 270, true, true);
  279. dlg.okButton.parentNode.removeChild(dlg.okButton);
  280. var createLink = mxUtils.bind(this, function(label, fn, preview)
  281. {
  282. linkCounter++;
  283. var div = document.createElement('div');
  284. div.style = "width:100%;vertical-align:middle;background:" + (linkCounter % 2 == 0? "#eee" : "#fff");
  285. var link = document.createElement('a');
  286. link.setAttribute('href', 'javascript:void(0);');
  287. if (preview != null)
  288. {
  289. var img = document.createElement('img');
  290. img.src = preview.url;
  291. img.width = preview.width;
  292. img.height= preview.height;
  293. img.style= "border: 1px solid black;margin:5px;vertical-align:middle"
  294. link.appendChild(img);
  295. }
  296. mxUtils.write(link, label);
  297. mxEvent.addListener(link, 'click', fn);
  298. div.appendChild(link);
  299. return div;
  300. });
  301. var error = mxUtils.bind(this, function(err)
  302. {
  303. this.ui.handleError(err, null, mxUtils.bind(this, function()
  304. {
  305. this.ui.spinner.stop();
  306. this.ui.hideDialog();
  307. }));
  308. });
  309. var selectAtt = mxUtils.bind(this, function()
  310. {
  311. linkCounter = 0;
  312. div.innerHTML = '';
  313. this.ui.spinner.spin(div, mxResources.get('loading'));
  314. Trello.cards.get(cardId + '/attachments', {fields: 'id,name,previews'}, mxUtils.bind(this, function(data)
  315. {
  316. this.ui.spinner.stop();
  317. var files = data;
  318. div.appendChild(createLink('../ [Up]', mxUtils.bind(this, function()
  319. {
  320. selectCard();
  321. })));
  322. mxUtils.br(div);
  323. if (files == null || files.length == 0)
  324. {
  325. mxUtils.write(div, mxResources.get('noFiles'));
  326. }
  327. else
  328. {
  329. var listFiles = mxUtils.bind(this, function()
  330. {
  331. for (var i = 0; i < files.length; i++)
  332. {
  333. (mxUtils.bind(this, function(file)
  334. {
  335. div.appendChild(createLink(file.name, mxUtils.bind(this, function()
  336. {
  337. this.ui.hideDialog();
  338. fn(cardId + this.SEPARATOR + file.id);
  339. }), file.previews != null? file.previews[0] : null));
  340. }))(files[i]);
  341. }
  342. });
  343. listFiles();
  344. }
  345. }), error);
  346. });
  347. // Adds paging for cards (files limited to 1000 by API)
  348. var pageSize = 100;
  349. var nextPageDiv = null;
  350. var scrollFn = null;
  351. var selectCard = mxUtils.bind(this, function(page)
  352. {
  353. if (page == null)
  354. {
  355. linkCounter = 0;
  356. div.innerHTML = '';
  357. page = 1;
  358. }
  359. this.ui.spinner.spin(div, mxResources.get('loading'));
  360. if (nextPageDiv != null && nextPageDiv.parentNode != null)
  361. {
  362. nextPageDiv.parentNode.removeChild(nextPageDiv);
  363. }
  364. nextPageDiv = document.createElement('a');
  365. nextPageDiv.style.display = 'block';
  366. nextPageDiv.setAttribute('href', 'javascript:void(0);');
  367. mxUtils.write(nextPageDiv, mxResources.get('more') + '...');
  368. var nextPage = mxUtils.bind(this, function()
  369. {
  370. mxEvent.removeListener(div, 'scroll', scrollFn);
  371. selectCard(page + 1);
  372. });
  373. mxEvent.addListener(nextPageDiv, 'click', nextPage);
  374. Trello.get('search', {
  375. 'query': '@me' + (filter != null ? ' ' + filter : ''),
  376. 'cards_limit': pageSize,
  377. 'cards_page': page-1
  378. },
  379. mxUtils.bind(this, function(data)
  380. {
  381. this.ui.spinner.stop();
  382. var cards = data.cards;
  383. if (cards == null || cards.length == 0)
  384. {
  385. if (filter != null)
  386. {
  387. div.appendChild(createLink(mxResources.get('clearFilter'), mxUtils.bind(this, function()
  388. {
  389. filter = null;
  390. selectCard();
  391. })));
  392. mxUtils.br(div);
  393. }
  394. mxUtils.write(div, mxResources.get('noFiles'));
  395. }
  396. else
  397. {
  398. if (page == 1)
  399. {
  400. if (filter != null)
  401. {
  402. div.appendChild(createLink(mxResources.get('clearFilter'), mxUtils.bind(this, function()
  403. {
  404. filter = null;
  405. selectCard();
  406. })));
  407. }
  408. else
  409. {
  410. div.appendChild(createLink(mxResources.get('filterCards') + '...', mxUtils.bind(this, function()
  411. {
  412. var dlg = new FilenameDialog(this.ui, mxResources.get('cardName'), mxResources.get('ok'), mxUtils.bind(this, function(value)
  413. {
  414. if (value != null)
  415. {
  416. filter = value;
  417. selectCard();
  418. }
  419. }), mxResources.get('cardName'));
  420. this.ui.showDialog(dlg.container, 300, 80, true, false);
  421. dlg.init();
  422. })));
  423. }
  424. mxUtils.br(div);
  425. }
  426. for (var i = 0; i < cards.length; i++)
  427. {
  428. (mxUtils.bind(this, function(card)
  429. {
  430. div.appendChild(createLink(card.name, mxUtils.bind(this, function()
  431. {
  432. if (showFiles)
  433. {
  434. cardId = card.id;
  435. selectAtt();
  436. }
  437. else
  438. {
  439. this.ui.hideDialog();
  440. fn(card.id);
  441. }
  442. })));
  443. }))(cards[i]);
  444. }
  445. }
  446. if (cards.length == pageSize)
  447. {
  448. div.appendChild(nextPageDiv);
  449. scrollFn = function()
  450. {
  451. if (div.scrollTop >= div.scrollHeight - div.offsetHeight)
  452. {
  453. nextPage();
  454. }
  455. };
  456. mxEvent.addListener(div, 'scroll', scrollFn);
  457. }
  458. }),
  459. error);
  460. });
  461. selectCard();
  462. };
  463. /**
  464. * Checks if the client is authorized
  465. */
  466. TrelloClient.prototype.isAuthorized = function()
  467. {
  468. //TODO this may break if Trello client.js is changed
  469. return localStorage["trello_token"] != null; //Trello.authorized(); doesn't work unless authorize is called first
  470. };
  471. /**
  472. * Logout and deauthorize the user.
  473. */
  474. TrelloClient.prototype.logout = function()
  475. {
  476. Trello.deauthorize();
  477. };