DrawioFile.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. DrawioFile = function(ui, data)
  6. {
  7. mxEventSource.call(this);
  8. /**
  9. * Holds the x-coordinate of the point.
  10. * @type number
  11. * @default 0
  12. */
  13. this.ui = ui;
  14. /**
  15. * Holds the x-coordinate of the point.
  16. * @type number
  17. * @default 0
  18. */
  19. this.data = data || '';
  20. this.shadowData = this.data;
  21. };
  22. /**
  23. * Global switch for realtime collaboration type to use sync URL parameter
  24. * with the following possible values:
  25. *
  26. * - none: overwrite
  27. * - manual: manual sync
  28. * - auto: automatic sync
  29. */
  30. DrawioFile.SYNC = urlParams['sync'] || 'auto';
  31. /**
  32. * Specifies if last write wins should be used for values and styles.
  33. */
  34. DrawioFile.LAST_WRITE_WINS = true;
  35. // Extends mxEventSource
  36. mxUtils.extend(DrawioFile, mxEventSource);
  37. /**
  38. * Delay for last save in ms.
  39. */
  40. DrawioFile.prototype.allChangesSavedKey = 'allChangesSaved';
  41. /**
  42. * Specifies the delay between the last change and the autosave.
  43. */
  44. DrawioFile.prototype.autosaveDelay = 1500;
  45. /**
  46. * Specifies the maximum delay before an autosave is forced even if the graph
  47. * is being changed.
  48. */
  49. DrawioFile.prototype.maxAutosaveDelay = 30000;
  50. /**
  51. * Contains the thread for the next autosave.
  52. */
  53. DrawioFile.prototype.autosaveThread = null;
  54. /**
  55. * Stores the timestamp for hte last autosave.
  56. */
  57. DrawioFile.prototype.lastAutosave = null;
  58. /**
  59. * Stores the modified state.
  60. */
  61. DrawioFile.prototype.modified = false;
  62. /**
  63. * Holds a copy of the current file data.
  64. */
  65. DrawioFile.prototype.data = null;
  66. /**
  67. * Holds a copy of the last saved file data.
  68. */
  69. DrawioFile.prototype.shadowData = null;
  70. /**
  71. * Holds a copy of the parsed last saved file data.
  72. */
  73. DrawioFile.prototype.shadowPages = null;
  74. /**
  75. * Specifies if the graph change listener is enabled. Default is true.
  76. */
  77. DrawioFile.prototype.changeListenerEnabled = true;
  78. /**
  79. * Sets the delay for autosave in milliseconds. Default is 1500.
  80. */
  81. DrawioFile.prototype.lastAutosaveRevision = null;
  82. /**
  83. * Sets the delay for autosave in milliseconds. Default is 1000.
  84. */
  85. DrawioFile.prototype.maxAutosaveRevisionDelay = 1800000;
  86. /**
  87. * Specifies if notify events should be ignored.
  88. */
  89. DrawioFile.prototype.inConflictState = false;
  90. /**
  91. * Specifies if notify events should be ignored.
  92. */
  93. DrawioFile.prototype.invalidChecksum = false;
  94. /**
  95. * Specifies if notify events should be ignored.
  96. */
  97. DrawioFile.prototype.reportEnabled = true;
  98. /**
  99. * Specifies if notify events should be ignored.
  100. */
  101. DrawioFile.prototype.stats = {
  102. joined: 0, /* number of join messages received */
  103. merged: 0, /* number of calls to merge */
  104. reload: 0, /* number of times the file was reloaded */
  105. checksumErrors: 0, /* number of checksum errors */
  106. bytesSent: 0, /* number of bytes send in messages */
  107. bytesReceived: 0, /* number of bytes received in messages */
  108. msgSent: 0, /* number of messages sent */
  109. msgReceived: 0, /* number of messages received */
  110. cacheHits: 0, /* number of times the cache returned patches */
  111. cacheMiss: 0, /* number of times we have missed a cache entry */
  112. cacheFail: 0, /* number of times we have failed to read the cache */
  113. conflicts: 0, /* number of write conflicts when saving a file */
  114. timeouts: 0 /* number of time we have given up to retry after a write conflict */
  115. };
  116. /**
  117. * Specifies if notify events should be ignored.
  118. */
  119. DrawioFile.prototype.getSize = function()
  120. {
  121. return (this.data != null) ? this.data.length : 0;
  122. };
  123. /**
  124. * Adds the listener for automatically saving the diagram for local changes.
  125. */
  126. DrawioFile.prototype.synchronizeFile = function(success, error)
  127. {
  128. if (this.savingFile)
  129. {
  130. if (error != null)
  131. {
  132. error({message: mxResources.get('busy')});
  133. }
  134. }
  135. else
  136. {
  137. if (this.sync != null)
  138. {
  139. this.sync.fileChanged(success, error);
  140. }
  141. else
  142. {
  143. this.updateFile(success, error);
  144. }
  145. }
  146. };
  147. /**
  148. * Adds the listener for automatically saving the diagram for local changes.
  149. */
  150. DrawioFile.prototype.updateFile = function(success, error, abort)
  151. {
  152. this.getLatestVersion(mxUtils.bind(this, function(latestFile)
  153. {
  154. try
  155. {
  156. if (abort == null || !abort())
  157. {
  158. if (latestFile != null)
  159. {
  160. this.mergeFile(latestFile, success, error);
  161. }
  162. else
  163. {
  164. this.reloadFile(success, error);
  165. }
  166. }
  167. }
  168. catch (e)
  169. {
  170. if (error != null)
  171. {
  172. error(e);
  173. }
  174. }
  175. }), error);
  176. };
  177. /**
  178. * Adds the listener for automatically saving the diagram for local changes.
  179. */
  180. DrawioFile.prototype.mergeFile = function(file, success, error)
  181. {
  182. try
  183. {
  184. // Takes copy of current shadow document
  185. var shadow = (this.shadowPages != null) ? this.shadowPages :
  186. this.ui.getPagesForNode(mxUtils.parseXml(
  187. this.shadowData).documentElement);
  188. this.checkShadow(shadow);
  189. // Loads new document as shadow document
  190. this.shadowPages = this.ui.getPagesForNode(
  191. mxUtils.parseXml(file.data).
  192. documentElement)
  193. // Creates a patch for backup if the checksum fails
  194. this.backupPatch = (this.isModified()) ?
  195. this.ui.diffPages(shadow,
  196. this.ui.pages) : null;
  197. // Patches the current document
  198. var patches = [this.ui.diffPages(shadow, this.shadowPages)];
  199. if (!this.ignorePatches(patches))
  200. {
  201. // Patching previous shadow to verify checksum
  202. var patched = this.ui.patchPages(shadow, patches[0]);
  203. var patchedDetails = {};
  204. var checksum = this.ui.getHashValueForPages(patched, patchedDetails);
  205. var currentDetails = {};
  206. var current = this.ui.getHashValueForPages(this.shadowPages, currentDetails);
  207. if (urlParams['test'] == '1')
  208. {
  209. EditorUi.debug('File.mergeFile', [this],
  210. 'backup', this.backupPatch,
  211. 'patches', patches,
  212. 'checksum', current == checksum, checksum);
  213. }
  214. if (checksum != null && checksum != current)
  215. {
  216. var data = this.compressReportData(this.getAnonymizedXmlForPages(patched));
  217. this.checksumError(error, patches,
  218. 'Checksum: ' + checksum +
  219. ((patchedDetails != null) ? ('\nDetails: ' +
  220. JSON.stringify(patchedDetails)) : '') +
  221. '\nCurrent: ' + current +
  222. ((currentDetails != null) ? ('\nCurrent Details: ' +
  223. JSON.stringify(currentDetails)) : '') +
  224. '\nPatched:\n' + data);
  225. // Abnormal termination
  226. return;
  227. }
  228. else
  229. {
  230. this.patch(patches,
  231. (DrawioFile.LAST_WRITE_WINS) ?
  232. this.backupPatch : null);
  233. this.checkPages();
  234. }
  235. }
  236. this.invalidChecksum = false;
  237. this.inConflictState = false;
  238. this.setDescriptor(file.getDescriptor());
  239. this.descriptorChanged();
  240. this.backupPatch = null;
  241. if (success != null)
  242. {
  243. success();
  244. }
  245. }
  246. catch (e)
  247. {
  248. this.inConflictState = true;
  249. this.invalidChecksum = true;
  250. if (error != null)
  251. {
  252. error(e);
  253. }
  254. try
  255. {
  256. this.sendErrorReport('Error in mergeFile', null, e);
  257. }
  258. catch (e2)
  259. {
  260. // ignore
  261. }
  262. }
  263. };
  264. /**
  265. * Adds the listener for automatically saving the diagram for local changes.
  266. */
  267. DrawioFile.prototype.getAnonymizedXmlForPages = function(pages)
  268. {
  269. var enc = new mxCodec(mxUtils.createXmlDocument());
  270. var file = enc.document.createElement('mxfile');
  271. if (pages != null)
  272. {
  273. for (var i = 0; i < pages.length; i++)
  274. {
  275. var temp = this.ui.anonymizeNode(enc.encode(
  276. new mxGraphModel(pages[i].root)));
  277. temp.setAttribute('id', pages[i].getId());
  278. if (pages[i].viewState)
  279. {
  280. this.ui.editor.graph.saveViewState(pages[i].viewState, temp, true);
  281. }
  282. file.appendChild(temp);
  283. }
  284. }
  285. return mxUtils.getPrettyXml(file);
  286. };
  287. /**
  288. * Checks if the file is empty.
  289. */
  290. DrawioFile.prototype.checkPages = function()
  291. {
  292. if (this.ui.getCurrentFile() == this && (this.ui.pages == null ||
  293. this.ui.pages.length == 0))
  294. {
  295. this.sendErrorReport(
  296. 'Pages is null or empty',
  297. 'ShadowPages: ' + (this.shadowPages != null) +
  298. '\nShadowData: ' + (this.shadowData != null));
  299. }
  300. };
  301. /**
  302. * Checks if the given shadow is valid.
  303. */
  304. DrawioFile.prototype.checkShadow = function(shadow)
  305. {
  306. if (shadow == null || shadow.length == 0)
  307. {
  308. var data = (this.shadowData == null) ? 'null' :
  309. this.compressReportData(
  310. this.ui.anonymizeString(
  311. this.shadowData),
  312. null, 5000);
  313. this.sendErrorReport(
  314. 'Shadow is null or empty',
  315. 'Shadow: ' + ((shadow != null) ? shadow.length : 'null') +
  316. '\nShadowPages: ' + ((this.shadowPages != null) ?
  317. this.shadowPages.length : 'null') +
  318. '\nShadowData: ' + data);
  319. }
  320. };
  321. /**
  322. * Adds the listener for automatically saving the diagram for local changes.
  323. */
  324. DrawioFile.prototype.compressReportData = function(data, limit, max)
  325. {
  326. limit = (limit != null) ? limit : 10000;
  327. if (data != null && data.length > limit)
  328. {
  329. data = this.ui.editor.graph.compress(data) + '\n';
  330. }
  331. if (max != null && data != null && data.length > max)
  332. {
  333. data = data.substring(0, max) + '[...]';
  334. }
  335. return data;
  336. };
  337. /**
  338. * Adds the listener for automatically saving the diagram for local changes.
  339. */
  340. DrawioFile.prototype.checksumError = function(error, patches, details)
  341. {
  342. this.stats.checksumErrors++;
  343. this.inConflictState = true;
  344. this.invalidChecksum = true;
  345. this.descriptorChanged();
  346. if (this.sync != null)
  347. {
  348. this.sync.updateOnlineState();
  349. }
  350. if (error != null)
  351. {
  352. error();
  353. }
  354. try
  355. {
  356. if (patches != null)
  357. {
  358. for (var i = 0; i < patches.length; i++)
  359. {
  360. this.ui.anonymizePatch(patches[i]);
  361. }
  362. }
  363. var data = this.compressReportData(
  364. this.getAnonymizedXmlForPages(
  365. this.shadowPages), 20000);
  366. var json = this.compressReportData(
  367. JSON.stringify(patches, null, 2));
  368. this.sendErrorReport(
  369. 'Checksum Error',
  370. ((details != null) ? (details) : '') +
  371. '\n\nPatches:\n' + json +
  372. '\n\nData:\n' + data);
  373. }
  374. catch (e)
  375. {
  376. // ignore
  377. }
  378. };
  379. /**
  380. * Adds the listener for automatically saving the diagram for local changes.
  381. */
  382. DrawioFile.prototype.sendErrorReport = function(title, details, error)
  383. {
  384. try
  385. {
  386. var user = this.getCurrentUser();
  387. var uid = (user != null) ? this.ui.hashValue(user.id) : 'unknown';
  388. if (this.stats.start != null)
  389. {
  390. this.stats.uptime = Math.round((new Date().getTime() -
  391. new Date(this.stats.start).getTime()) / 1000);
  392. }
  393. var filename = this.getTitle();
  394. var dot = filename.lastIndexOf('.');
  395. var ext = 'none';
  396. if (dot > 0)
  397. {
  398. ext = filename.substring(dot);
  399. }
  400. var stack = (error != null) ? error.stack : new Error().stack;
  401. EditorUi.sendReport(title + ' ' + new Date().toISOString() + ':' +
  402. '\n\nBrowser=' + navigator.userAgent +
  403. '\nPlugins=' + ((mxSettings.settings != null) ? mxSettings.getPlugins() : 'null') +
  404. '\nFile=' + this.ui.hashValue(this.getId()) + ' (' + this.getMode() + ')' +
  405. '\nClient=' + ((this.sync != null) ? (this.sync.clientId) : 'null') +
  406. '\nUser=' + uid +
  407. '\nExt=' + ext +
  408. '\nSize=' + this.getSize() +
  409. '\nSync=' + DrawioFile.SYNC +
  410. '\n\nStats:\n' + JSON.stringify(this.stats, null, 2) +
  411. ((details != null) ? ('\n\n' + details) : '') +
  412. '\n\nStack:\n' + stack);
  413. }
  414. catch (e)
  415. {
  416. // ignore
  417. }
  418. };
  419. /**
  420. * Adds the listener for automatically saving the diagram for local changes.
  421. */
  422. DrawioFile.prototype.reloadFile = function(success, error)
  423. {
  424. try
  425. {
  426. this.ui.spinner.stop();
  427. var fn = mxUtils.bind(this, function()
  428. {
  429. this.stats.reload++;
  430. this.reportEnabled = false;
  431. // Restores view state and current page
  432. var viewState = this.ui.editor.graph.getViewState();
  433. var selection = this.ui.editor.graph.getSelectionCells();
  434. var page = this.ui.currentPage;
  435. this.ui.loadFile(this.getHash(), true, null, mxUtils.bind(this, function()
  436. {
  437. if (this.ui.fileLoadedError == null)
  438. {
  439. this.ui.restoreViewState(page, viewState, selection);
  440. if (this.backupPatch != null)
  441. {
  442. this.patch([this.backupPatch]);
  443. }
  444. // Carry-over stats
  445. var file = this.ui.getCurrentFile();
  446. if (file != null)
  447. {
  448. file.stats = this.stats;
  449. }
  450. if (success != null)
  451. {
  452. success();
  453. }
  454. }
  455. }), true);
  456. });
  457. if (this.isModified() && this.backupPatch == null)
  458. {
  459. this.ui.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function()
  460. {
  461. this.handleFileSuccess(DrawioFile.SYNC == 'manual');
  462. }), fn, mxResources.get('cancel'), mxResources.get('discardChanges'));
  463. }
  464. else
  465. {
  466. fn();
  467. }
  468. }
  469. catch (e)
  470. {
  471. if (error != null)
  472. {
  473. error(e);
  474. }
  475. }
  476. };
  477. /**
  478. * Shows a conflict dialog to the user.
  479. */
  480. DrawioFile.prototype.copyFile = function(success, error)
  481. {
  482. this.ui.editor.editAsNew(this.ui.getFileData(true),
  483. this.ui.getCopyFilename(this));
  484. };
  485. /**
  486. * Returns true if the patches in the given array are empty.
  487. */
  488. DrawioFile.prototype.ignorePatches = function(patches)
  489. {
  490. var ignore = true;
  491. for (var i = 0; i < patches.length && ignore; i++)
  492. {
  493. ignore = ignore && Object.keys(patches[i]).length == 0;
  494. }
  495. return ignore;
  496. };
  497. /**
  498. * Applies the given patches to the file.
  499. */
  500. DrawioFile.prototype.patch = function(patches, resolver)
  501. {
  502. // Saves state of undo history
  503. var undoMgr = this.ui.editor.undoManager;
  504. var history = undoMgr.history.slice();
  505. var nextAdd = undoMgr.indexOfNextAdd;
  506. // Hides graph during updates
  507. var graph = this.ui.editor.graph;
  508. graph.container.style.visibility = 'hidden';
  509. // Ignores change events
  510. var prev = this.changeListenerEnabled;
  511. this.changeListenerEnabled = false;
  512. // Folding and math change require special handling
  513. var fold = graph.foldingEnabled;
  514. var math = graph.mathEnabled;
  515. // Updates text editor if cell changes during validation
  516. var redraw = graph.cellRenderer.redraw;
  517. graph.cellRenderer.redraw = function(state)
  518. {
  519. if (state.view.graph.isEditing(state.cell))
  520. {
  521. state.view.graph.scrollCellToVisible(state.cell);
  522. state.view.graph.cellEditor.resize();
  523. }
  524. redraw.apply(this, arguments);
  525. };
  526. graph.model.beginUpdate();
  527. try
  528. {
  529. // Applies patches
  530. for (var i = 0; i < patches.length; i++)
  531. {
  532. this.ui.pages = this.ui.patchPages(this.ui.pages,
  533. patches[i], true, resolver, this.isModified());
  534. }
  535. // Always needs at least one page
  536. if (this.ui.pages.length == 0)
  537. {
  538. this.ui.pages.push(this.ui.createPage());
  539. }
  540. // Checks if current page was removed
  541. if (mxUtils.indexOf(this.ui.pages, this.ui.currentPage) < 0)
  542. {
  543. this.ui.selectPage(this.ui.pages[0], true);
  544. }
  545. }
  546. finally
  547. {
  548. graph.model.endUpdate();
  549. // Restores previous state
  550. graph.container.style.visibility = '';
  551. graph.cellRenderer.redraw = redraw;
  552. this.changeListenerEnabled = prev;
  553. // Restores history state
  554. undoMgr.history = history;
  555. undoMgr.indexOfNextAdd = nextAdd;
  556. undoMgr.fireEvent(new mxEventObject(mxEvent.CLEAR));
  557. if (this.ui.currentPage == null || this.ui.currentPage.needsUpdate)
  558. {
  559. // Updates the graph and background
  560. if (math != graph.mathEnabled)
  561. {
  562. this.ui.editor.updateGraphComponents();
  563. graph.refresh();
  564. }
  565. else
  566. {
  567. if (fold != graph.foldingEnabled)
  568. {
  569. graph.view.revalidate();
  570. }
  571. else
  572. {
  573. graph.view.validate();
  574. }
  575. graph.sizeDidChange();
  576. }
  577. // Updates view state in format panel if nothing is selected
  578. if (this.ui.format != null && graph.isSelectionEmpty())
  579. {
  580. this.ui.format.refresh();
  581. }
  582. }
  583. this.ui.updateTabContainer();
  584. }
  585. };
  586. /**
  587. * Adds the listener for automatically saving the diagram for local changes.
  588. */
  589. DrawioFile.prototype.save = function(revision, success, error, unloading, overwrite, manual)
  590. {
  591. if (!this.isEditable())
  592. {
  593. if (error != null)
  594. {
  595. error({message: mxResources.get('readOnly')});
  596. }
  597. else
  598. {
  599. throw new Error(mxResources.get('readOnly'));
  600. }
  601. }
  602. else if (!overwrite && this.invalidChecksum)
  603. {
  604. if (error != null)
  605. {
  606. error({message: mxResources.get('checksum')});
  607. }
  608. else
  609. {
  610. throw new Error(mxResources.get('checksum'));
  611. }
  612. }
  613. else
  614. {
  615. this.updateFileData();
  616. this.clearAutosave();
  617. }
  618. };
  619. /**
  620. * Translates this point by the given vector.
  621. *
  622. * @param {number} dx X-coordinate of the translation.
  623. * @param {number} dy Y-coordinate of the translation.
  624. */
  625. DrawioFile.prototype.updateFileData = function()
  626. {
  627. this.setData(this.ui.getFileData(null, null, null, null, null, null, null, null, this));
  628. };
  629. /**
  630. * Translates this point by the given vector.
  631. *
  632. * @param {number} dx X-coordinate of the translation.
  633. * @param {number} dy Y-coordinate of the translation.
  634. */
  635. DrawioFile.prototype.saveAs = function(filename, success, error) { };
  636. /**
  637. * Translates this point by the given vector.
  638. *
  639. * @param {number} dx X-coordinate of the translation.
  640. * @param {number} dy Y-coordinate of the translation.
  641. */
  642. DrawioFile.prototype.saveFile = function(title, revision, success, error) { };
  643. /**
  644. * Returns true if copy, export and print are not allowed for this file.
  645. */
  646. DrawioFile.prototype.getPublicUrl = function(fn)
  647. {
  648. fn(null);
  649. };
  650. /**
  651. * Returns true if copy, export and print are not allowed for this file.
  652. */
  653. DrawioFile.prototype.isRestricted = function()
  654. {
  655. return false;
  656. };
  657. /**
  658. * Translates this point by the given vector.
  659. *
  660. * @param {number} dx X-coordinate of the translation.
  661. * @param {number} dy Y-coordinate of the translation.
  662. */
  663. DrawioFile.prototype.isModified = function()
  664. {
  665. return this.modified;
  666. };
  667. /**
  668. * Translates this point by the given vector.
  669. *
  670. * @param {number} dx X-coordinate of the translation.
  671. * @param {number} dy Y-coordinate of the translation.
  672. */
  673. DrawioFile.prototype.setModified = function(value)
  674. {
  675. this.modified = value;
  676. };
  677. /**
  678. * Specifies if the autosave checkbox should be shown in the document
  679. * properties dialog. Default is false.
  680. */
  681. DrawioFile.prototype.isAutosaveOptional = function()
  682. {
  683. return false;
  684. };
  685. /**
  686. * Translates this point by the given vector.
  687. *
  688. * @param {number} dx X-coordinate of the translation.
  689. * @param {number} dy Y-coordinate of the translation.
  690. */
  691. DrawioFile.prototype.isAutosave = function()
  692. {
  693. return !this.inConflictState && this.ui.editor.autosave;
  694. };
  695. /**
  696. * Translates this point by the given vector.
  697. *
  698. * @param {number} dx X-coordinate of the translation.
  699. * @param {number} dy Y-coordinate of the translation.
  700. */
  701. DrawioFile.prototype.isRenamable = function()
  702. {
  703. return false;
  704. };
  705. /**
  706. * Translates this point by the given vector.
  707. *
  708. * @param {number} dx X-coordinate of the translation.
  709. * @param {number} dy Y-coordinate of the translation.
  710. */
  711. DrawioFile.prototype.rename = function(title, success, error) { };
  712. /**
  713. * Translates this point by the given vector.
  714. *
  715. * @param {number} dx X-coordinate of the translation.
  716. * @param {number} dy Y-coordinate of the translation.
  717. */
  718. DrawioFile.prototype.isMovable = function()
  719. {
  720. return false;
  721. };
  722. /**
  723. * Translates this point by the given vector.
  724. *
  725. * @param {number} dx X-coordinate of the translation.
  726. * @param {number} dy Y-coordinate of the translation.
  727. */
  728. DrawioFile.prototype.move = function(folderId, success, error) { };
  729. /**
  730. * Returns the hash of the file which consists of a prefix for the storage
  731. * type and the ID of the file.
  732. */
  733. DrawioFile.prototype.getHash = function()
  734. {
  735. return '';
  736. };
  737. /**
  738. * Returns the ID of the file.
  739. */
  740. DrawioFile.prototype.getId = function()
  741. {
  742. return '';
  743. };
  744. /**
  745. * Returns true if the file is editable.
  746. */
  747. DrawioFile.prototype.isEditable = function()
  748. {
  749. return !this.ui.editor.isChromelessView() || this.ui.editor.editable;
  750. };
  751. /**
  752. * Returns the location as a new object.
  753. * @type mx.Point
  754. */
  755. DrawioFile.prototype.getUi = function()
  756. {
  757. return this.ui;
  758. };
  759. /**
  760. * Returns the current title of the file.
  761. */
  762. DrawioFile.prototype.getTitle = function()
  763. {
  764. return '';
  765. };
  766. /**
  767. * Sets the current data of the file.
  768. */
  769. DrawioFile.prototype.setData = function(data)
  770. {
  771. this.data = data;
  772. };
  773. /**
  774. * Returns the current data of the file.
  775. */
  776. DrawioFile.prototype.getData = function()
  777. {
  778. return this.data;
  779. };
  780. /**
  781. * Opens this file in the editor.
  782. */
  783. DrawioFile.prototype.open = function()
  784. {
  785. var data = this.getData();
  786. if (data != null)
  787. {
  788. this.ui.setFileData(data);
  789. // Updates shadow in case any page IDs have been updated
  790. this.shadowData = mxUtils.getXml(this.ui.getXmlFileData());
  791. }
  792. this.installListeners();
  793. if (this.isSyncSupported())
  794. {
  795. this.startSync();
  796. }
  797. };
  798. /**
  799. * Hook for subclassers.
  800. */
  801. DrawioFile.prototype.isSyncSupported = function()
  802. {
  803. return false;
  804. };
  805. /**
  806. * Hook for subclassers.
  807. */
  808. DrawioFile.prototype.isRevisionHistorySupported = function()
  809. {
  810. return false;
  811. };
  812. /**
  813. * Hook for subclassers.
  814. */
  815. DrawioFile.prototype.getRevisions = function(success, error)
  816. {
  817. success(null);
  818. };
  819. /**
  820. * Hook for subclassers to get the latest descriptor of this file
  821. * and return it in the success handler.
  822. */
  823. DrawioFile.prototype.loadDescriptor = function(success, error)
  824. {
  825. success(null);
  826. };
  827. /**
  828. * Hook for subclassers to get the latest etag of this file
  829. * and return it in the success handler.
  830. */
  831. DrawioFile.prototype.loadPatchDescriptor = function(success, error)
  832. {
  833. this.loadDescriptor(mxUtils.bind(this, function(desc)
  834. {
  835. success(desc);
  836. }), error);
  837. };
  838. /**
  839. * Creates a starts the synchronization.
  840. */
  841. DrawioFile.prototype.startSync = function()
  842. {
  843. if ((DrawioFile.SYNC == 'auto' && urlParams['stealth'] != '1') &&
  844. (urlParams['rt'] == '1' || !this.ui.editor.chromeless ||
  845. this.ui.editor.editable))
  846. {
  847. this.sync = new DrawioFileSync(this);
  848. this.sync.start();
  849. }
  850. };
  851. /**
  852. * Hook for subclassers to check if an error is a conflict.
  853. */
  854. DrawioFile.prototype.isConflict = function()
  855. {
  856. return false;
  857. };
  858. /**
  859. * Gets the channel ID for sync messages.
  860. */
  861. DrawioFile.prototype.getChannelId = function()
  862. {
  863. // Slash, space and plus replaced with underscore
  864. return this.ui.editor.graph.compress(this.getHash()).replace(/[\/ +]/g, '_');
  865. };
  866. /**
  867. * Gets the channel ID from the given descriptor.
  868. */
  869. DrawioFile.prototype.getChannelKey = function(desc)
  870. {
  871. return null;
  872. };
  873. /**
  874. * Returns the current etag.
  875. */
  876. DrawioFile.prototype.getCurrentUser = function()
  877. {
  878. return null;
  879. };
  880. /**
  881. * Hook for subclassers to get the latest version of this file
  882. * and return it in the success handler.
  883. */
  884. DrawioFile.prototype.getLatestVersion = function(success, error)
  885. {
  886. success(null);
  887. };
  888. /**
  889. * Returns the last modified date of this file.
  890. */
  891. DrawioFile.prototype.getLastModifiedDate = function()
  892. {
  893. return new Date();
  894. };
  895. /**
  896. * Sets the current etag.
  897. */
  898. DrawioFile.prototype.setCurrentEtag = function(etag)
  899. {
  900. this.setDescriptorEtag(this.getDescriptor(), etag);
  901. };
  902. /**
  903. * Returns the current etag.
  904. */
  905. DrawioFile.prototype.getCurrentEtag = function()
  906. {
  907. return this.getDescriptorEtag(this.getDescriptor());
  908. };
  909. /**
  910. * Returns the descriptor from this file.
  911. */
  912. DrawioFile.prototype.getDescriptor = function()
  913. {
  914. return null;
  915. };
  916. /**
  917. * Sets the descriptor for this file.
  918. */
  919. DrawioFile.prototype.setDescriptor = function() { };
  920. /**
  921. * Updates the etag on the given descriptor.
  922. */
  923. DrawioFile.prototype.setDescriptorEtag = function(desc, etag) { };
  924. /**
  925. * Returns the etag from the given descriptor.
  926. */
  927. DrawioFile.prototype.getDescriptorEtag = function(desc)
  928. {
  929. return null;
  930. };
  931. /**
  932. * Returns the secret from the given descriptor.
  933. */
  934. DrawioFile.prototype.getDescriptorSecret = function(desc)
  935. {
  936. return null;
  937. };
  938. /**
  939. * Installs the change listener.
  940. */
  941. DrawioFile.prototype.installListeners = function()
  942. {
  943. if (this.changeListener == null)
  944. {
  945. this.changeListener = mxUtils.bind(this, function(sender, eventObject)
  946. {
  947. var edit = (eventObject != null) ? eventObject.getProperty('edit') : null;
  948. if (this.changeListenerEnabled && this.isEditable() && (edit == null || !edit.ignoreEdit))
  949. {
  950. this.fileChanged();
  951. }
  952. });
  953. this.ui.editor.graph.model.addListener(mxEvent.CHANGE, this.changeListener);
  954. // Some options trigger autosave
  955. this.ui.editor.graph.addListener('gridSizeChanged', this.changeListener);
  956. this.ui.editor.graph.addListener('shadowVisibleChanged', this.changeListener);
  957. this.ui.addListener('pageFormatChanged', this.changeListener);
  958. this.ui.addListener('pageScaleChanged', this.changeListener);
  959. this.ui.addListener('backgroundColorChanged', this.changeListener);
  960. this.ui.addListener('backgroundImageChanged', this.changeListener);
  961. this.ui.addListener('foldingEnabledChanged', this.changeListener);
  962. this.ui.addListener('mathEnabledChanged', this.changeListener);
  963. this.ui.addListener('gridEnabledChanged', this.changeListener);
  964. this.ui.addListener('guidesEnabledChanged', this.changeListener);
  965. this.ui.addListener('pageViewChanged', this.changeListener);
  966. }
  967. };
  968. /**
  969. * Returns the location as a new object.
  970. * @type mx.Point
  971. */
  972. DrawioFile.prototype.addAllSavedStatus = function(status)
  973. {
  974. if (this.ui.statusContainer != null)
  975. {
  976. status = (status != null) ? status : mxUtils.htmlEntities(mxResources.get(this.allChangesSavedKey));
  977. if (this.isRevisionHistorySupported())
  978. {
  979. this.ui.editor.setStatus('<div title="'+ mxUtils.htmlEntities(mxResources.get('revisionHistory')) +
  980. '" style="text-decoration:underline;cursor:pointer;">' + status + '</div>');
  981. var links = this.ui.statusContainer.getElementsByTagName('div');
  982. if (links.length > 0)
  983. {
  984. mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
  985. {
  986. this.ui.actions.get('revisionHistory').funct();
  987. }));
  988. }
  989. }
  990. else
  991. {
  992. this.ui.editor.setStatus(status);
  993. }
  994. }
  995. };
  996. /**
  997. * Adds the listener for automatically saving the diagram for local changes.
  998. */
  999. DrawioFile.prototype.addUnsavedStatus = function(err)
  1000. {
  1001. if (!this.inConflictState && this.ui.statusContainer != null)
  1002. {
  1003. if (err instanceof Error && err.message != null && err.message != '')
  1004. {
  1005. this.ui.editor.setStatus('<div class="geStatusAlert" style="overflow:hidden;">' +
  1006. mxUtils.htmlEntities(mxResources.get('unsavedChanges')) +
  1007. ' (' + mxUtils.htmlEntities(err.message) + ')</div>');
  1008. }
  1009. else
  1010. {
  1011. // FIXME: Handle multiple tabs
  1012. // if (this.ui.mode == null && urlParams['splash'] == '0')
  1013. // {
  1014. // try
  1015. // {
  1016. // this.ui.updateDraft();
  1017. // this.setModified(false);
  1018. // }
  1019. // catch (e)
  1020. // {
  1021. // // Keeps modified flag unchanged
  1022. // }
  1023. // }
  1024. var msg = this.getErrorMessage(err);
  1025. if (msg != null && msg.length > 60)
  1026. {
  1027. msg = msg.substring(0, 60) + '...';
  1028. }
  1029. this.ui.editor.setStatus('<div class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
  1030. mxUtils.htmlEntities(mxResources.get('unsavedChangesClickHereToSave')) +
  1031. ((msg != null && msg != '') ? ' (' + mxUtils.htmlEntities(msg) + ')' : '') + '</div>');
  1032. // Installs click handler for saving
  1033. var links = this.ui.statusContainer.getElementsByTagName('div');
  1034. if (links != null && links.length > 0)
  1035. {
  1036. mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
  1037. {
  1038. this.ui.actions.get((this.ui.mode == null || !this.isEditable()) ?
  1039. 'saveAs' : 'save').funct();
  1040. }));
  1041. }
  1042. else
  1043. {
  1044. this.ui.editor.setStatus('<div class="geStatusAlert" style="overflow:hidden;">' +
  1045. mxUtils.htmlEntities(mxResources.get('unsavedChanges')) + '</div>');
  1046. }
  1047. }
  1048. }
  1049. };
  1050. /**
  1051. * Halts all timers and shows a conflict status message. The optional error
  1052. * handler is invoked first.
  1053. */
  1054. DrawioFile.prototype.addConflictStatus = function(fn, message)
  1055. {
  1056. if (this.invalidChecksum)
  1057. {
  1058. message = mxResources.get('checksum');
  1059. }
  1060. this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) +
  1061. ((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : ''));
  1062. this.ui.spinner.stop();
  1063. this.clearAutosave();
  1064. var links = (this.ui.statusContainer != null) ? this.ui.statusContainer.getElementsByTagName('div') : null;
  1065. if (links != null && links.length > 0)
  1066. {
  1067. mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function(evt)
  1068. {
  1069. if (mxEvent.getSource(evt).nodeName != 'IMG')
  1070. {
  1071. fn();
  1072. }
  1073. }));
  1074. }
  1075. else
  1076. {
  1077. this.ui.alert(mxUtils.htmlEntities(mxResources.get('fileChangedSync')), fn);
  1078. }
  1079. };
  1080. /**
  1081. * Halts all timers and shows a conflict status message. The optional error
  1082. * handler is invoked first.
  1083. */
  1084. DrawioFile.prototype.setConflictStatus = function(message)
  1085. {
  1086. this.ui.editor.setStatus('<div class="geStatusAlert geBlink" style="cursor:pointer;overflow:hidden;">' + message +
  1087. ' <a href="https://desk.draw.io/support/solutions/articles/16000087947" target="_blank"><img border="0" ' +
  1088. 'title="' + mxUtils.htmlEntities(mxResources.get('help')) + '" style="margin-left:2px;cursor:help;' +
  1089. 'opacity:0.5;width:16px;height:16px;" valign="bottom" src="' + Editor.helpImage + '" style=""/></a></div>');
  1090. };
  1091. /**
  1092. * Shows a conflict dialog to the user.
  1093. */
  1094. DrawioFile.prototype.showRefreshDialog = function(success, error)
  1095. {
  1096. if (this.ui.editor.isChromelessView() && !this.ui.editor.editable)
  1097. {
  1098. this.ui.alert(mxResources.get('fileChangedSync'), mxUtils.bind(this, function()
  1099. {
  1100. this.reloadFile(success, error);
  1101. }));
  1102. }
  1103. else
  1104. {
  1105. // Allows for escape key to be pressed while dialog is showing
  1106. this.addConflictStatus(mxUtils.bind(this, function()
  1107. {
  1108. this.showRefreshDialog(success, error);
  1109. }));
  1110. this.ui.showError(mxResources.get('error') + ' (' + mxResources.get('checksum') + ')',
  1111. mxResources.get('fileChangedSyncDialog'),
  1112. mxResources.get('makeCopy'), mxUtils.bind(this, function()
  1113. {
  1114. this.copyFile(success, error);
  1115. }), null, mxResources.get('synchronize'), mxUtils.bind(this, function()
  1116. {
  1117. this.reloadFile(success, error);
  1118. }), mxResources.get('cancel'), mxUtils.bind(this, function()
  1119. {
  1120. this.ui.hideDialog();
  1121. }), 360, 150);
  1122. }
  1123. };
  1124. /**
  1125. * Shows a dialog with no synchronize option.
  1126. */
  1127. DrawioFile.prototype.showCopyDialog = function(success, error, overwrite)
  1128. {
  1129. this.inConflictState = false;
  1130. this.invalidChecksum = false;
  1131. this.addUnsavedStatus();
  1132. this.ui.showError(mxResources.get('externalChanges'),
  1133. mxResources.get('fileChangedOverwriteDialog'),
  1134. mxResources.get('makeCopy'), mxUtils.bind(this, function()
  1135. {
  1136. this.copyFile(success, error);
  1137. }), null, mxResources.get('overwrite'), overwrite,
  1138. mxResources.get('cancel'), mxUtils.bind(this, function()
  1139. {
  1140. this.ui.hideDialog();
  1141. }), 360, 150);
  1142. };
  1143. /**
  1144. * Shows a conflict dialog to the user.
  1145. */
  1146. DrawioFile.prototype.showConflictDialog = function(overwrite, synchronize)
  1147. {
  1148. this.ui.showError(mxResources.get('externalChanges'),
  1149. mxResources.get('fileChangedSyncDialog'),
  1150. mxResources.get('overwrite'), overwrite, null,
  1151. mxResources.get('synchronize'), synchronize,
  1152. mxResources.get('cancel'), mxUtils.bind(this, function()
  1153. {
  1154. this.ui.hideDialog();
  1155. this.handleFileError(null, false);
  1156. }), 340, 150);
  1157. };
  1158. /**
  1159. * Checks if the client is authorized and calls the next step.
  1160. */
  1161. DrawioFile.prototype.redirectToNewApp = function(error)
  1162. {
  1163. this.ui.spinner.stop();
  1164. if (!this.redirectDialogShowing)
  1165. {
  1166. this.redirectDialogShowing = true;
  1167. var url = window.location.protocol + '//' + window.location.host + '/' + this.ui.getSearch(
  1168. ['create', 'title', 'mode', 'url', 'drive', 'splash', 'state']) + '#' + this.getHash();
  1169. var redirect = mxUtils.bind(this, function()
  1170. {
  1171. var fn = mxUtils.bind(this, function()
  1172. {
  1173. this.redirectDialogShowing = false;
  1174. if (window.location.href == url)
  1175. {
  1176. window.location.reload();
  1177. }
  1178. else
  1179. {
  1180. window.location.href = url;
  1181. }
  1182. });
  1183. if (error == null && this.isModified())
  1184. {
  1185. this.ui.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function()
  1186. {
  1187. this.redirectDialogShowing = false;
  1188. }), fn, mxResources.get('cancel'), mxResources.get('discardChanges'));
  1189. }
  1190. else
  1191. {
  1192. fn();
  1193. }
  1194. });
  1195. if (error != null)
  1196. {
  1197. if (this.isModified())
  1198. {
  1199. this.ui.confirm(mxResources.get('redirectToNewApp'), mxUtils.bind(this, function()
  1200. {
  1201. this.redirectDialogShowing = false;
  1202. error();
  1203. }), redirect, mxResources.get('cancel'), mxResources.get('discardChanges'));
  1204. }
  1205. else
  1206. {
  1207. this.ui.confirm(mxResources.get('redirectToNewApp'), redirect, mxUtils.bind(this, function()
  1208. {
  1209. this.redirectDialogShowing = false;
  1210. error();
  1211. }));
  1212. }
  1213. }
  1214. else
  1215. {
  1216. this.ui.alert(mxResources.get('redirectToNewApp'), redirect);
  1217. }
  1218. }
  1219. };
  1220. /**
  1221. * Adds the listener for automatically saving the diagram for local changes.
  1222. */
  1223. DrawioFile.prototype.handleFileSuccess = function(saved)
  1224. {
  1225. this.ui.spinner.stop();
  1226. if (this.ui.getCurrentFile() == this)
  1227. {
  1228. if (this.isModified())
  1229. {
  1230. this.fileChanged();
  1231. }
  1232. else if (saved)
  1233. {
  1234. this.addAllSavedStatus();
  1235. if (this.sync != null)
  1236. {
  1237. this.sync.resetUpdateStatusThread();
  1238. if (this.sync.remoteFileChanged)
  1239. {
  1240. this.sync.remoteFileChanged = false;
  1241. this.sync.fileChangedNotify();
  1242. }
  1243. }
  1244. }
  1245. else
  1246. {
  1247. this.ui.editor.setStatus('');
  1248. }
  1249. }
  1250. };
  1251. /**
  1252. * Adds the listener for automatically saving the diagram for local changes.
  1253. */
  1254. DrawioFile.prototype.handleFileError = function(err, manual)
  1255. {
  1256. this.ui.spinner.stop();
  1257. if (this.ui.getCurrentFile() == this)
  1258. {
  1259. if (this.inConflictState)
  1260. {
  1261. this.handleConflictError(err, manual);
  1262. }
  1263. else
  1264. {
  1265. if (this.isModified())
  1266. {
  1267. this.addUnsavedStatus(err);
  1268. }
  1269. if (manual)
  1270. {
  1271. this.ui.handleError(err, (err != null) ? mxResources.get('errorSavingFile') : null);
  1272. }
  1273. else if (!this.isModified())
  1274. {
  1275. var msg = (err != null) ? ((err.error != null) ? err.error.message : err.message) : null;
  1276. if (msg != null && msg.length > 60)
  1277. {
  1278. msg = msg.substring(0, 60) + '...';
  1279. }
  1280. this.ui.editor.setStatus('<div class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
  1281. mxUtils.htmlEntities(mxResources.get('error')) +
  1282. ((msg != null) ? ' (' + mxUtils.htmlEntities(msg) + ')' : '') + '</div>');
  1283. }
  1284. }
  1285. }
  1286. };
  1287. /**
  1288. * Adds the listener for automatically saving the diagram for local changes.
  1289. */
  1290. DrawioFile.prototype.handleConflictError = function(err, manual)
  1291. {
  1292. var success = mxUtils.bind(this, function()
  1293. {
  1294. this.handleFileSuccess(true);
  1295. });
  1296. var error = mxUtils.bind(this, function(err2)
  1297. {
  1298. this.handleFileError(err2, true);
  1299. });
  1300. var overwrite = mxUtils.bind(this, function()
  1301. {
  1302. if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
  1303. {
  1304. this.ui.editor.setStatus('');
  1305. this.save(true, success, error, null, true, (this.constructor ==
  1306. GitHubFile && err != null) ? err.commitMessage : null)
  1307. }
  1308. });
  1309. var synchronize = mxUtils.bind(this, function()
  1310. {
  1311. if (this.ui.spinner.spin(document.body, mxResources.get('updatingDocument')))
  1312. {
  1313. this.synchronizeFile(mxUtils.bind(this, function()
  1314. {
  1315. this.ui.spinner.stop();
  1316. if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
  1317. {
  1318. this.save(true, success, error, null, null, (this.constructor ==
  1319. GitHubFile && err != null) ? err.commitMessage : null)
  1320. }
  1321. }), error);
  1322. }
  1323. })
  1324. if (DrawioFile.SYNC == 'none')
  1325. {
  1326. this.showCopyDialog(success, error, overwrite);
  1327. }
  1328. else if (this.invalidChecksum)
  1329. {
  1330. this.showRefreshDialog(success, error);
  1331. }
  1332. else if (manual)
  1333. {
  1334. this.showConflictDialog(overwrite, synchronize);
  1335. }
  1336. else
  1337. {
  1338. this.addConflictStatus(mxUtils.bind(this, function()
  1339. {
  1340. this.ui.editor.setStatus(mxUtils.htmlEntities(
  1341. mxResources.get('updatingDocument')));
  1342. this.synchronizeFile(success, error);
  1343. }), this.getErrorMessage(err));
  1344. }
  1345. };
  1346. /**
  1347. * Adds the listener for automatically saving the diagram for local changes.
  1348. */
  1349. DrawioFile.prototype.getErrorMessage = function(err)
  1350. {
  1351. return (err != null) ? ((err.error != null) ? err.error.message : err.message) : null
  1352. };
  1353. /**
  1354. * Adds the listener for automatically saving the diagram for local changes.
  1355. */
  1356. DrawioFile.prototype.fileChanged = function()
  1357. {
  1358. this.setModified(true);
  1359. if (this.isAutosave())
  1360. {
  1361. this.addAllSavedStatus(mxUtils.htmlEntities(mxResources.get('saving')) + '...');
  1362. this.autosave(this.autosaveDelay, this.maxAutosaveDelay, mxUtils.bind(this, function(resp)
  1363. {
  1364. // Does not update status if another autosave was scheduled
  1365. if (this.autosaveThread == null)
  1366. {
  1367. this.handleFileSuccess(true);
  1368. }
  1369. }), mxUtils.bind(this, function(err)
  1370. {
  1371. this.handleFileError(err);
  1372. }));
  1373. }
  1374. else if ((!this.isAutosaveOptional() || !this.ui.editor.autosave) &&
  1375. !this.inConflictState)
  1376. {
  1377. this.addUnsavedStatus();
  1378. }
  1379. };
  1380. /**
  1381. * Invokes sync and updates shadow document.
  1382. */
  1383. DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error)
  1384. {
  1385. try
  1386. {
  1387. this.inConflictState = false;
  1388. this.invalidChecksum = false;
  1389. this.checkPages();
  1390. if (this.sync == null)
  1391. {
  1392. this.shadowData = savedData;
  1393. this.shadowPages = null;
  1394. if (success != null)
  1395. {
  1396. success();
  1397. }
  1398. }
  1399. else
  1400. {
  1401. this.sync.fileSaved(this.ui.getPagesForNode(
  1402. mxUtils.parseXml(savedData).documentElement),
  1403. lastDesc, success, error);
  1404. }
  1405. }
  1406. catch (e)
  1407. {
  1408. this.inConflictState = true;
  1409. this.invalidChecksum = true;
  1410. if (error != null)
  1411. {
  1412. error(e);
  1413. }
  1414. try
  1415. {
  1416. this.sendErrorReport('Error in fileSaved', null, e);
  1417. }
  1418. catch (e2)
  1419. {
  1420. // ignore
  1421. }
  1422. }
  1423. };
  1424. /**
  1425. * Adds the listener for automatically saving the diagram for local changes.
  1426. */
  1427. DrawioFile.prototype.autosave = function(delay, maxDelay, success, error)
  1428. {
  1429. if (this.lastAutosave == null)
  1430. {
  1431. this.lastAutosave = new Date().getTime();
  1432. }
  1433. var tmp = (new Date().getTime() - this.lastAutosave < maxDelay) ? delay : 0;
  1434. this.clearAutosave();
  1435. // Starts new timer or executes immediately if not unsaved for maxDelay
  1436. var thread = window.setTimeout(mxUtils.bind(this, function()
  1437. {
  1438. this.lastAutosave = null;
  1439. if (this.autosaveThread == thread)
  1440. {
  1441. this.autosaveThread = null;
  1442. }
  1443. // Workaround for duplicate save if UI is blocking
  1444. // after save while pending autosave triggers
  1445. if (this.isModified() && this.isAutosaveNow())
  1446. {
  1447. var rev = this.isAutosaveRevision();
  1448. if (rev)
  1449. {
  1450. this.lastAutosaveRevision = new Date().getTime();
  1451. }
  1452. this.save(rev, mxUtils.bind(this, function(resp)
  1453. {
  1454. this.autosaveCompleted();
  1455. if (success != null)
  1456. {
  1457. success(resp);
  1458. }
  1459. }), mxUtils.bind(this, function(resp)
  1460. {
  1461. if (error != null)
  1462. {
  1463. error(resp);
  1464. }
  1465. }));
  1466. }
  1467. else
  1468. {
  1469. if (!this.isModified())
  1470. {
  1471. this.ui.editor.setStatus('');
  1472. }
  1473. if (success != null)
  1474. {
  1475. success(null);
  1476. }
  1477. }
  1478. }), tmp);
  1479. this.autosaveThread = thread;
  1480. };
  1481. /**
  1482. * Returns true if an autosave is required at the time of execution.
  1483. * This implementation returns true.
  1484. */
  1485. DrawioFile.prototype.isAutosaveNow = function()
  1486. {
  1487. return true;
  1488. };
  1489. /**
  1490. * Hooks for subclassers after the autosave has completed.
  1491. */
  1492. DrawioFile.prototype.autosaveCompleted = function() { };
  1493. /**
  1494. * Adds the listener for automatically saving the diagram for local changes.
  1495. */
  1496. DrawioFile.prototype.clearAutosave = function()
  1497. {
  1498. if (this.autosaveThread != null)
  1499. {
  1500. window.clearTimeout(this.autosaveThread);
  1501. this.autosaveThread = null;
  1502. }
  1503. };
  1504. /**
  1505. * Returns the location as a new object.
  1506. * @type mx.Point
  1507. */
  1508. DrawioFile.prototype.isAutosaveRevision = function()
  1509. {
  1510. var now = new Date().getTime();
  1511. return (this.lastAutosaveRevision == null) || (now - this.lastAutosaveRevision) > this.maxAutosaveRevisionDelay;
  1512. };
  1513. /**
  1514. * Translates this point by the given vector.
  1515. *
  1516. * @param {number} dx X-coordinate of the translation.
  1517. * @param {number} dy Y-coordinate of the translation.
  1518. */
  1519. DrawioFile.prototype.descriptorChanged = function()
  1520. {
  1521. this.fireEvent(new mxEventObject('descriptorChanged'));
  1522. };
  1523. /**
  1524. * Translates this point by the given vector.
  1525. *
  1526. * @param {number} dx X-coordinate of the translation.
  1527. * @param {number} dy Y-coordinate of the translation.
  1528. */
  1529. DrawioFile.prototype.contentChanged = function()
  1530. {
  1531. this.fireEvent(new mxEventObject('contentChanged'));
  1532. };
  1533. /**
  1534. * Returns the location as a new object.
  1535. */
  1536. DrawioFile.prototype.close = function(unloading)
  1537. {
  1538. if (this.isAutosave() && this.isModified())
  1539. {
  1540. this.save(this.isAutosaveRevision(), null, null, unloading);
  1541. }
  1542. this.destroy();
  1543. };
  1544. /**
  1545. * Returns the location as a new object.
  1546. */
  1547. DrawioFile.prototype.hasSameExtension = function(title, newTitle)
  1548. {
  1549. if (title != null && newTitle != null)
  1550. {
  1551. var dot = title.lastIndexOf('.');
  1552. var ext = (dot > 0) ? title.substring(dot) : '';
  1553. dot = newTitle.lastIndexOf('.');
  1554. return ext === ((dot > 0) ? newTitle.substring(dot) : '');
  1555. }
  1556. return title == newTitle;
  1557. };
  1558. /**
  1559. * Removes the change listener.
  1560. */
  1561. DrawioFile.prototype.removeListeners = function()
  1562. {
  1563. if (this.changeListener != null)
  1564. {
  1565. this.ui.editor.graph.model.removeListener(this.changeListener);
  1566. this.ui.editor.graph.removeListener(this.changeListener);
  1567. this.ui.removeListener(this.changeListener);
  1568. this.changeListener = null;
  1569. }
  1570. };
  1571. /**
  1572. * Stops any pending autosaves and removes all listeners.
  1573. */
  1574. DrawioFile.prototype.destroy = function()
  1575. {
  1576. try
  1577. {
  1578. if (!this.ui.isOffline() && this.reportEnabled &&
  1579. (DrawioFile.SYNC == 'auto' ||
  1580. DrawioFile.SYNC == 'manual'))
  1581. {
  1582. var user = this.getCurrentUser();
  1583. var uid = (user != null) ? this.ui.hashValue(user.id) : 'unknown';
  1584. this.stats.end = new Date().toISOString();
  1585. if (this.stats.start != null)
  1586. {
  1587. this.stats.uptime = Math.round((new Date().getTime() -
  1588. new Date(this.stats.start).getTime()) / 1000);
  1589. }
  1590. EditorUi.logEvent({category: 'RT-END-' + DrawioFile.SYNC,
  1591. action: 'file-' + this.getId() +
  1592. '-mode-' + this.getMode() +
  1593. '-size-' + this.getSize() +
  1594. '-user-' + uid +
  1595. ((this.sync != null) ? ('-client-' + this.sync.clientId ) : ''),
  1596. label: this.stats});
  1597. }
  1598. }
  1599. catch (e)
  1600. {
  1601. // ignore
  1602. }
  1603. this.clearAutosave();
  1604. this.removeListeners();
  1605. if (this.sync != null)
  1606. {
  1607. this.sync.destroy();
  1608. this.sync = null;
  1609. }
  1610. };