TrelloClient.js 14 KB

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