DrawioFile.js 46 KB

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