DrawioFile.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095
  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. this.ui.addListener('connectionPointsChanged', this.changeListener);
  1091. this.ui.addListener('connectionArrowsChanged', this.changeListener);
  1092. }
  1093. };
  1094. /**
  1095. * Returns the location as a new object.
  1096. * @type mx.Point
  1097. */
  1098. DrawioFile.prototype.addAllSavedStatus = function(status)
  1099. {
  1100. if (this.ui.statusContainer != null && this.ui.getCurrentFile() == this)
  1101. {
  1102. status = (status != null) ? status : mxUtils.htmlEntities(mxResources.get(this.allChangesSavedKey));
  1103. this.ui.editor.setStatus('<div title="'+ status + '">' + status + '</div>');
  1104. var links = this.ui.statusContainer.getElementsByTagName('div');
  1105. if (links.length > 0 && this.isRevisionHistorySupported())
  1106. {
  1107. links[0].style.cursor = 'pointer';
  1108. links[0].style.textDecoration = 'underline';
  1109. mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
  1110. {
  1111. this.ui.actions.get('revisionHistory').funct();
  1112. }));
  1113. }
  1114. }
  1115. };
  1116. /**
  1117. * Adds the listener for automatically saving the diagram for local changes.
  1118. */
  1119. DrawioFile.prototype.addUnsavedStatus = function(err)
  1120. {
  1121. if (!this.inConflictState && this.ui.statusContainer != null && this.ui.getCurrentFile() == this)
  1122. {
  1123. if (err instanceof Error && err.message != null && err.message != '')
  1124. {
  1125. var status = mxUtils.htmlEntities(mxResources.get('unsavedChanges'));
  1126. this.ui.editor.setStatus('<div title="'+ status +
  1127. '" class="geStatusAlert" style="overflow:hidden;">' + status +
  1128. ' (' + mxUtils.htmlEntities(err.message) + ')</div>');
  1129. }
  1130. else
  1131. {
  1132. // FIXME: Handle multiple tabs
  1133. // if (this.ui.mode == null && urlParams['splash'] == '0')
  1134. // {
  1135. // try
  1136. // {
  1137. // this.ui.updateDraft();
  1138. // this.setModified(false);
  1139. // }
  1140. // catch (e)
  1141. // {
  1142. // // Keeps modified flag unchanged
  1143. // }
  1144. // }
  1145. var msg = this.getErrorMessage(err);
  1146. if (msg == null && this.lastSaved != null)
  1147. {
  1148. var str = this.ui.timeSince(new Date(this.lastSaved));
  1149. // Only show if more than a minute ago
  1150. if (str != null)
  1151. {
  1152. msg = mxResources.get('lastSaved', [str]);
  1153. }
  1154. }
  1155. if (msg != null && msg.length > 60)
  1156. {
  1157. msg = msg.substring(0, 60) + '...';
  1158. }
  1159. var status = mxUtils.htmlEntities(mxResources.get('unsavedChangesClickHereToSave')) +
  1160. ((msg != null && msg != '') ? ' (' + mxUtils.htmlEntities(msg) + ')' : '');
  1161. this.ui.editor.setStatus('<div title="'+ status +
  1162. '" class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
  1163. status + '</div>');
  1164. // Installs click handler for saving
  1165. var links = this.ui.statusContainer.getElementsByTagName('div');
  1166. if (links != null && links.length > 0)
  1167. {
  1168. mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
  1169. {
  1170. this.ui.actions.get((this.ui.mode == null || !this.isEditable()) ?
  1171. 'saveAs' : 'save').funct();
  1172. }));
  1173. }
  1174. else
  1175. {
  1176. var status = mxUtils.htmlEntities(mxResources.get('unsavedChanges'));
  1177. this.ui.editor.setStatus('<div title="'+ status +
  1178. '" class="geStatusAlert" style="overflow:hidden;">' + status +
  1179. ' (' + mxUtils.htmlEntities(err.message) + ')</div>');
  1180. }
  1181. }
  1182. }
  1183. };
  1184. /**
  1185. * Halts all timers and shows a conflict status message. The optional error
  1186. * handler is invoked first.
  1187. */
  1188. DrawioFile.prototype.addConflictStatus = function(fn, message)
  1189. {
  1190. if (this.invalidChecksum && message == null)
  1191. {
  1192. message = mxResources.get('checksum');
  1193. }
  1194. this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) +
  1195. ((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : ''));
  1196. this.ui.spinner.stop();
  1197. this.clearAutosave();
  1198. var links = (this.ui.statusContainer != null) ? this.ui.statusContainer.getElementsByTagName('div') : null;
  1199. if (links != null && links.length > 0)
  1200. {
  1201. mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function(evt)
  1202. {
  1203. if (mxEvent.getSource(evt).nodeName != 'IMG')
  1204. {
  1205. fn();
  1206. }
  1207. }));
  1208. }
  1209. else
  1210. {
  1211. this.ui.alert(mxUtils.htmlEntities(mxResources.get('fileChangedSync')), fn);
  1212. }
  1213. };
  1214. /**
  1215. * Halts all timers and shows a conflict status message. The optional error
  1216. * handler is invoked first.
  1217. */
  1218. DrawioFile.prototype.setConflictStatus = function(message)
  1219. {
  1220. this.ui.editor.setStatus('<div title="'+ message + '" class="geStatusAlert geBlink" style="cursor:pointer;overflow:hidden;">' +
  1221. message + ' <a href="https://desk.draw.io/support/solutions/articles/16000087947" target="_blank"><img border="0" ' +
  1222. 'style="margin-left:2px;cursor:help;opacity:0.5;width:16px;height:16px;" valign="bottom" src="' + Editor.helpImage +
  1223. '" style=""/></a></div>');
  1224. };
  1225. /**
  1226. * Shows a conflict dialog to the user.
  1227. */
  1228. DrawioFile.prototype.showRefreshDialog = function(success, error, message)
  1229. {
  1230. if (message == null)
  1231. {
  1232. message = mxResources.get('checksum');
  1233. }
  1234. if (this.ui.editor.isChromelessView() && !this.ui.editor.editable)
  1235. {
  1236. this.ui.alert(mxResources.get('fileChangedSync'), mxUtils.bind(this, function()
  1237. {
  1238. this.reloadFile(success, error);
  1239. }));
  1240. }
  1241. else
  1242. {
  1243. // Allows for escape key to be pressed while dialog is showing
  1244. this.addConflictStatus(mxUtils.bind(this, function()
  1245. {
  1246. this.showRefreshDialog(success, error);
  1247. }), message);
  1248. this.ui.showError(mxResources.get('error') + ' (' + message + ')',
  1249. mxResources.get('fileChangedSyncDialog'),
  1250. mxResources.get('makeCopy'), mxUtils.bind(this, function()
  1251. {
  1252. this.copyFile(success, error);
  1253. }), null, mxResources.get('synchronize'), mxUtils.bind(this, function()
  1254. {
  1255. this.reloadFile(success, error);
  1256. }), mxResources.get('cancel'), mxUtils.bind(this, function()
  1257. {
  1258. this.ui.hideDialog();
  1259. }), 360, 150);
  1260. }
  1261. };
  1262. /**
  1263. * Shows a dialog with no synchronize option.
  1264. */
  1265. DrawioFile.prototype.showCopyDialog = function(success, error, overwrite)
  1266. {
  1267. this.inConflictState = false;
  1268. this.invalidChecksum = false;
  1269. this.addUnsavedStatus();
  1270. this.ui.showError(mxResources.get('externalChanges'),
  1271. mxResources.get('fileChangedOverwriteDialog'),
  1272. mxResources.get('makeCopy'), mxUtils.bind(this, function()
  1273. {
  1274. this.copyFile(success, error);
  1275. }), null, mxResources.get('overwrite'), overwrite,
  1276. mxResources.get('cancel'), mxUtils.bind(this, function()
  1277. {
  1278. this.ui.hideDialog();
  1279. }), 360, 150);
  1280. };
  1281. /**
  1282. * Shows a conflict dialog to the user.
  1283. */
  1284. DrawioFile.prototype.showConflictDialog = function(overwrite, synchronize)
  1285. {
  1286. this.ui.showError(mxResources.get('externalChanges'),
  1287. mxResources.get('fileChangedSyncDialog'),
  1288. mxResources.get('overwrite'), overwrite, null,
  1289. mxResources.get('synchronize'), synchronize,
  1290. mxResources.get('cancel'), mxUtils.bind(this, function()
  1291. {
  1292. this.ui.hideDialog();
  1293. this.handleFileError(null, false);
  1294. }), 340, 150);
  1295. };
  1296. /**
  1297. * Checks if the client is authorized and calls the next step.
  1298. */
  1299. DrawioFile.prototype.redirectToNewApp = function(error)
  1300. {
  1301. this.ui.spinner.stop();
  1302. if (!this.redirectDialogShowing)
  1303. {
  1304. this.redirectDialogShowing = true;
  1305. var url = window.location.protocol + '//' + window.location.host + '/' + this.ui.getSearch(
  1306. ['create', 'title', 'mode', 'url', 'drive', 'splash', 'state']) + '#' + this.getHash();
  1307. var redirect = mxUtils.bind(this, function()
  1308. {
  1309. var fn = mxUtils.bind(this, function()
  1310. {
  1311. this.redirectDialogShowing = false;
  1312. if (window.location.href == url)
  1313. {
  1314. window.location.reload();
  1315. }
  1316. else
  1317. {
  1318. window.location.href = url;
  1319. }
  1320. });
  1321. if (error == null && this.isModified())
  1322. {
  1323. this.ui.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function()
  1324. {
  1325. this.redirectDialogShowing = false;
  1326. }), fn, mxResources.get('cancel'), mxResources.get('discardChanges'));
  1327. }
  1328. else
  1329. {
  1330. fn();
  1331. }
  1332. });
  1333. if (error != null)
  1334. {
  1335. if (this.isModified())
  1336. {
  1337. this.ui.confirm(mxResources.get('redirectToNewApp'), mxUtils.bind(this, function()
  1338. {
  1339. this.redirectDialogShowing = false;
  1340. error();
  1341. }), redirect, mxResources.get('cancel'), mxResources.get('discardChanges'));
  1342. }
  1343. else
  1344. {
  1345. this.ui.confirm(mxResources.get('redirectToNewApp'), redirect, mxUtils.bind(this, function()
  1346. {
  1347. this.redirectDialogShowing = false;
  1348. error();
  1349. }));
  1350. }
  1351. }
  1352. else
  1353. {
  1354. this.ui.alert(mxResources.get('redirectToNewApp'), redirect);
  1355. }
  1356. }
  1357. };
  1358. /**
  1359. * Adds the listener for automatically saving the diagram for local changes.
  1360. */
  1361. DrawioFile.prototype.handleFileSuccess = function(saved)
  1362. {
  1363. this.ui.spinner.stop();
  1364. if (this.ui.getCurrentFile() == this)
  1365. {
  1366. if (this.isModified())
  1367. {
  1368. this.fileChanged();
  1369. }
  1370. else if (saved)
  1371. {
  1372. this.addAllSavedStatus();
  1373. if (this.sync != null)
  1374. {
  1375. this.sync.resetUpdateStatusThread();
  1376. if (this.sync.remoteFileChanged)
  1377. {
  1378. this.sync.remoteFileChanged = false;
  1379. this.sync.fileChangedNotify();
  1380. }
  1381. }
  1382. }
  1383. else
  1384. {
  1385. this.ui.editor.setStatus('');
  1386. }
  1387. }
  1388. };
  1389. /**
  1390. * Adds the listener for automatically saving the diagram for local changes.
  1391. */
  1392. DrawioFile.prototype.handleFileError = function(err, manual)
  1393. {
  1394. this.ui.spinner.stop();
  1395. if (this.ui.getCurrentFile() == this)
  1396. {
  1397. if (this.inConflictState)
  1398. {
  1399. this.handleConflictError(err, manual);
  1400. }
  1401. else
  1402. {
  1403. if (this.isModified())
  1404. {
  1405. this.addUnsavedStatus(err);
  1406. }
  1407. if (manual)
  1408. {
  1409. this.ui.handleError(err, (err != null) ? mxResources.get('errorSavingFile') : null);
  1410. }
  1411. else if (!this.isModified())
  1412. {
  1413. var msg = (err != null) ? ((err.error != null) ? err.error.message : err.message) : null;
  1414. if (msg != null && msg.length > 60)
  1415. {
  1416. msg = msg.substring(0, 60) + '...';
  1417. }
  1418. this.ui.editor.setStatus('<div class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
  1419. mxUtils.htmlEntities(mxResources.get('error')) +
  1420. ((msg != null) ? ' (' + mxUtils.htmlEntities(msg) + ')' : '') + '</div>');
  1421. }
  1422. else if (this.isModified() && !manual && this.isAutosave())
  1423. {
  1424. if (this.lastWarned == null)
  1425. {
  1426. this.lastWarned = Date.now();
  1427. }
  1428. else if (Date.now() - this.lastWarned > this.warnInterval)
  1429. {
  1430. var msg = '';
  1431. if (this.lastSaved != null)
  1432. {
  1433. var str = this.ui.timeSince(new Date(this.lastSaved));
  1434. // Only show if more than a minute ago
  1435. if (str != null)
  1436. {
  1437. msg = mxResources.get('lastSaved', [str]);
  1438. }
  1439. }
  1440. this.ui.showError(mxResources.get('unsavedChanges'), msg, mxResources.get('ignore'),
  1441. mxUtils.bind(this, function()
  1442. {
  1443. this.lastWarned = Date.now();
  1444. this.ui.hideDialog();
  1445. EditorUi.logEvent({category: 'IGNORE-WARN-SAVE-FILE-' + this.getHash() +
  1446. '-size-' + this.getSize(), action: 'ignore'});
  1447. }), null, mxResources.get('save'), mxUtils.bind(this, function()
  1448. {
  1449. this.lastWarned = Date.now();
  1450. this.ui.actions.get((this.ui.mode == null || !this.isEditable()) ?
  1451. 'saveAs' : 'save').funct();
  1452. EditorUi.logEvent({category: 'SAVE-WARN-SAVE-FILE-' + this.getHash() +
  1453. '-size-' + this.getSize(), action: 'save'});
  1454. }), null, null, 360, 120);
  1455. }
  1456. }
  1457. }
  1458. }
  1459. };
  1460. /**
  1461. * Adds the listener for automatically saving the diagram for local changes.
  1462. */
  1463. DrawioFile.prototype.handleConflictError = function(err, manual)
  1464. {
  1465. var success = mxUtils.bind(this, function()
  1466. {
  1467. this.handleFileSuccess(true);
  1468. });
  1469. var error = mxUtils.bind(this, function(err2)
  1470. {
  1471. this.handleFileError(err2, true);
  1472. });
  1473. var overwrite = mxUtils.bind(this, function()
  1474. {
  1475. if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
  1476. {
  1477. this.ui.editor.setStatus('');
  1478. this.save(true, success, error, null, true, (this.constructor ==
  1479. GitHubFile && err != null) ? err.commitMessage : null)
  1480. }
  1481. });
  1482. var synchronize = mxUtils.bind(this, function()
  1483. {
  1484. if (this.ui.spinner.spin(document.body, mxResources.get('updatingDocument')))
  1485. {
  1486. this.synchronizeFile(mxUtils.bind(this, function()
  1487. {
  1488. this.ui.spinner.stop();
  1489. if (this.ui.spinner.spin(document.body, mxResources.get('saving')))
  1490. {
  1491. this.save(true, success, error, null, null, (this.constructor ==
  1492. GitHubFile && err != null) ? err.commitMessage : null)
  1493. }
  1494. }), error);
  1495. }
  1496. })
  1497. if (DrawioFile.SYNC == 'none')
  1498. {
  1499. this.showCopyDialog(success, error, overwrite);
  1500. }
  1501. else if (this.invalidChecksum)
  1502. {
  1503. this.showRefreshDialog(success, error, this.getErrorMessage(err));
  1504. }
  1505. else if (manual)
  1506. {
  1507. this.showConflictDialog(overwrite, synchronize);
  1508. }
  1509. else
  1510. {
  1511. this.addConflictStatus(mxUtils.bind(this, function()
  1512. {
  1513. this.ui.editor.setStatus(mxUtils.htmlEntities(
  1514. mxResources.get('updatingDocument')));
  1515. this.synchronizeFile(success, error);
  1516. }), this.getErrorMessage(err));
  1517. }
  1518. };
  1519. /**
  1520. * Adds the listener for automatically saving the diagram for local changes.
  1521. */
  1522. DrawioFile.prototype.getErrorMessage = function(err)
  1523. {
  1524. return (err != null) ? ((err.error != null) ? err.error.message : err.message) : null
  1525. };
  1526. /**
  1527. * Adds the listener for automatically saving the diagram for local changes.
  1528. */
  1529. DrawioFile.prototype.fileChanged = function()
  1530. {
  1531. this.setModified(true);
  1532. if (this.isAutosave())
  1533. {
  1534. this.addAllSavedStatus(mxUtils.htmlEntities(mxResources.get('saving')) + '...');
  1535. this.autosave(this.autosaveDelay, this.maxAutosaveDelay, mxUtils.bind(this, function(resp)
  1536. {
  1537. // Does not update status if another autosave was scheduled
  1538. if (this.autosaveThread == null)
  1539. {
  1540. this.handleFileSuccess(true);
  1541. }
  1542. }), mxUtils.bind(this, function(err)
  1543. {
  1544. this.handleFileError(err);
  1545. }));
  1546. }
  1547. else if ((!this.isAutosaveOptional() || !this.ui.editor.autosave) &&
  1548. !this.inConflictState)
  1549. {
  1550. this.addUnsavedStatus();
  1551. }
  1552. };
  1553. /**
  1554. * Invokes sync and updates shadow document.
  1555. */
  1556. DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error)
  1557. {
  1558. this.lastSaved = new Date();
  1559. try
  1560. {
  1561. this.stats.saved++;
  1562. this.inConflictState = false;
  1563. this.invalidChecksum = false;
  1564. if (this.sync == null)
  1565. {
  1566. this.shadowData = savedData;
  1567. this.shadowPages = null;
  1568. if (success != null)
  1569. {
  1570. success();
  1571. }
  1572. }
  1573. else
  1574. {
  1575. this.sync.fileSaved(this.ui.getPagesForNode(
  1576. mxUtils.parseXml(savedData).documentElement),
  1577. lastDesc, success, error, savedData);
  1578. }
  1579. }
  1580. catch (e)
  1581. {
  1582. this.inConflictState = true;
  1583. this.invalidChecksum = true;
  1584. this.descriptorChanged();
  1585. if (error != null)
  1586. {
  1587. error(e);
  1588. }
  1589. try
  1590. {
  1591. if (this.errorReportsEnabled)
  1592. {
  1593. this.sendErrorReport('Error in fileSaved', null, e);
  1594. }
  1595. else
  1596. {
  1597. var user = this.getCurrentUser();
  1598. var uid = (user != null) ? user.id : 'unknown';
  1599. EditorUi.logError('Error in fileSaved', null,
  1600. this.getMode() + '.' + this.getId(),
  1601. uid, e);
  1602. }
  1603. }
  1604. catch (e2)
  1605. {
  1606. // ignore
  1607. }
  1608. }
  1609. };
  1610. /**
  1611. * Adds the listener for automatically saving the diagram for local changes.
  1612. */
  1613. DrawioFile.prototype.autosave = function(delay, maxDelay, success, error)
  1614. {
  1615. if (this.lastAutosave == null)
  1616. {
  1617. this.lastAutosave = new Date().getTime();
  1618. }
  1619. var tmp = (new Date().getTime() - this.lastAutosave < maxDelay) ? delay : 0;
  1620. this.clearAutosave();
  1621. // Starts new timer or executes immediately if not unsaved for maxDelay
  1622. var thread = window.setTimeout(mxUtils.bind(this, function()
  1623. {
  1624. this.lastAutosave = null;
  1625. if (this.autosaveThread == thread)
  1626. {
  1627. this.autosaveThread = null;
  1628. }
  1629. // Workaround for duplicate save if UI is blocking
  1630. // after save while pending autosave triggers
  1631. if (this.isModified() && this.isAutosaveNow())
  1632. {
  1633. var rev = this.isAutosaveRevision();
  1634. if (rev)
  1635. {
  1636. this.lastAutosaveRevision = new Date().getTime();
  1637. }
  1638. this.save(rev, mxUtils.bind(this, function(resp)
  1639. {
  1640. this.autosaveCompleted();
  1641. if (success != null)
  1642. {
  1643. success(resp);
  1644. }
  1645. }), mxUtils.bind(this, function(resp)
  1646. {
  1647. if (error != null)
  1648. {
  1649. error(resp);
  1650. }
  1651. }));
  1652. }
  1653. else
  1654. {
  1655. if (!this.isModified())
  1656. {
  1657. this.ui.editor.setStatus('');
  1658. }
  1659. if (success != null)
  1660. {
  1661. success(null);
  1662. }
  1663. }
  1664. }), tmp);
  1665. this.autosaveThread = thread;
  1666. };
  1667. /**
  1668. * Returns true if an autosave is required at the time of execution.
  1669. * This implementation returns true.
  1670. */
  1671. DrawioFile.prototype.isAutosaveNow = function()
  1672. {
  1673. return true;
  1674. };
  1675. /**
  1676. * Hooks for subclassers after the autosave has completed.
  1677. */
  1678. DrawioFile.prototype.autosaveCompleted = function() { };
  1679. /**
  1680. * Adds the listener for automatically saving the diagram for local changes.
  1681. */
  1682. DrawioFile.prototype.clearAutosave = function()
  1683. {
  1684. if (this.autosaveThread != null)
  1685. {
  1686. window.clearTimeout(this.autosaveThread);
  1687. this.autosaveThread = null;
  1688. }
  1689. };
  1690. /**
  1691. * Returns the location as a new object.
  1692. * @type mx.Point
  1693. */
  1694. DrawioFile.prototype.isAutosaveRevision = function()
  1695. {
  1696. var now = new Date().getTime();
  1697. return (this.lastAutosaveRevision == null) || (now - this.lastAutosaveRevision) > this.maxAutosaveRevisionDelay;
  1698. };
  1699. /**
  1700. * Translates this point by the given vector.
  1701. *
  1702. * @param {number} dx X-coordinate of the translation.
  1703. * @param {number} dy Y-coordinate of the translation.
  1704. */
  1705. DrawioFile.prototype.descriptorChanged = function()
  1706. {
  1707. this.fireEvent(new mxEventObject('descriptorChanged'));
  1708. };
  1709. /**
  1710. * Translates this point by the given vector.
  1711. *
  1712. * @param {number} dx X-coordinate of the translation.
  1713. * @param {number} dy Y-coordinate of the translation.
  1714. */
  1715. DrawioFile.prototype.contentChanged = function()
  1716. {
  1717. this.fireEvent(new mxEventObject('contentChanged'));
  1718. };
  1719. /**
  1720. * Returns the location as a new object.
  1721. */
  1722. DrawioFile.prototype.close = function(unloading)
  1723. {
  1724. this.updateFileData();
  1725. this.stats.closed++;
  1726. if (this.isAutosave() && this.isModified())
  1727. {
  1728. this.save(this.isAutosaveRevision(), null, null, unloading);
  1729. }
  1730. this.destroy();
  1731. };
  1732. /**
  1733. * Returns the location as a new object.
  1734. */
  1735. DrawioFile.prototype.hasSameExtension = function(title, newTitle)
  1736. {
  1737. if (title != null && newTitle != null)
  1738. {
  1739. var dot = title.lastIndexOf('.');
  1740. var ext = (dot > 0) ? title.substring(dot) : '';
  1741. dot = newTitle.lastIndexOf('.');
  1742. return ext === ((dot > 0) ? newTitle.substring(dot) : '');
  1743. }
  1744. return title == newTitle;
  1745. };
  1746. /**
  1747. * Removes the change listener.
  1748. */
  1749. DrawioFile.prototype.removeListeners = function()
  1750. {
  1751. if (this.changeListener != null)
  1752. {
  1753. this.ui.editor.graph.model.removeListener(this.changeListener);
  1754. this.ui.editor.graph.removeListener(this.changeListener);
  1755. this.ui.removeListener(this.changeListener);
  1756. this.changeListener = null;
  1757. }
  1758. };
  1759. /**
  1760. * Stops any pending autosaves and removes all listeners.
  1761. */
  1762. DrawioFile.prototype.destroy = function()
  1763. {
  1764. this.stats.destroyed++;
  1765. try
  1766. {
  1767. if (!this.ui.isOffline() && this.reportEnabled &&
  1768. (DrawioFile.SYNC == 'auto' ||
  1769. DrawioFile.SYNC == 'manual'))
  1770. {
  1771. var user = this.getCurrentUser();
  1772. var uid = (user != null) ? user.id : 'unknown';
  1773. EditorUi.logEvent({category: DrawioFile.SYNC + '-DESTROY-FILE-' + DrawioFile.SYNC,
  1774. action: 'file-' + this.getId() +
  1775. '-mode-' + this.getMode() +
  1776. '-size-' + this.getSize() +
  1777. '-user-' + uid +
  1778. ((this.sync != null) ? ('-client-' + this.sync.clientId ) : ''),
  1779. label: this.stats});
  1780. }
  1781. }
  1782. catch (e)
  1783. {
  1784. // ignore
  1785. }
  1786. this.clearAutosave();
  1787. this.removeListeners();
  1788. if (this.sync != null)
  1789. {
  1790. this.sync.destroy();
  1791. this.sync = null;
  1792. }
  1793. };
  1794. /**
  1795. * Are comments supported
  1796. */
  1797. DrawioFile.prototype.commentsSupported = function()
  1798. {
  1799. return false; //The default is false and files that support it must explicitly state that
  1800. };
  1801. /**
  1802. * Show refresh button?
  1803. */
  1804. DrawioFile.prototype.commentsRefreshNeeded = function()
  1805. {
  1806. return true;
  1807. };
  1808. /**
  1809. * Show save button?
  1810. */
  1811. DrawioFile.prototype.commentsSaveNeeded = function()
  1812. {
  1813. return false;
  1814. };
  1815. /**
  1816. * Get comments of the file
  1817. */
  1818. DrawioFile.prototype.getComments = function(success, error)
  1819. {
  1820. success([]); //placeholder
  1821. };
  1822. /**
  1823. * Add a comment to the file
  1824. */
  1825. DrawioFile.prototype.addComment = function(comment, success, error)
  1826. {
  1827. success(Date.now()); //placeholder
  1828. };
  1829. /**
  1830. * Can add a reply to a reply
  1831. */
  1832. DrawioFile.prototype.canReplyToReplies = function()
  1833. {
  1834. return true;
  1835. };
  1836. /**
  1837. * Can add comments (The permission to comment to this file)
  1838. */
  1839. DrawioFile.prototype.canComment = function()
  1840. {
  1841. return true;
  1842. };
  1843. /**
  1844. * Get a new comment object
  1845. */
  1846. DrawioFile.prototype.newComment = function(content, user)
  1847. {
  1848. return new DrawioComment(this, null, content, Date.now(), Date.now(), false, user);
  1849. };