DiffSync.js 23 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. /**
  6. * Removes all labels, user objects and styles from the given node in-place.
  7. */
  8. EditorUi.DIFF_INSERT = 'i';
  9. /**
  10. * Removes all labels, user objects and styles from the given node in-place.
  11. */
  12. EditorUi.DIFF_REMOVE = 'r';
  13. /**
  14. * Removes all labels, user objects and styles from the given node in-place.
  15. */
  16. EditorUi.DIFF_UPDATE = 'u';
  17. /**
  18. * Shared codec.
  19. */
  20. EditorUi.prototype.codec = new mxCodec();
  21. /**
  22. * Contains all view state properties that should not be ignored in diff sync.
  23. */
  24. EditorUi.prototype.viewStateProperties = {background: true, backgroundImage: true, shadowVisible: true,
  25. foldingEnabled: true, pageScale: true, mathEnabled: true, pageFormat: true};
  26. /**
  27. * Contains all known cell properties that should be ignored in generic diff sync.
  28. */
  29. EditorUi.prototype.cellPrototype = new mxCell();
  30. /**
  31. * Removes all labels, user objects and styles from the given node in-place.
  32. */
  33. EditorUi.prototype.patchPages = function(pages, diff, markPages, resolver, updateEdgeParents)
  34. {
  35. var resolverLookup = {};
  36. var newPages = [];
  37. var inserted = {};
  38. var removed = {};
  39. var lookup = {};
  40. var moved = {};
  41. if (resolver != null && resolver[EditorUi.DIFF_UPDATE] != null)
  42. {
  43. for (var id in resolver[EditorUi.DIFF_UPDATE])
  44. {
  45. resolverLookup[id] = resolver[EditorUi.DIFF_UPDATE][id];
  46. }
  47. }
  48. if (diff[EditorUi.DIFF_REMOVE] != null)
  49. {
  50. for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++)
  51. {
  52. removed[diff[EditorUi.DIFF_REMOVE][i]] = true;
  53. }
  54. }
  55. if (diff[EditorUi.DIFF_INSERT] != null)
  56. {
  57. for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++)
  58. {
  59. inserted[diff[EditorUi.DIFF_INSERT][i].previous] = diff[EditorUi.DIFF_INSERT][i];
  60. }
  61. }
  62. if (diff[EditorUi.DIFF_UPDATE] != null)
  63. {
  64. for (var id in diff[EditorUi.DIFF_UPDATE])
  65. {
  66. var pageDiff = diff[EditorUi.DIFF_UPDATE][id];
  67. if (pageDiff.previous != null)
  68. {
  69. moved[pageDiff.previous] = id;
  70. }
  71. }
  72. }
  73. // Restores existing order and creates lookup
  74. if (pages != null)
  75. {
  76. var prev = '';
  77. for (var i = 0; i < pages.length; i++)
  78. {
  79. var pageId = pages[i].getId();
  80. lookup[pageId] = pages[i];
  81. if (moved[prev] == null && !removed[pageId] &&
  82. (diff[EditorUi.DIFF_UPDATE] == null ||
  83. diff[EditorUi.DIFF_UPDATE][pageId] == null ||
  84. diff[EditorUi.DIFF_UPDATE][pageId].previous == null))
  85. {
  86. moved[prev] = pageId;
  87. }
  88. prev = pageId;
  89. }
  90. }
  91. // FIXME: Workaround for possible duplicate pages
  92. var added = {};
  93. var addPage = mxUtils.bind(this, function(page)
  94. {
  95. var id = (page != null) ? page.getId() : '';
  96. if (page != null && !added[id])
  97. {
  98. added[id] = true;
  99. newPages.push(page);
  100. var pageDiff = (diff[EditorUi.DIFF_UPDATE] != null) ?
  101. diff[EditorUi.DIFF_UPDATE][id] : null;
  102. if (pageDiff != null)
  103. {
  104. this.updatePageRoot(page);
  105. if (pageDiff.name != null)
  106. {
  107. page.setName(pageDiff.name);
  108. }
  109. if (pageDiff.view != null)
  110. {
  111. this.patchViewState(page, pageDiff.view);
  112. }
  113. if (pageDiff.cells != null)
  114. {
  115. this.patchPage(page, pageDiff.cells,
  116. resolverLookup[page.getId()],
  117. updateEdgeParents);
  118. }
  119. if (markPages && (pageDiff.cells != null ||
  120. pageDiff.view != null))
  121. {
  122. page.needsUpdate = true;
  123. }
  124. }
  125. }
  126. var mov = moved[id];
  127. if (mov != null)
  128. {
  129. delete moved[id];
  130. addPage(lookup[mov]);
  131. }
  132. var ins = inserted[id];
  133. if (ins != null)
  134. {
  135. delete inserted[id];
  136. insertPage(ins);
  137. }
  138. });
  139. var insertPage = mxUtils.bind(this, function(ins)
  140. {
  141. var diagram = mxUtils.parseXml(ins.data).documentElement;
  142. var newPage = new DiagramPage(diagram);
  143. this.updatePageRoot(newPage);
  144. var page = lookup[newPage.getId()];
  145. if (page == null)
  146. {
  147. addPage(newPage);
  148. }
  149. else
  150. {
  151. // Updates root if page already in UI
  152. page.root = newPage.root;
  153. if (this.currentPage == page)
  154. {
  155. this.editor.graph.model.setRoot(page.root);
  156. }
  157. else if (markPages)
  158. {
  159. page.needsUpdate = true;
  160. }
  161. }
  162. });
  163. addPage();
  164. // Handles orphaned moved pages
  165. for (var id in moved)
  166. {
  167. addPage(lookup[moved[id]]);
  168. delete moved[id];
  169. }
  170. // Handles orphaned inserted pages
  171. for (var id in inserted)
  172. {
  173. insertPage(inserted[id]);
  174. delete inserted[id];
  175. }
  176. return newPages;
  177. };
  178. /**
  179. * Removes all labels, user objects and styles from the given node in-place.
  180. */
  181. EditorUi.prototype.patchViewState = function(page, diff)
  182. {
  183. if (page.viewState != null && diff != null)
  184. {
  185. if (page == this.currentPage)
  186. {
  187. page.viewState = this.editor.graph.getViewState();
  188. }
  189. for (var key in diff)
  190. {
  191. page.viewState[key] = JSON.parse(diff[key]);
  192. }
  193. if (page == this.currentPage)
  194. {
  195. this.editor.graph.setViewState(page.viewState);
  196. }
  197. }
  198. };
  199. /**
  200. * Removes all labels, user objects and styles from the given node in-place.
  201. */
  202. EditorUi.prototype.createParentLookup = function(model, diff)
  203. {
  204. var parentLookup = {};
  205. function getLookup(id)
  206. {
  207. var result = parentLookup[id];
  208. if (result == null)
  209. {
  210. result = {inserted: [], moved: {}};
  211. parentLookup[id] = result;
  212. }
  213. return result;
  214. };
  215. if (diff[EditorUi.DIFF_INSERT] != null)
  216. {
  217. for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++)
  218. {
  219. var temp = diff[EditorUi.DIFF_INSERT][i];
  220. var par = (temp.parent != null) ? temp.parent : '';
  221. var prev = (temp.previous != null) ? temp.previous : '';
  222. getLookup(par).inserted[prev] = temp;
  223. }
  224. }
  225. if (diff[EditorUi.DIFF_UPDATE] != null)
  226. {
  227. for (var id in diff[EditorUi.DIFF_UPDATE])
  228. {
  229. var temp = diff[EditorUi.DIFF_UPDATE][id];
  230. if (temp.previous != null)
  231. {
  232. var par = temp.parent;
  233. if (par == null)
  234. {
  235. var cell = model.getCell(id);
  236. if (cell != null)
  237. {
  238. var parent = model.getParent(cell);
  239. if (parent != null)
  240. {
  241. par = parent.getId();
  242. }
  243. }
  244. }
  245. if (par != null)
  246. {
  247. getLookup(par).moved[temp.previous] = id;
  248. }
  249. }
  250. }
  251. }
  252. return parentLookup;
  253. };
  254. /**
  255. * Removes all labels, user objects and styles from the given node in-place.
  256. */
  257. EditorUi.prototype.patchPage = function(page, diff, resolver, updateEdgeParents)
  258. {
  259. var model = (page == this.currentPage) ? this.editor.graph.model : new mxGraphModel(page.root);
  260. var parentLookup = this.createParentLookup(model, diff);
  261. model.beginUpdate();
  262. try
  263. {
  264. // Disables or delays update of edge parents to after patch
  265. var prev = model.updateEdgeParent;
  266. var dict = new mxDictionary();
  267. var pendingUpdates = [];
  268. model.updateEdgeParent = function(edge, root)
  269. {
  270. if (!dict.get(edge) && updateEdgeParents)
  271. {
  272. dict.put(edge, true);
  273. pendingUpdates.push(edge);
  274. }
  275. };
  276. // Handles new root cells
  277. var temp = parentLookup[''];
  278. var cellDiff = (temp != null && temp.inserted != null) ? temp.inserted[''] : null;
  279. var root = null;
  280. if (cellDiff != null)
  281. {
  282. root = this.getCellForJson(cellDiff);
  283. }
  284. // Handles cells becoming root (very unlikely but possible)
  285. if (root == null)
  286. {
  287. var id = (temp != null && temp.moved != null) ? temp.moved[''] : null;
  288. if (id != null)
  289. {
  290. root = model.getCell(id);
  291. }
  292. }
  293. if (root != null)
  294. {
  295. model.setRoot(root);
  296. page.root = root;
  297. }
  298. // Removes cells
  299. if (diff[EditorUi.DIFF_REMOVE] != null)
  300. {
  301. for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++)
  302. {
  303. var cell = model.getCell(diff[EditorUi.DIFF_REMOVE][i]);
  304. if (cell != null)
  305. {
  306. model.remove(cell);
  307. }
  308. }
  309. }
  310. // Patches cell structure
  311. this.patchCellRecursive(page, model, model.root, parentLookup, diff);
  312. // Applies patches and changes terminals after all cells are inserted
  313. if (diff[EditorUi.DIFF_UPDATE] != null)
  314. {
  315. var res = (resolver != null && resolver.cells != null) ?
  316. resolver.cells[EditorUi.DIFF_UPDATE] : null;
  317. for (var id in diff[EditorUi.DIFF_UPDATE])
  318. {
  319. this.patchCell(model, model.getCell(id),
  320. diff[EditorUi.DIFF_UPDATE][id],
  321. (res != null) ? res[id] : null);
  322. }
  323. }
  324. // Sets terminals for inserted cells after all cells are inserted
  325. if (diff[EditorUi.DIFF_INSERT] != null)
  326. {
  327. for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++)
  328. {
  329. var cellDiff = diff[EditorUi.DIFF_INSERT][i];
  330. var cell = model.getCell(cellDiff.id);
  331. if (cell != null)
  332. {
  333. model.setTerminal(cell, model.getCell(cellDiff.source), true);
  334. model.setTerminal(cell, model.getCell(cellDiff.target), false);
  335. }
  336. }
  337. }
  338. // Updates edge parents after all patches have been applied
  339. model.updateEdgeParent = prev;
  340. if (updateEdgeParents && pendingUpdates.length > 0)
  341. {
  342. for (var i = 0; i < pendingUpdates.length; i++)
  343. {
  344. if (model.contains(pendingUpdates[i]))
  345. {
  346. model.updateEdgeParent(pendingUpdates[i]);
  347. }
  348. }
  349. }
  350. }
  351. finally
  352. {
  353. model.endUpdate();
  354. }
  355. };
  356. /**
  357. * Removes all labels, user objects and styles from the given node in-place.
  358. */
  359. EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup, diff)
  360. {
  361. var temp = parentLookup[cell.getId()];
  362. var inserted = (temp != null && temp.inserted != null) ? temp.inserted : {};
  363. var moved = (temp != null && temp.moved != null) ? temp.moved : {};
  364. var index = 0;
  365. // Restores existing order
  366. var childCount = model.getChildCount(cell);
  367. var prev = '';
  368. for (var i = 0; i < childCount; i++)
  369. {
  370. var cellId = model.getChildAt(cell, i).getId();
  371. if (moved[prev] == null &&
  372. (diff[EditorUi.DIFF_UPDATE] == null ||
  373. diff[EditorUi.DIFF_UPDATE][cellId] == null ||
  374. (diff[EditorUi.DIFF_UPDATE][cellId].previous == null &&
  375. diff[EditorUi.DIFF_UPDATE][cellId].parent == null)))
  376. {
  377. moved[prev] = cellId;
  378. }
  379. prev = cellId;
  380. }
  381. var addCell = mxUtils.bind(this, function(child)
  382. {
  383. var id = (child != null) ? child.getId() : '';
  384. if (child != null)
  385. {
  386. if (model.getChildAt(cell, index) != child)
  387. {
  388. model.add(cell, child, index);
  389. }
  390. this.patchCellRecursive(page, model,
  391. child, parentLookup, diff);
  392. index++;
  393. }
  394. var mov = moved[id];
  395. if (mov != null)
  396. {
  397. delete moved[id];
  398. addCell(model.getCell(mov));
  399. }
  400. var ins = inserted[id];
  401. if (ins != null)
  402. {
  403. delete inserted[id];
  404. addCell(this.getCellForJson(ins));
  405. }
  406. });
  407. addCell();
  408. // Handles orphaned moved pages
  409. for (var id in moved)
  410. {
  411. addCell(model.getCell(moved[id]));
  412. delete moved[id];
  413. }
  414. // Handles orphaned inserted pages
  415. for (var id in inserted)
  416. {
  417. addCell(this.getCellForJson(inserted[id]));
  418. delete inserted[id];
  419. }
  420. };
  421. /**
  422. * Removes all labels, user objects and styles from the given node in-place.
  423. */
  424. EditorUi.prototype.patchCell = function(model, cell, diff, resolve)
  425. {
  426. if (cell != null && diff != null)
  427. {
  428. // Last write wins for value except if label is empty
  429. if (resolve == null || (resolve.xmlValue == null &&
  430. (resolve.value == null || resolve.value == '')))
  431. {
  432. if (diff.value != null)
  433. {
  434. model.setValue(cell, diff.value);
  435. }
  436. else if (diff.xmlValue != null)
  437. {
  438. model.setValue(cell, mxUtils.parseXml(diff.xmlValue).documentElement);
  439. }
  440. }
  441. // Last write wins for style
  442. if ((resolve == null || resolve.style == null) && diff.style != null)
  443. {
  444. model.setStyle(cell, diff.style);
  445. }
  446. if (diff.visible != null)
  447. {
  448. model.setVisible(cell, diff.visible == 1);
  449. }
  450. if (diff.collapsed != null)
  451. {
  452. model.setCollapsed(cell, diff.collapsed == 1);
  453. }
  454. if (diff.vertex != null)
  455. {
  456. // Changes vertex state in-place
  457. cell.vertex = diff.vertex == 1;
  458. }
  459. if (diff.edge != null)
  460. {
  461. // Changes edge state in-place
  462. cell.edge = diff.edge == 1;
  463. }
  464. if (diff.connectable != null)
  465. {
  466. // Changes connectable state in-place
  467. cell.connectable = diff.connectable == 1;
  468. }
  469. if (diff.geometry != null)
  470. {
  471. model.setGeometry(cell, this.codec.decode(mxUtils.parseXml(
  472. diff.geometry).documentElement));
  473. }
  474. if (diff.source != null)
  475. {
  476. model.setTerminal(cell, model.getCell(diff.source), true);
  477. }
  478. if (diff.target != null)
  479. {
  480. model.setTerminal(cell, model.getCell(diff.target), false);
  481. }
  482. for (var key in diff)
  483. {
  484. if (!(key in this.cellPrototype))
  485. {
  486. cell[key] = diff[key];
  487. }
  488. }
  489. }
  490. };
  491. /**
  492. * Gets a file node that is comparable with a remote file node
  493. * so that using isEqualNode returns true if the files can be
  494. * considered equal.
  495. */
  496. EditorUi.prototype.getPagesForNode = function(node)
  497. {
  498. var tmp = this.editor.extractGraphModel(node, true);
  499. if (tmp != null)
  500. {
  501. node = tmp;
  502. }
  503. var diagrams = node.getElementsByTagName('diagram');
  504. var pages = [];
  505. for (var i = 0; i < diagrams.length; i++)
  506. {
  507. var page = new DiagramPage(diagrams[i]);
  508. this.updatePageRoot(page);
  509. pages.push(page);
  510. }
  511. return pages;
  512. };
  513. /**
  514. * Removes all labels, user objects and styles from the given node in-place.
  515. */
  516. EditorUi.prototype.diffPages = function(oldPages, newPages)
  517. {
  518. var graph = this.editor.graph;
  519. var inserted = [];
  520. var removed = [];
  521. var result = {};
  522. var lookup = {};
  523. var diff = {};
  524. var prev = null;
  525. for (var i = 0; i < newPages.length; i++)
  526. {
  527. lookup[newPages[i].getId()] = {page: newPages[i], prev: prev};
  528. prev = newPages[i];
  529. }
  530. prev = null;
  531. for (var i = 0; i < oldPages.length; i++)
  532. {
  533. var id = oldPages[i].getId();
  534. var newPage = lookup[id];
  535. if (newPage == null)
  536. {
  537. removed.push(id);
  538. }
  539. else
  540. {
  541. var temp = this.diffPage(oldPages[i], newPage.page);
  542. var pageDiff = {};
  543. if (Object.keys(temp).length > 0)
  544. {
  545. pageDiff.cells = temp;
  546. }
  547. var view = this.diffViewState(oldPages[i], newPage.page);
  548. if (Object.keys(view).length > 0)
  549. {
  550. pageDiff.view = view;
  551. }
  552. if (((newPage.prev != null) ? prev == null : prev != null) ||
  553. (prev != null && newPage.prev != null &&
  554. prev.getId() != newPage.prev.getId()))
  555. {
  556. pageDiff.previous = (newPage.prev != null) ? newPage.prev.getId() : '';
  557. }
  558. // FIXME: Check why names can be null in newer files
  559. // ignore in hash and do not diff null names for now
  560. if (newPage.page.getName() != null &&
  561. oldPages[i].getName() != newPage.page.getName())
  562. {
  563. pageDiff.name = newPage.page.getName();
  564. }
  565. if (Object.keys(pageDiff).length > 0)
  566. {
  567. diff[id] = pageDiff;
  568. }
  569. }
  570. delete lookup[oldPages[i].getId()];
  571. prev = oldPages[i];
  572. }
  573. for (var id in lookup)
  574. {
  575. var newPage = lookup[id];
  576. inserted.push({data: mxUtils.getXml(newPage.page.node),
  577. previous: (newPage.prev != null) ?
  578. newPage.prev.getId() : ''});
  579. }
  580. if (Object.keys(diff).length > 0)
  581. {
  582. result[EditorUi.DIFF_UPDATE] = diff;
  583. }
  584. if (removed.length > 0)
  585. {
  586. result[EditorUi.DIFF_REMOVE] = removed;
  587. }
  588. if (inserted.length > 0)
  589. {
  590. result[EditorUi.DIFF_INSERT] = inserted;
  591. }
  592. return result;
  593. };
  594. /**
  595. * Removes all labels, user objects and styles from the given node in-place.
  596. */
  597. EditorUi.prototype.createCellLookup = function(cell, prev, lookup)
  598. {
  599. lookup = (lookup != null) ? lookup : {};
  600. lookup[cell.getId()] = {cell: cell, prev: prev};
  601. var childCount = cell.getChildCount();
  602. prev = null;
  603. for (var i = 0; i < childCount; i++)
  604. {
  605. var child = cell.getChildAt(i);
  606. this.createCellLookup(child, prev, lookup);
  607. prev = child;
  608. }
  609. return lookup;
  610. };
  611. /**
  612. * Removes all labels, user objects and styles from the given node in-place.
  613. */
  614. EditorUi.prototype.diffCellRecursive = function(cell, prev, lookup, diff, removed)
  615. {
  616. diff = (diff != null) ? diff : {};
  617. var newCell = lookup[cell.getId()];
  618. delete lookup[cell.getId()];
  619. if (newCell == null)
  620. {
  621. removed.push(cell.getId());
  622. }
  623. else
  624. {
  625. var temp = this.diffCell(cell, newCell.cell);
  626. if (temp.parent != null ||
  627. (((newCell.prev != null) ? prev == null : prev != null) ||
  628. (prev != null && newCell.prev != null &&
  629. prev.getId() != newCell.prev.getId())))
  630. {
  631. temp.previous = (newCell.prev != null) ? newCell.prev.getId() : '';
  632. }
  633. if (Object.keys(temp).length > 0)
  634. {
  635. diff[cell.getId()] = temp;
  636. }
  637. }
  638. var childCount = cell.getChildCount();
  639. prev = null;
  640. for (var i = 0; i < childCount; i++)
  641. {
  642. var child = cell.getChildAt(i);
  643. this.diffCellRecursive(child, prev, lookup, diff, removed);
  644. prev = child;
  645. }
  646. return diff;
  647. };
  648. /**
  649. * Removes all labels, user objects and styles from the given node in-place.
  650. */
  651. EditorUi.prototype.diffPage = function(oldPage, newPage)
  652. {
  653. var inserted = [];
  654. var removed = [];
  655. var result = {};
  656. this.updatePageRoot(oldPage);
  657. this.updatePageRoot(newPage);
  658. var lookup = this.createCellLookup(newPage.root);
  659. var diff = this.diffCellRecursive(oldPage.root, null, lookup, diff, removed);
  660. for (var id in lookup)
  661. {
  662. var newCell = lookup[id];
  663. inserted.push(this.getJsonForCell(newCell.cell, newCell.prev));
  664. }
  665. if (Object.keys(diff).length > 0)
  666. {
  667. result[EditorUi.DIFF_UPDATE] = diff;
  668. }
  669. if (removed.length > 0)
  670. {
  671. result[EditorUi.DIFF_REMOVE] = removed;
  672. }
  673. if (inserted.length > 0)
  674. {
  675. result[EditorUi.DIFF_INSERT] = inserted;
  676. }
  677. return result;
  678. };
  679. /**
  680. * Removes all labels, user objects and styles from the given node in-place.
  681. */
  682. EditorUi.prototype.diffViewState = function(oldPage, newPage)
  683. {
  684. var source = oldPage.viewState;
  685. var target = newPage.viewState;
  686. var result = {};
  687. if (newPage == this.currentPage)
  688. {
  689. target = this.editor.graph.getViewState();
  690. }
  691. if (source != null && target != null)
  692. {
  693. for (var key in this.viewStateProperties)
  694. {
  695. // LATER: Check if normalization is needed for
  696. // object attribute order to compare JSON
  697. var old = JSON.stringify(source[key]);
  698. var now = JSON.stringify(target[key]);
  699. if (old != now)
  700. {
  701. result[key] = now;
  702. }
  703. }
  704. }
  705. return result;
  706. };
  707. /**
  708. * Removes all labels, user objects and styles from the given node in-place.
  709. */
  710. EditorUi.prototype.getCellForJson = function(json)
  711. {
  712. var geometry = (json.geometry != null) ? this.codec.decode(
  713. mxUtils.parseXml(json.geometry).documentElement) : null;
  714. var value = json.value;
  715. if (json.xmlValue != null)
  716. {
  717. value = mxUtils.parseXml(json.xmlValue).documentElement;
  718. }
  719. var cell = new mxCell(value, geometry, json.style);
  720. cell.connectable = json.connectable != 0;
  721. cell.collapsed = json.collapsed == 1;
  722. cell.visible = json.visible != 0;
  723. cell.vertex = json.vertex == 1;
  724. cell.edge = json.edge == 1;
  725. cell.id = json.id;
  726. for (var key in json)
  727. {
  728. if (!(key in this.cellPrototype))
  729. {
  730. cell[key] = json[key];
  731. }
  732. }
  733. return cell;
  734. };
  735. /**
  736. * Removes all labels, user objects and styles from the given node in-place.
  737. */
  738. EditorUi.prototype.getJsonForCell = function(cell, previous)
  739. {
  740. var result = {id: cell.getId()};
  741. if (cell.vertex)
  742. {
  743. result.vertex = 1;
  744. }
  745. if (cell.edge)
  746. {
  747. result.edge = 1;
  748. }
  749. if (!cell.connectable)
  750. {
  751. result.connectable = 0;
  752. }
  753. if (cell.parent != null)
  754. {
  755. result.parent = cell.parent.getId();
  756. }
  757. if (previous != null)
  758. {
  759. result.previous = previous.getId();
  760. }
  761. if (cell.source != null)
  762. {
  763. result.source = cell.source.getId();
  764. }
  765. if (cell.target != null)
  766. {
  767. result.target = cell.target.getId();
  768. }
  769. if (cell.style != null)
  770. {
  771. result.style = cell.style;
  772. }
  773. if (cell.geometry != null)
  774. {
  775. result.geometry = mxUtils.getXml(this.codec.encode(cell.geometry));
  776. }
  777. if (cell.collapsed)
  778. {
  779. result.collapsed = 1;
  780. }
  781. if (!cell.visible)
  782. {
  783. result.visible = 0;
  784. }
  785. if (cell.value != null)
  786. {
  787. if (typeof cell.value === 'object' && typeof cell.value.nodeType === 'number' &&
  788. typeof cell.value.nodeName === 'string' && typeof cell.value.getAttribute === 'function')
  789. {
  790. result.xmlValue = mxUtils.getXml(cell.value);
  791. }
  792. else
  793. {
  794. result.value = cell.value;
  795. }
  796. }
  797. for (var key in cell)
  798. {
  799. if (key != 'mxObjectId' && !(key in this.cellPrototype))
  800. {
  801. result[key] = cell[key];
  802. }
  803. }
  804. return result;
  805. };
  806. /**
  807. * Removes all labels, user objects and styles from the given node in-place.
  808. */
  809. EditorUi.prototype.diffCell = function(oldCell, newCell)
  810. {
  811. var diff = {};
  812. if (oldCell.vertex != newCell.vertex)
  813. {
  814. diff.vertex = (newCell.vertex) ? 1 : 0;
  815. }
  816. if (oldCell.edge != newCell.edge)
  817. {
  818. diff.edge = (newCell.edge) ? 1 : 0;
  819. }
  820. if (oldCell.connectable != newCell.connectable)
  821. {
  822. diff.connectable = (newCell.connectable) ? 1 : 0;
  823. }
  824. if (((oldCell.parent != null) ? newCell.parent == null : newCell.parent != null) ||
  825. (oldCell.parent != null && newCell.parent != null &&
  826. oldCell.parent.getId() != newCell.parent.getId()))
  827. {
  828. diff.parent = (newCell.parent != null) ? newCell.parent.getId() : '';
  829. }
  830. if (((oldCell.source != null) ? newCell.source == null : newCell.source != null) ||
  831. (oldCell.source != null && newCell.source != null &&
  832. oldCell.source.getId() != newCell.source.getId()))
  833. {
  834. diff.source = (newCell.source != null) ? newCell.source.getId() : '';
  835. }
  836. if (((oldCell.target != null) ? newCell.target == null : newCell.target != null) ||
  837. (oldCell.target != null && newCell.target != null &&
  838. oldCell.target.getId() != newCell.target.getId()))
  839. {
  840. diff.target = (newCell.target != null) ? newCell.target.getId() : '';
  841. }
  842. function isNode(value)
  843. {
  844. return value != null && typeof value === 'object' && typeof value.nodeType === 'number' &&
  845. typeof value.nodeName === 'string' && typeof value.getAttribute === 'function';
  846. };
  847. if (isNode(oldCell.value) && isNode(newCell.value))
  848. {
  849. if (!oldCell.value.isEqualNode(newCell.value))
  850. {
  851. diff.xmlValue = mxUtils.getXml(newCell.value);
  852. }
  853. }
  854. else if (oldCell.value != newCell.value)
  855. {
  856. if (isNode(newCell.value))
  857. {
  858. diff.xmlValue = mxUtils.getXml(newCell.value);
  859. }
  860. else
  861. {
  862. diff.value = (newCell.value != null) ? newCell.value : '';
  863. }
  864. }
  865. if (oldCell.style != newCell.style)
  866. {
  867. // LATER: Split into keys and do fine-grained diff
  868. diff.style = newCell.style;
  869. }
  870. if (oldCell.visible != newCell.visible)
  871. {
  872. diff.visible = (newCell.visible) ? 1 : 0;
  873. }
  874. if (oldCell.collapsed != newCell.collapsed)
  875. {
  876. diff.collapsed = (newCell.collapsed) ? 1 : 0;
  877. }
  878. // FIXME: Proto only needed because source.geometry has no constructor (wrong type?)
  879. if (!this.isObjectEqual(oldCell.geometry, newCell.geometry, new mxGeometry()))
  880. {
  881. diff.geometry = mxUtils.getXml(this.codec.encode(newCell.geometry));
  882. }
  883. // Compares all keys from oldCell to newCell and uses null in the diff
  884. // to force the attribute to be removed in the receinving client
  885. for (var key in oldCell)
  886. {
  887. if (key != 'mxObjectId' && !(key in this.cellPrototype) &&
  888. oldCell[key] != newCell[key])
  889. {
  890. diff[key] = (newCell[key] === undefined) ? null : newCell[key];
  891. }
  892. }
  893. // Compares the remaining keys in newCell with oldCell
  894. for (var key in newCell)
  895. {
  896. if (!(key in oldCell) &&
  897. key != 'mxObjectId' && !(key in this.cellPrototype) &&
  898. oldCell[key] != newCell[key])
  899. {
  900. diff[key] = (newCell[key] === undefined) ? null : newCell[key];
  901. }
  902. }
  903. return diff;
  904. };
  905. /**
  906. *
  907. */
  908. EditorUi.prototype.isObjectEqual = function(source, target, proto)
  909. {
  910. if (source == null && target == null)
  911. {
  912. return true;
  913. }
  914. else if ((source != null) ? target == null : target != null)
  915. {
  916. return false;
  917. }
  918. else
  919. {
  920. var replacer = function(key, value)
  921. {
  922. return (proto == null || proto[key] != value) ? ((value === true) ? 1 : value) : undefined;
  923. };
  924. //console.log('eq', JSON.stringify(source, replacer), JSON.stringify(target, replacer));
  925. return JSON.stringify(source, replacer) == JSON.stringify(target, replacer);
  926. }
  927. };