RealtimeMapping.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. * Copyright (c) 2006-2016, Gaudenz Alder
  4. */
  5. /**
  6. * Creates an object that maps all changes from the given diagramMap to the
  7. * given graph model.
  8. */
  9. function RealtimeMapping(driveRealtime, diagramMap, page)
  10. {
  11. this.driveRealtime = driveRealtime;
  12. this.diagramMap = diagramMap;
  13. this.page = page;
  14. this.graphModel = new mxGraphModel();
  15. if (page.root != null)
  16. {
  17. this.graphModel.setRoot(page.root);
  18. }
  19. this.ui = this.driveRealtime.ui;
  20. this.root = this.driveRealtime.root;
  21. this.graph = this.driveRealtime.graph;
  22. this.rtModel = this.driveRealtime.rtModel;
  23. };
  24. /**
  25. * Specifies the key of the root element in the model. Default is root.
  26. */
  27. RealtimeMapping.prototype.driveRealtime = null;
  28. /**
  29. * Specifies the key of the root element in the model. Default is root.
  30. */
  31. RealtimeMapping.prototype.diagramMap = null;
  32. /**
  33. * Specifies the key of the root element in the model. Default is root.
  34. */
  35. RealtimeMapping.prototype.page = null;
  36. /**
  37. * Specifies the key of the root element in the model. Default is root.
  38. */
  39. RealtimeMapping.prototype.graphModel = null;
  40. /**
  41. * Specifies the key of the root element in the model. Default is root.
  42. */
  43. RealtimeMapping.prototype.needsUpdate = true;
  44. /**
  45. * Specifies the key of the root element in the model. Default is root.
  46. */
  47. RealtimeMapping.prototype.selectionMap = null;
  48. /**
  49. * Synchronizes the collaboration model and the graph model and installs
  50. * the required listeners to keep them in sync.
  51. */
  52. RealtimeMapping.prototype.init = function()
  53. {
  54. this.diagramMap.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, mxUtils.bind(this, function(evt)
  55. {
  56. if (!this.driveRealtime.isLocalEvent(evt))
  57. {
  58. if (evt.property == this.driveRealtime.rootKey && evt.newValue != null)
  59. {
  60. this.beginUpdate();
  61. this.initGraph();
  62. this.needsUpdate = true;
  63. }
  64. else if (evt.property == 'name' && evt.newValue != null)
  65. {
  66. this.driveRealtime.ignoreChange = true;
  67. this.graph.model.execute(new RenamePage(this.ui, this.page, evt.newValue));
  68. this.driveRealtime.ignoreChange = false;
  69. }
  70. else if (evt.newValue != null)
  71. {
  72. if (evt.property == 'pageFormat')
  73. {
  74. this.realtimePageFormatChanged(evt.newValue);
  75. }
  76. else if (evt.property == 'pageScale')
  77. {
  78. this.realtimePageScaleChanged(evt.newValue);
  79. }
  80. else if (evt.property == 'backgroundColor')
  81. {
  82. this.realtimeBackgroundColorChanged(evt.newValue);
  83. }
  84. else if (evt.property == 'shadowVisible')
  85. {
  86. this.realtimeShadowVisibleChanged(evt.newValue);
  87. }
  88. else if (evt.property == 'foldingEnabled')
  89. {
  90. this.realtimeFoldingEnabledChanged(evt.newValue);
  91. }
  92. else if (evt.property == 'pageVisible')
  93. {
  94. this.realtimePageVisibleChanged(evt.newValue);
  95. }
  96. else if (evt.property == 'backgroundImage')
  97. {
  98. this.realtimeBackgroundImageChanged(evt.newValue);
  99. }
  100. else if (evt.property == 'mathEnabled')
  101. {
  102. this.realtimeMathEnabledChanged(evt.newValue);
  103. }
  104. }
  105. // Marks the mapping dirty regardless of active state
  106. if (evt.newValue != null && (evt.property == 'pageFormat' ||
  107. evt.property == 'pageScale' || evt.property == 'shadowVisible' ||
  108. evt.property == 'backgroundColor' || evt.property == 'foldingEnabled' ||
  109. evt.property == 'backgroundImage' || evt.property == 'mathEnabled' ||
  110. evt.property == 'pageVisible'))
  111. {
  112. this.needsUpdate = true;
  113. }
  114. }
  115. }));
  116. if (this.diagramMap.has(this.driveRealtime.rootKey))
  117. {
  118. this.initGraph();
  119. }
  120. else
  121. {
  122. this.initRealtime();
  123. }
  124. this.page.root = this.graphModel.getRoot();
  125. this.selectionMap = this.diagramMap.get('select');
  126. if (this.selectionMap == null)
  127. {
  128. this.initializeSelection();
  129. }
  130. // Resets selection state to ensure change event
  131. if (this.driveRealtime.file.isEditable())
  132. {
  133. this.selectionMap.set(this.driveRealtime.userId, '');
  134. }
  135. this.installRemoteSelectionListener();
  136. };
  137. /**
  138. *
  139. */
  140. RealtimeMapping.prototype.initializeSelection = function()
  141. {
  142. this.selectionMap = this.rtModel.createMap();
  143. if (this.driveRealtime.file.isEditable())
  144. {
  145. this.diagramMap.set('select', this.selectionMap);
  146. }
  147. //this.log('Selection list created');
  148. };
  149. /**
  150. * Adds a listener for changes to the RT selection map to highlight
  151. * remote selection
  152. */
  153. RealtimeMapping.prototype.installRemoteSelectionListener = function()
  154. {
  155. this.selectionMap.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, mxUtils.bind(this, function(evt)
  156. {
  157. if (!this.driveRealtime.isLocalEvent(evt) && evt.newValue != null && (this.ui.currentPage == null ||
  158. this.ui.currentPage == this.page))
  159. {
  160. var cellIds = evt.newValue.split(',');
  161. for (var i = 0; i < cellIds.length; i++)
  162. {
  163. this.driveRealtime.highlight(this.driveRealtime.model.getCell(cellIds[i]), evt.sessionId);
  164. }
  165. }
  166. }));
  167. };
  168. /**
  169. * Returns true if this diagram is being displayed.
  170. */
  171. RealtimeMapping.prototype.isActive = function()
  172. {
  173. return this.ui.currentPage == null || this.ui.currentPage.mapping == this;
  174. };
  175. /**
  176. * Syncs initial state from collab model to graph model.
  177. */
  178. RealtimeMapping.prototype.getGraphModel = function()
  179. {
  180. return (this.isActive()) ? this.driveRealtime.model : this.graphModel;
  181. };
  182. /**
  183. * Syncs initial state from collab model to graph model.
  184. */
  185. RealtimeMapping.prototype.initGraph = function()
  186. {
  187. if (this.isActive())
  188. {
  189. this.activate(true);
  190. mxClient.NO_FO = (this.graph.mathEnabled) ? true : Editor.prototype.originalNoForeignObject;
  191. // TODO: Fixes math offset - why?
  192. this.ui.editor.graph.sizeDidChange();
  193. }
  194. var rtRoot = this.diagramMap.get(this.driveRealtime.rootKey);
  195. if (rtRoot.cell == null)
  196. {
  197. this.createCell(rtRoot);
  198. this.restoreCell(rtRoot);
  199. }
  200. else
  201. {
  202. this.installAllRealtimeCellListeners(rtRoot);
  203. }
  204. // Stores root in current model and local model
  205. var gm = this.getGraphModel();
  206. gm.setRoot(rtRoot.cell);
  207. if (gm != this.graphModel)
  208. {
  209. this.graphModel.setRoot(gm.getRoot());
  210. }
  211. };
  212. /**
  213. * Writes the graph properties from the realtime model to the given mxGraphModel node.
  214. */
  215. RealtimeMapping.prototype.writeRealtimeToNode = function(node)
  216. {
  217. node.setAttribute('shadow', this.diagramMap.get('shadowVisible'));
  218. node.setAttribute('fold', this.diagramMap.get('foldingEnabled'));
  219. node.setAttribute('math', this.diagramMap.get('mathEnabled'));
  220. node.setAttribute('pageScale', this.diagramMap.get('pageScale'));
  221. var img = this.diagramMap.get('backgroundImage');
  222. if (img != null && img.length > 0)
  223. {
  224. node.setAttribute('backgroundImage', img);
  225. }
  226. var color = this.diagramMap.get('backgroundColor');
  227. if (color != null)
  228. {
  229. node.setAttribute('background', color);
  230. }
  231. var pf = this.diagramMap.get('pageFormat');
  232. if (pf != null)
  233. {
  234. var values = pf.split(',');
  235. if (values.length > 1)
  236. {
  237. node.setAttribute('pageWidth', parseInt(values[0]));
  238. node.setAttribute('pageHeight', parseInt(values[1]));
  239. }
  240. }
  241. };
  242. /**
  243. * Writes the graph properties from the realtime model to the given mxGraphModel node.
  244. */
  245. RealtimeMapping.prototype.writeNodeToRealtime = function(node)
  246. {
  247. this.diagramMap.set('shadowVisible', node.getAttribute('shadow'));
  248. this.diagramMap.set('foldingEnabled', node.getAttribute('fold'));
  249. this.diagramMap.set('mathEnabled', node.getAttribute('math'));
  250. this.diagramMap.set('pageScale', node.getAttribute('pageScale'));
  251. this.diagramMap.set('pageVisible', node.getAttribute('pageVisible'));
  252. var img = node.getAttribute('backgroundImage');
  253. if (img != null && img.length > 0)
  254. {
  255. this.diagramMap.set('backgroundImage', img);
  256. }
  257. var color = node.getAttribute('background');
  258. if (color != null)
  259. {
  260. this.diagramMap.set('backgroundColor', color);
  261. }
  262. this.diagramMap.set('pageFormat', node.getAttribute('pageWidth') + ',' +
  263. node.getAttribute('pageHeight'));
  264. };
  265. /**
  266. * Syncs initial state from collab model to graph model.
  267. */
  268. RealtimeMapping.prototype.activate = function(quiet)
  269. {
  270. this.realtimePageFormatChanged(this.diagramMap.get('pageFormat'), quiet);
  271. this.realtimePageScaleChanged(this.diagramMap.get('pageScale'), quiet);
  272. this.realtimeMathEnabledChanged(this.diagramMap.get('mathEnabled'), quiet);
  273. this.realtimeBackgroundColorChanged(this.diagramMap.get('backgroundColor'), quiet);
  274. this.realtimeShadowVisibleChanged(this.diagramMap.get('shadowVisible'), quiet);
  275. this.realtimeFoldingEnabledChanged(this.diagramMap.get('foldingEnabled'), quiet);
  276. this.realtimePageVisibleChanged(this.diagramMap.get('pageVisible'), quiet);
  277. this.realtimeBackgroundImageChanged(this.diagramMap.get('backgroundImage'), quiet);
  278. };
  279. /**
  280. * Syncs initial state from graph model to collab model.
  281. */
  282. RealtimeMapping.prototype.initRealtime = function()
  283. {
  284. this.rtModel.beginCompoundOperation();
  285. try
  286. {
  287. var rtCell = this.createRealtimeCell(this.getGraphModel().getRoot());
  288. this.saveRealtimeCell(rtCell.cell);
  289. this.diagramMap.set(this.driveRealtime.rootKey, rtCell);
  290. if (this.page.graphModelNode != null)
  291. {
  292. this.writeNodeToRealtime(this.page.graphModelNode);
  293. }
  294. else
  295. {
  296. var vs = this.page.viewState;
  297. var pf = (vs != null) ? vs.pageFormat : mxSettings.getPageFormat();
  298. this.diagramMap.set('shadowVisible', (vs != null && vs.shadowVisible) ? '1' : '0');
  299. this.diagramMap.set('foldingEnabled', (vs != null && !vs.foldingEnabled) ? '0' : '1');
  300. this.diagramMap.set('mathEnabled', (vs != null && vs.mathEnabled) ? '1' : '0');
  301. this.diagramMap.set('pageScale', this.graph.pageScale);
  302. this.diagramMap.set('pageVisible', (vs != null && !vs.pageVisible) ? '0' : '1');
  303. this.diagramMap.set('pageFormat', pf.width + ',' + pf.height);
  304. this.diagramMap.set('backgroundImage', (vs != null && vs.backgroundImage != null) ?
  305. JSON.stringify(vs.backgroundImage) : '');
  306. this.diagramMap.set('backgroundColor', (vs != null && vs.background != null) ?
  307. this.graph.background : '');
  308. }
  309. this.root.set('modifiedDate', new Date().getTime());
  310. this.rtModel.endCompoundOperation();
  311. }
  312. catch (e)
  313. {
  314. this.rtModel.endCompoundOperation();
  315. this.ui.handleError(e);
  316. }
  317. };
  318. /**
  319. * Syncs initial state from graph model to collab model.
  320. */
  321. RealtimeMapping.prototype.createRealtimeCell = function(cell)
  322. {
  323. var rtCell = cell.rtCell;
  324. if (rtCell == null)
  325. {
  326. rtCell = this.rtModel.create('Cell');
  327. rtCell.children = this.rtModel.createList();
  328. rtCell.cell = cell;
  329. cell.rtCell = rtCell;
  330. rtCell.cellId = cell.id;
  331. rtCell.type = (cell.vertex) ? 'vertex' : ((cell.edge) ? 'edge' : '');
  332. rtCell.connectable = (cell.connectable == null || cell.connectable) ? '1' : '0';
  333. if (mxUtils.isNode(cell.value))
  334. {
  335. rtCell.xmlValue = mxUtils.getXml(cell.value);
  336. }
  337. else if (cell.value != null)
  338. {
  339. rtCell.value = cell.value;
  340. }
  341. rtCell.style = (cell.style != null) ? cell.style : null;
  342. rtCell.geometry = (cell.geometry != null) ? mxUtils.getXml(this.driveRealtime.codec.encode(cell.geometry)) : null;
  343. rtCell.visible = (cell.visible == null || cell.visible) ? '1' : '0';
  344. rtCell.collapsed = (cell.collapsed != null && cell.collapsed) ? '1' : '0';
  345. for (var i = 0; i < this.graphModel.getChildCount(cell); i++)
  346. {
  347. var child = this.graphModel.getChildAt(cell, i);
  348. this.createRealtimeCell(child);
  349. if (child.rtCell.parent == null)
  350. {
  351. child.rtCell.parent = rtCell;
  352. rtCell.children.push(child.rtCell);
  353. }
  354. }
  355. this.installRealtimeCellListeners(rtCell);
  356. }
  357. return rtCell;
  358. };
  359. /**
  360. * Syncs initial state from graph model to collab model.
  361. */
  362. RealtimeMapping.prototype.saveRealtimeCell = function(cell)
  363. {
  364. if (cell.source != null)
  365. {
  366. if (cell.source.rtCell == null)
  367. {
  368. this.createRealtimeCell(cell.source);
  369. }
  370. cell.rtCell.source = cell.source.rtCell;
  371. }
  372. else
  373. {
  374. cell.rtCell.source = null;
  375. }
  376. if (cell.target != null)
  377. {
  378. if (cell.target.rtCell == null)
  379. {
  380. this.createRealtimeCell(cell.target);
  381. }
  382. cell.rtCell.target = cell.target.rtCell;
  383. }
  384. else
  385. {
  386. cell.rtCell.target = null;
  387. }
  388. for (var i = 0; i < this.graphModel.getChildCount(cell); i++)
  389. {
  390. this.saveRealtimeCell(this.graphModel.getChildAt(cell, i));
  391. }
  392. };
  393. /**
  394. * Syncs initial state from collab model to graph model.
  395. */
  396. RealtimeMapping.prototype.createCell = function(rtCell)
  397. {
  398. var cell = rtCell.cell;
  399. if (cell == null)
  400. {
  401. cell = new mxCell();
  402. rtCell.cell = cell;
  403. cell.rtCell = rtCell;
  404. cell.id = rtCell.cellId;
  405. cell.vertex = rtCell.type == 'vertex';
  406. cell.edge = rtCell.type == 'edge';
  407. cell.connectable = rtCell.connectable != '0';
  408. cell.value = (rtCell.xmlValue != null) ? mxUtils.parseXml(rtCell.xmlValue).documentElement : rtCell.value;
  409. cell.style = rtCell.style;
  410. cell.geometry = (rtCell.geometry != null) ? this.driveRealtime.codec.decode(mxUtils.parseXml(rtCell.geometry).documentElement) : null;
  411. cell.visible = rtCell.visible != '0';
  412. cell.collapsed = rtCell.collapsed == '1';
  413. for (var i = 0; i < rtCell.children.length; i++)
  414. {
  415. var rtChild = rtCell.children.get(i);
  416. this.createCell(rtChild);
  417. if (rtChild.cell.parent == null)
  418. {
  419. cell.insert(rtChild.cell);
  420. }
  421. }
  422. this.installRealtimeCellListeners(rtCell);
  423. }
  424. return cell;
  425. };
  426. /**
  427. * Restores connection between edges and terminals.
  428. */
  429. RealtimeMapping.prototype.restoreCell = function(rtCell)
  430. {
  431. var valid = true;
  432. if (rtCell.cell != null)
  433. {
  434. //console.log('restoreCell', rtCell.cellId);
  435. if (rtCell.source != null)
  436. {
  437. // Removes edge if source is no longer in the model
  438. if (rtCell.source.parent == null)
  439. {
  440. //console.log('invalid source', valid, rtCell.cellId, rtCell.source.cellId);
  441. rtCell.source = null;
  442. valid = false;
  443. }
  444. else
  445. {
  446. if (rtCell.source.cell == null)
  447. {
  448. this.createCell(rtCell.source);
  449. }
  450. rtCell.source.cell.insertEdge(rtCell.cell, true);
  451. }
  452. }
  453. if (valid && rtCell.target != null)
  454. {
  455. // Removes edge if source is no longer in the model
  456. if (rtCell.target.parent == null)
  457. {
  458. //console.log('invalid target', valid, rtCell.cellId, rtCell.target.cellId);
  459. rtCell.target = null;
  460. valid = false;
  461. }
  462. else
  463. {
  464. if (rtCell.target.cell == null)
  465. {
  466. this.createCell(rtCell.target);
  467. }
  468. rtCell.target.cell.insertEdge(rtCell.cell, false);
  469. }
  470. }
  471. // Checks if edge contains required terminals or terminal points
  472. if (valid && this.graphModel.isEdge(rtCell.cell))
  473. {
  474. var geo = this.graphModel.getGeometry(rtCell.cell);
  475. valid = geo != null &&
  476. (this.graphModel.getTerminal(rtCell.cell, true) != null || geo.getTerminalPoint(true) != null) &&
  477. (this.graphModel.getTerminal(rtCell.cell, false) != null || geo.getTerminalPoint(false) != null);
  478. //console.log('geometry check', valid, rtCell.cellId);
  479. }
  480. }
  481. // Removes invalid cell
  482. if (!valid)
  483. {
  484. if (rtCell.parent != null)
  485. {
  486. rtCell.parent.children.removeValue(rtCell);
  487. rtCell.parent = null;
  488. }
  489. if (rtCell.cell != null)
  490. {
  491. // TODO: Remove from source and target?
  492. //console.log('remove invalid cell', rtCell.cellId);
  493. this.getGraphModel().remove(rtCell.cell);
  494. }
  495. }
  496. else
  497. {
  498. for (var i = 0; i < rtCell.children.length; i++)
  499. {
  500. this.restoreCell(rtCell.children.get(i));
  501. }
  502. }
  503. };
  504. /**
  505. * Restores connection between edges and terminals.
  506. */
  507. RealtimeMapping.prototype.containsRealtimeCell = function(rtCell)
  508. {
  509. var tmp = rtCell;
  510. while (tmp.parent != null)
  511. {
  512. tmp = tmp.parent;
  513. }
  514. return tmp == this.diagramMap.get(this.driveRealtime.rootKey);
  515. };
  516. /**
  517. *
  518. */
  519. RealtimeMapping.prototype.beginUpdate = function()
  520. {
  521. var graphModel = this.getGraphModel();
  522. if (!this.driveRealtime.ignoreChange)
  523. {
  524. this.driveRealtime.ignoreChange = true;
  525. graphModel.beginUpdate();
  526. window.setTimeout(mxUtils.bind(this, function()
  527. {
  528. graphModel.endUpdate();
  529. this.driveRealtime.ignoreChange = false;
  530. }), 0);
  531. }
  532. return graphModel;
  533. };
  534. /**
  535. * Syncs initial state from collab model to graph model.
  536. */
  537. RealtimeMapping.prototype.installAllRealtimeCellListeners = function(rtCell)
  538. {
  539. if (rtCell != null)
  540. {
  541. this.installRealtimeCellListeners(rtCell);
  542. for (var i = 0; i < rtCell.children.length; i++)
  543. {
  544. this.installAllRealtimeCellListeners(rtCell.children.get(i));
  545. }
  546. }
  547. };
  548. /**
  549. * Adds the listener for added and removed cells in the collab model and maps
  550. * them to the graph model.
  551. */
  552. RealtimeMapping.prototype.installRealtimeCellListeners = function(rtCell)
  553. {
  554. rtCell.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, mxUtils.bind(this, function(evt)
  555. {
  556. this.handleValueChanged(rtCell, evt);
  557. this.needsUpdate = true;
  558. }));
  559. rtCell.children.addEventListener(gapi.drive.realtime.EventType.VALUES_ADDED, mxUtils.bind(this, function(evt)
  560. {
  561. this.handleValuesAdded(rtCell, evt);
  562. this.needsUpdate = true;
  563. }));
  564. rtCell.children.addEventListener(gapi.drive.realtime.EventType.VALUES_REMOVED, mxUtils.bind(this, function(evt)
  565. {
  566. this.handleValuesRemoved(rtCell, evt);
  567. this.needsUpdate = true;
  568. }));
  569. };
  570. /**
  571. * Adds the listener for added and removed cells in the collab model and maps
  572. * them to the graph model.
  573. */
  574. RealtimeMapping.prototype.handleValueChanged = function(rtCell, evt)
  575. {
  576. var cell = rtCell.cell;
  577. if (!this.driveRealtime.isLocalEvent(evt) && cell != null)
  578. {
  579. var value = evt.newValue;
  580. var key = evt.property;
  581. var graphModel = this.beginUpdate();
  582. //console.log('valueChanged: cell=' + rtCell.cellId + ' key=' + key + ' value=' + ((value != null) ? (value.cellId || value) : '[null]'));
  583. if (key == 'type')
  584. {
  585. cell.vertex = value == 'vertex';
  586. cell.edge = value == 'edge';
  587. }
  588. else if (key == 'connectable')
  589. {
  590. cell.connectable = (value == '1');
  591. }
  592. else if (key == 'source' || key == 'target')
  593. {
  594. if (value == null)
  595. {
  596. if (evt.oldValue != null)
  597. {
  598. graphModel.setTerminal(cell, null, key == 'source');
  599. }
  600. }
  601. else
  602. {
  603. // Handles the case where an edge is connected to a vertex which is not in the model
  604. if (value.cell == null || !this.containsRealtimeCell(value) || graphModel.getCell(value.cellId) == null)
  605. {
  606. if (rtCell.parent != null)
  607. {
  608. rtCell.parent.children.removeValue(rtCell);
  609. rtCell.parent = null;
  610. }
  611. graphModel.setTerminal(cell, null, key == 'source');
  612. graphModel.remove(rtCell.cell);
  613. rtCell[key] = null;
  614. }
  615. else
  616. {
  617. graphModel.setTerminal(cell, value.cell, key == 'source');
  618. }
  619. }
  620. }
  621. else if (key == 'value')
  622. {
  623. graphModel.setValue(cell, value);
  624. }
  625. else if (key == 'xmlValue')
  626. {
  627. graphModel.setValue(cell, mxUtils.parseXml(value).documentElement);
  628. }
  629. else if (key == 'style')
  630. {
  631. graphModel.setStyle(cell, value);
  632. }
  633. else if (key == 'geometry')
  634. {
  635. var geometry = (value != null) ? this.driveRealtime.codec.decode(mxUtils.parseXml(value).documentElement) : null;
  636. graphModel.setGeometry(cell, geometry);
  637. }
  638. else if (key == 'collapsed')
  639. {
  640. graphModel.setCollapsed(cell, value == '1');
  641. }
  642. else if (key == 'visible')
  643. {
  644. graphModel.setVisible(cell, value == '1');
  645. }
  646. else if (key == 'parent')
  647. {
  648. // Removes the child from its previous parent in the realtime model.
  649. if (evt.oldValue != null)
  650. {
  651. //console.log('remove clone', 'parent', evt.oldValue.cellId, 'child', rtCell.cellId);
  652. evt.oldValue.children.removeValue(rtCell);
  653. }
  654. else
  655. {
  656. this.createCell(rtCell);
  657. this.restoreCell(rtCell);
  658. }
  659. if (value == null)
  660. {
  661. graphModel.remove(cell);
  662. }
  663. else
  664. {
  665. var index = value.children.indexOf(rtCell);
  666. if (index >= 0)
  667. {
  668. //console.log('move child', 'parent', value.cellId, 'child', rtCell.cellId, index);
  669. graphModel.add(value.cell, rtCell.cell, index);
  670. }
  671. }
  672. }
  673. }
  674. };
  675. /**
  676. * Adds the listener for added and removed cells in the collab model and maps
  677. * them to the graph model.
  678. */
  679. RealtimeMapping.prototype.handleValuesAdded = function(rtCell, evt)
  680. {
  681. if (!this.driveRealtime.isLocalEvent(evt))
  682. {
  683. var graphModel = this.beginUpdate();
  684. for (var i = 0; i < evt.values.length; i++)
  685. {
  686. var rtChild = evt.values[i];
  687. //console.log('valueAdded', 'parent', rtCell.cellId, 'child', rtChild.cellId, 'index', evt.index + i, rtChild);
  688. // Removes child if the parent of the child and the parent of the children array are not
  689. // the same. This happens if clients move a cell into different parents concurrently.
  690. if (rtChild.parent != null)
  691. {
  692. if (rtChild.parent != rtCell)
  693. {
  694. //console.log('remove clone', 'parent', rtCell.cellId, 'child', rtChild.cellId);
  695. rtCell.children.removeValue(rtChild);
  696. }
  697. else
  698. {
  699. if (rtChild.cell == null || rtChild.cell.parent == null)
  700. {
  701. this.createCell(rtChild);
  702. this.restoreCell(rtChild);
  703. }
  704. // Resolves conflict when two clients change the order of a child at the same
  705. // time which results in the same child appearing multiple times in the list.
  706. // Note that the actual child index may be different from the event information
  707. // at this point so a generic check for duplicates is performed based on the
  708. // first appearance of the cell in the list.
  709. var first = rtCell.children.indexOf(rtChild);
  710. var last = rtCell.children.lastIndexOf(rtChild);
  711. while (first != last)
  712. {
  713. //console.log('remove duplicate', rtChild.cellId, last);
  714. rtCell.children.remove(last);
  715. last = rtCell.children.lastIndexOf(rtChild);
  716. }
  717. // Inserts the child at the smallest index to maintain consistency with RT
  718. if (rtChild.parent == rtCell)
  719. {
  720. //console.log('add', 'parent', rtCell.cellId, 'child', rtChild.cellId, 'index', Math.min(first, evt.index + i));
  721. graphModel.add(rtCell.cell, rtChild.cell, Math.min(first, evt.index + i));
  722. }
  723. }
  724. }
  725. }
  726. }
  727. };
  728. /**
  729. * Adds the listener for added and removed cells in the collab model and maps
  730. * them to the graph model.
  731. */
  732. RealtimeMapping.prototype.handleValuesRemoved = function(rtCell, evt)
  733. {
  734. if (!this.driveRealtime.isLocalEvent(evt))
  735. {
  736. var graphModel = this.beginUpdate();
  737. for (var i = 0; i < evt.values.length; i++)
  738. {
  739. var rtChild = evt.values[i];
  740. if (rtChild.cell != null)
  741. {
  742. //console.log('valueRemoved', 'parent', rtCell.cellId, 'child', rtChild.cellId,
  743. // 'index', evt.index + i, rtChild, rtChild.cell);
  744. // Checks if the realtime parent and the graph parent are different and updates the parent
  745. // in the graph to match the realtime parent. This happens if the child was removed as a
  746. // clone in another client.
  747. if (rtChild.parent != null && rtChild.parent != rtCell && rtChild.cell.parent != rtChild.parent.cell)
  748. {
  749. //console.log('move clone', rtChild.cellId, evt.index + i, rtChild.parent.cellId);
  750. var index = rtChild.parent.children.indexOf(rtChild);
  751. graphModel.add(rtChild.parent.cell, rtChild.cell, index);
  752. }
  753. else
  754. {
  755. // Checks if the realtime parent contains a duplicate entry of this child
  756. // and updates the index of the child in the graph. This happens if the
  757. // child was removed as a duplicate entry in another client.
  758. var index = rtCell.children.indexOf(rtChild);
  759. if (index >= 0)
  760. {
  761. //console.log('adjust duplicate', rtCell.cellId, evt.index + i, rtChild.cellId, 'index', index);
  762. graphModel.add(rtCell.cell, rtChild.cell, index);
  763. }
  764. }
  765. }
  766. }
  767. }
  768. };
  769. /**
  770. * Syncs initial state from collab model to graph model.
  771. */
  772. RealtimeMapping.prototype.realtimePageFormatChanged = function(value, quiet)
  773. {
  774. if (value != null)
  775. {
  776. var values = value.split(',');
  777. if (values.length > 1)
  778. {
  779. if (!this.isActive())
  780. {
  781. if (this.page.viewState != null)
  782. {
  783. this.page.viewState.pageFormat = new mxRectangle(0, 0, parseInt(values[0]), parseInt(values[1]));
  784. }
  785. }
  786. else if (quiet)
  787. {
  788. this.graph.pageFormat = new mxRectangle(0, 0, parseInt(values[0]), parseInt(values[1]));
  789. }
  790. else
  791. {
  792. this.driveRealtime.ignorePageFormatChanged = true;
  793. this.ui.setPageFormat(new mxRectangle(0, 0, parseInt(values[0]), parseInt(values[1])));
  794. this.driveRealtime.ignorePageFormatChanged = false;
  795. }
  796. }
  797. }
  798. };
  799. /**
  800. * Syncs initial state from collab model to graph model.
  801. */
  802. RealtimeMapping.prototype.realtimePageScaleChanged = function(value, quiet)
  803. {
  804. if (value != null)
  805. {
  806. if (!this.isActive())
  807. {
  808. if (this.page.viewState != null)
  809. {
  810. this.page.viewState.pageScale = parseFloat(value);
  811. }
  812. }
  813. else if (quiet)
  814. {
  815. this.graph.pageScale = parseFloat(value);
  816. }
  817. else
  818. {
  819. this.driveRealtime.ignorePageScaleChanged = true;
  820. this.ui.setPageScale(parseFloat(value));
  821. this.driveRealtime.ignorePageScaleChanged = false;
  822. }
  823. }
  824. };
  825. /**
  826. * Syncs initial state from collab model to graph model.
  827. */
  828. RealtimeMapping.prototype.realtimeBackgroundColorChanged = function(value, quiet)
  829. {
  830. if (!this.isActive())
  831. {
  832. if (this.page.viewState != null)
  833. {
  834. this.page.viewState.background = (value == '') ? null : value;
  835. }
  836. }
  837. else if (quiet)
  838. {
  839. this.graph.background = (value == '') ? null : value;
  840. }
  841. else
  842. {
  843. this.driveRealtime.ignoreBackgroundColorChanged = true;
  844. this.ui.setBackgroundColor((value == '') ? null : value);
  845. this.driveRealtime.ignoreBackgroundColorChanged = false;
  846. }
  847. };
  848. /**
  849. * Syncs initial state from collab model to graph model.
  850. */
  851. RealtimeMapping.prototype.realtimeFoldingEnabledChanged = function(value, quiet)
  852. {
  853. if (!this.isActive())
  854. {
  855. if (this.page.viewState != null)
  856. {
  857. this.page.viewState.foldingEnabled = value == '1';
  858. }
  859. }
  860. else if (quiet)
  861. {
  862. this.graph.foldingEnabled = value == '1';
  863. }
  864. else
  865. {
  866. this.driveRealtime.ignoreFoldingEnabledChanged = true;
  867. this.ui.setFoldingEnabled(value == '1');
  868. this.driveRealtime.ignoreFoldingEnabledChanged = false;
  869. }
  870. };
  871. /**
  872. * Syncs initial state from collab model to graph model.
  873. */
  874. RealtimeMapping.prototype.realtimePageVisibleChanged = function(value, quiet)
  875. {
  876. if (!this.isActive())
  877. {
  878. if (this.page.viewState != null)
  879. {
  880. this.page.viewState.pageVisible = value != '0';
  881. }
  882. }
  883. else if (quiet)
  884. {
  885. this.graph.pageVisible = value != '0';
  886. this.graph.pageBreaksVisible = this.graph.pageVisible;
  887. this.graph.preferPageSize = this.graph.pageVisible;
  888. }
  889. else
  890. {
  891. this.driveRealtime.ignorePageVisibleChanged = true;
  892. this.ui.setPageVisible(value != '0');
  893. this.driveRealtime.ignorePageVisibleChanged = false;
  894. }
  895. };
  896. /**
  897. * Syncs initial state from collab model to graph model.
  898. */
  899. RealtimeMapping.prototype.realtimeShadowVisibleChanged = function(value, quiet)
  900. {
  901. // Does not need quiet mode as it's handled independently of refresh
  902. if (!this.isActive())
  903. {
  904. if (this.page.viewState != null)
  905. {
  906. this.page.viewState.shadowVisible = value == '1';
  907. }
  908. }
  909. else
  910. {
  911. this.driveRealtime.ignoreShadowVisibleChanged = true;
  912. this.ui.editor.graph.setShadowVisible(value == '1');
  913. this.driveRealtime.ignoreShadowVisibleChanged = false;
  914. }
  915. };
  916. /**
  917. * Syncs initial state from collab model to graph model.
  918. */
  919. RealtimeMapping.prototype.realtimeBackgroundImageChanged = function(value, quiet)
  920. {
  921. var data = (value != null && value.length > 0) ? JSON.parse(value) : null;
  922. if (!this.isActive())
  923. {
  924. if (this.page.viewState != null)
  925. {
  926. this.page.viewState.backgroundImage = (data != null) ? new mxImage(data.src, data.width, data.height) : null;
  927. }
  928. }
  929. else if (quiet)
  930. {
  931. this.graph.setBackgroundImage((data != null) ? new mxImage(data.src, data.width, data.height) : null);
  932. }
  933. else
  934. {
  935. this.driveRealtime.ignoreBackgroundImageChanged = true;
  936. this.ui.setBackgroundImage((data != null) ? new mxImage(data.src, data.width, data.height) : null);
  937. this.driveRealtime.ignoreBackgroundImageChanged = false;
  938. }
  939. };
  940. /**
  941. * Syncs initial state from collab model to graph model.
  942. */
  943. RealtimeMapping.prototype.realtimeMathEnabledChanged = function(value, quiet)
  944. {
  945. if (!this.isActive())
  946. {
  947. if (this.page.viewState != null)
  948. {
  949. this.page.viewState.mathEnabled = urlParams['math'] == '1' || value == '1';
  950. }
  951. }
  952. else if (quiet)
  953. {
  954. this.graph.mathEnabled = urlParams['math'] == '1' || value == '1';
  955. }
  956. else
  957. {
  958. this.driveRealtime.ignoreMathEnabledChanged = true;
  959. this.ui.setMathEnabled(urlParams['math'] == '1' || value == '1');
  960. this.driveRealtime.ignoreMathEnabledChanged = false;
  961. }
  962. };
  963. /**
  964. * Syncs initial state from collab model to graph model.
  965. */
  966. RealtimeMapping.prototype.removeAllRealtimeCellListeners = function(rtCell)
  967. {
  968. if (rtCell != null)
  969. {
  970. rtCell.removeAllEventListeners();
  971. rtCell.children.removeAllEventListeners();
  972. for (var i = 0; i < rtCell.children.length; i++)
  973. {
  974. this.removeAllRealtimeCellListeners(rtCell.children.get(i));
  975. }
  976. }
  977. };
  978. /**
  979. * Syncs initial state from collab model to graph model.
  980. */
  981. RealtimeMapping.prototype.destroy = function()
  982. {
  983. this.diagramMap.removeAllEventListeners();
  984. this.selectionMap.removeAllEventListeners();
  985. this.removeAllRealtimeCellListeners(this.diagramMap.get(this.driveRealtime.rootKey));
  986. };