1/** 2 * Copyright (c) 2006-2017, JGraph Ltd 3 * Copyright (c) 2006-2017, Gaudenz Alder 4 */ 5DrawioFile = function(ui, data) 6{ 7 mxEventSource.call(this); 8 9 /** 10 * Holds the x-coordinate of the point. 11 * @type number 12 * @default 0 13 */ 14 this.ui = ui; 15 16 /** 17 * Holds the x-coordinate of the point. 18 * @type number 19 * @default 0 20 */ 21 this.data = data || ''; 22 this.shadowData = this.data; 23 this.shadowPages = null; 24 this.created = new Date().getTime(); 25 26 // Creates the stats object 27 this.stats = { 28 opened: 0, /* number of calls to open */ 29 merged: 0, /* number of calls to merge */ 30 fileMerged: 0, /* number of calls to mergeFile */ 31 fileReloaded: 0, /* number of calls to mergeFile */ 32 conflicts: 0, /* number of write conflicts when saving a file */ 33 timeouts: 0, /* number of time we have given up to retry after a write conflict */ 34 saved: 0, /* number of calls to fileSaved */ 35 closed: 0, /* number of calls to close */ 36 destroyed: 0, /* number of calls to close */ 37 joined: 0, /* number of join messages received */ 38 checksumErrors: 0, /* number of checksum errors */ 39 bytesSent: 0, /* number of bytes send in messages */ 40 bytesReceived: 0, /* number of bytes received in messages */ 41 msgSent: 0, /* number of messages sent */ 42 msgReceived: 0, /* number of messages received */ 43 cacheHits: 0, /* number of times the cache returned patches */ 44 cacheMiss: 0, /* number of times we have missed a cache entry */ 45 cacheFail: 0 /* number of times we have failed to read the cache */ 46 }; 47}; 48 49/** 50 * Global switch for realtime collaboration type to use sync URL parameter 51 * with the following possible values: 52 * 53 * - none: overwrite 54 * - manual: manual sync 55 * - auto: automatic sync 56 */ 57DrawioFile.SYNC = urlParams['sync'] || 'auto'; 58 59/** 60 * Specifies if last write wins should be used for values and styles. 61 */ 62DrawioFile.LAST_WRITE_WINS = true; 63 64// Extends mxEventSource 65mxUtils.extend(DrawioFile, mxEventSource); 66 67/** 68 * Specifies the resource key for all changes saved status message. 69 */ 70DrawioFile.prototype.allChangesSavedKey = 'allChangesSaved'; 71 72/** 73 * Specifies the resource key for saving spinner. 74 */ 75DrawioFile.prototype.savingSpinnerKey = 'saving'; 76 77/** 78 * Specifies the resource key for saving status message. 79 */ 80DrawioFile.prototype.savingStatusKey = 'saving'; 81 82/** 83 * Specifies the delay between the last change and the autosave. 84 */ 85DrawioFile.prototype.autosaveDelay = 1500; 86 87/** 88 * Specifies the maximum delay before an autosave is forced even if the graph 89 * is being changed. 90 */ 91DrawioFile.prototype.maxAutosaveDelay = 30000; 92 93/** 94 * Specifies the delay for loading the file after an optimistic sync message. 95 * This should be the delay for the file to be saved minus the delay for the 96 * sync message to travel. 97 */ 98DrawioFile.prototype.optimisticSyncDelay = 300; 99 100/** 101 * Contains the thread for the next autosave. 102 */ 103DrawioFile.prototype.autosaveThread = null; 104 105/** 106 * Stores the time stamp for the last autosave. 107 */ 108DrawioFile.prototype.lastAutosave = null; 109 110/** 111 * Stores the time stamp for the last autosave. 112 */ 113DrawioFile.prototype.lastSaved = null; 114 115/** 116 * Stores the time stamp for the last autosave. 117 */ 118DrawioFile.prototype.lastChanged = null; 119 120/** 121 * Stores the time stamp when the file was opened. 122 */ 123DrawioFile.prototype.opened = null; 124 125/** 126 * Stores the modified state. 127 */ 128DrawioFile.prototype.modified = false; 129 130/** 131 * Stores a shadow of the modified state. 132 */ 133DrawioFile.prototype.shadowModified = false; 134 135/** 136 * Holds a copy of the current file data. 137 */ 138DrawioFile.prototype.data = null; 139 140/** 141 * Holds a copy of the last saved file data. 142 */ 143DrawioFile.prototype.shadowData = null; 144 145/** 146 * Holds a copy of the parsed last saved file data. 147 */ 148DrawioFile.prototype.shadowPages = null; 149 150/** 151 * Specifies if the graph change listener is enabled. Default is true. 152 */ 153DrawioFile.prototype.changeListenerEnabled = true; 154 155/** 156 * Sets the delay for autosave in milliseconds. Default is 1500. 157 */ 158DrawioFile.prototype.lastAutosaveRevision = null; 159 160/** 161 * Sets the delay between revisions when using autosave. Default is 300000 162 * ie 5 mins. Set this to 0 to create a revision on every autosave. 163 */ 164DrawioFile.prototype.maxAutosaveRevisionDelay = 300000; 165 166/** 167 * Specifies if notify events should be ignored. 168 */ 169DrawioFile.prototype.inConflictState = false; 170 171/** 172 * Specifies if notify events should be ignored. 173 */ 174DrawioFile.prototype.invalidChecksum = false; 175 176/** 177 * Specifies if error reports should be sent. 178 */ 179DrawioFile.prototype.errorReportsEnabled = false; 180 181/** 182 * Specifies if stats should be sent. 183 */ 184DrawioFile.prototype.ageStart = null; 185 186/** 187 * Specifies if notify events should be ignored. 188 */ 189DrawioFile.prototype.getSize = function() 190{ 191 return (this.data != null) ? this.data.length : 0; 192}; 193 194/** 195 * Adds the listener for automatically saving the diagram for local changes. 196 */ 197DrawioFile.prototype.synchronizeFile = function(success, error) 198{ 199 if (this.savingFile) 200 { 201 if (error != null) 202 { 203 error({message: mxResources.get('busy')}); 204 } 205 } 206 else 207 { 208 if (this.sync != null) 209 { 210 this.sync.fileChanged(success, error); 211 } 212 else 213 { 214 this.updateFile(success, error); 215 } 216 } 217}; 218 219/** 220* Adds the listener for automatically saving the diagram for local changes. 221*/ 222DrawioFile.prototype.updateFile = function(success, error, abort, shadow) 223{ 224 if (abort == null || !abort()) 225 { 226 if (this.ui.getCurrentFile() != this || this.invalidChecksum) 227 { 228 if (error != null) 229 { 230 error(); 231 } 232 } 233 else 234 { 235 this.getLatestVersion(mxUtils.bind(this, function(latestFile) 236 { 237 try 238 { 239 if (abort == null || !abort()) 240 { 241 if (this.ui.getCurrentFile() != this || this.invalidChecksum) 242 { 243 if (error != null) 244 { 245 error(); 246 } 247 } 248 else 249 { 250 if (latestFile != null) 251 { 252 this.mergeFile(latestFile, success, error, shadow); 253 } 254 else 255 { 256 this.reloadFile(success, error); 257 } 258 } 259 } 260 } 261 catch (e) 262 { 263 if (error != null) 264 { 265 error(e); 266 } 267 } 268 }), error); 269 } 270 } 271}; 272 273/** 274 * Adds the listener for automatically saving the diagram for local changes. 275 */ 276DrawioFile.prototype.mergeFile = function(file, success, error, diffShadow) 277{ 278 var reportError = true; 279 280 try 281 { 282 this.stats.fileMerged++; 283 284 // Takes copy of current shadow document 285 var shadow = (this.shadowPages != null) ? this.shadowPages : 286 this.ui.getPagesForNode(mxUtils.parseXml( 287 this.shadowData).documentElement); 288 289 // Loads new document as shadow document 290 var pages = this.ui.getPagesForNode( 291 mxUtils.parseXml(file.data). 292 documentElement) 293 294 if (pages != null && pages.length > 0) 295 { 296 this.shadowPages = pages; 297 298 // Creates a patch for backup if the checksum fails 299 this.backupPatch = (this.isModified()) ? 300 this.ui.diffPages(shadow, 301 this.ui.pages) : null; 302 303 // Patches the current document 304 var patches = [this.ui.diffPages((diffShadow != null) ? 305 diffShadow : shadow, this.shadowPages)]; 306 var ignored = this.ignorePatches(patches); 307 308 if (!ignored) 309 { 310 // Patching previous shadow to verify checksum 311 var patched = this.ui.patchPages(shadow, patches[0]); 312 313 var patchedDetails = {}; 314 var checksum = this.ui.getHashValueForPages(patched, patchedDetails); 315 var currentDetails = {}; 316 var current = this.ui.getHashValueForPages(this.shadowPages, currentDetails); 317 318 if (urlParams['test'] == '1') 319 { 320 EditorUi.debug('File.mergeFile', [this], 321 'backup', this.backupPatch, 322 'patches', patches, 323 'checksum', current == checksum, checksum); 324 } 325 326 if (checksum != null && checksum != current) 327 { 328 var fileData = this.compressReportData(this.getAnonymizedXmlForPages(pages)); 329 var data = this.compressReportData(this.getAnonymizedXmlForPages(patched)); 330 var from = this.ui.hashValue(file.getCurrentEtag()); 331 var to = this.ui.hashValue(this.getCurrentEtag()); 332 333 this.checksumError(error, patches, 334 'Shadow Details: ' + JSON.stringify(patchedDetails) + 335 '\nChecksum: ' + checksum + 336 '\nCurrent: ' + current + 337 '\nCurrent Details: ' + JSON.stringify(currentDetails) + 338 '\nFrom: ' + from + 339 '\nTo: ' + to + 340 '\n\nFile Data:\n' + fileData + 341 '\nPatched Shadow:\n' + data, null, 'mergeFile'); 342 343 // Abnormal termination 344 return; 345 } 346 else 347 { 348 // Patches the current document 349 this.patch(patches, 350 (DrawioFile.LAST_WRITE_WINS) ? 351 this.backupPatch : null); 352 } 353 } 354 } 355 else 356 { 357 reportError = false; 358 throw new Error(mxResources.get('notADiagramFile')); 359 } 360 361 this.invalidChecksum = false; 362 this.inConflictState = false; 363 this.setDescriptor(file.getDescriptor()); 364 this.descriptorChanged(); 365 this.backupPatch = null; 366 367 if (success != null) 368 { 369 success(); 370 } 371 } 372 catch (e) 373 { 374 this.inConflictState = true; 375 this.invalidChecksum = true; 376 this.descriptorChanged(); 377 378 if (error != null) 379 { 380 error(e); 381 } 382 383 try 384 { 385 if (reportError) 386 { 387 if (this.errorReportsEnabled) 388 { 389 this.sendErrorReport('Error in mergeFile', null, e); 390 } 391 else 392 { 393 var user = this.getCurrentUser(); 394 var uid = (user != null) ? user.id : 'unknown'; 395 396 EditorUi.logError('Error in mergeFile', null, 397 this.getMode() + '.' + this.getId(), 398 uid, e); 399 } 400 } 401 } 402 catch (e2) 403 { 404 // ignore 405 } 406 } 407}; 408 409/** 410 * Adds the listener for automatically saving the diagram for local changes. 411 */ 412DrawioFile.prototype.getAnonymizedXmlForPages = function(pages) 413{ 414 var enc = new mxCodec(mxUtils.createXmlDocument()); 415 var file = enc.document.createElement('mxfile'); 416 417 if (pages != null) 418 { 419 for (var i = 0; i < pages.length; i++) 420 { 421 var temp = enc.encode(new mxGraphModel(pages[i].root)); 422 423 if (urlParams['dev'] != '1') 424 { 425 temp = this.ui.anonymizeNode(temp, true); 426 } 427 428 temp.setAttribute('id', pages[i].getId()); 429 430 if (pages[i].viewState) 431 { 432 this.ui.editor.graph.saveViewState(pages[i].viewState, temp, true); 433 } 434 435 file.appendChild(temp); 436 } 437 } 438 439 return mxUtils.getPrettyXml(file); 440}; 441 442/** 443 * Adds the listener for automatically saving the diagram for local changes. 444 */ 445DrawioFile.prototype.compressReportData = function(data, limit, max) 446{ 447 limit = (limit != null) ? limit : 10000; 448 449 if (max != null && data != null && data.length > max) 450 { 451 data = data.substring(0, max) + '[...]'; 452 } 453 else if (data != null && data.length > limit) 454 { 455 data = Graph.compress(data) + '\n'; 456 } 457 458 return data; 459}; 460 461/** 462 * Adds the listener for automatically saving the diagram for local changes. 463 */ 464DrawioFile.prototype.checksumError = function(error, patches, details, etag, functionName) 465{ 466 this.stats.checksumErrors++; 467 this.inConflictState = true; 468 this.invalidChecksum = true; 469 this.descriptorChanged(); 470 471 if (this.sync != null) 472 { 473 this.sync.updateOnlineState(); 474 } 475 476 if (error != null) 477 { 478 error(); 479 } 480 481 try 482 { 483 if (this.errorReportsEnabled) 484 { 485 if (patches != null) 486 { 487 for (var i = 0; i < patches.length; i++) 488 { 489 this.ui.anonymizePatch(patches[i]); 490 } 491 } 492 493 var fn = mxUtils.bind(this, function(file) 494 { 495 var json = this.compressReportData( 496 JSON.stringify(patches, null, 2)); 497 var remote = (file != null) ? this.compressReportData( 498 this.getAnonymizedXmlForPages( 499 this.ui.getPagesForNode( 500 mxUtils.parseXml(file.data).documentElement)), 25000) : 'n/a'; 501 502 this.sendErrorReport('Checksum Error in ' + functionName + ' ' + this.getHash(), 503 ((details != null) ? (details) : '') + '\n\nPatches:\n' + json + 504 ((remote != null) ? ('\n\nRemote:\n' + remote) : ''), null, 70000); 505 }); 506 507 if (etag == null) 508 { 509 fn(null); 510 } 511 else 512 { 513 this.getLatestVersion(mxUtils.bind(this, function(file) 514 { 515 if (file != null && file.getCurrentEtag() == etag) 516 { 517 fn(file); 518 } 519 else 520 { 521 fn(null); 522 } 523 }), function() {}); 524 } 525 } 526 else 527 { 528 var user = this.getCurrentUser(); 529 var uid = (user != null) ? user.id : 'unknown'; 530 531 EditorUi.logError('Checksum Error in ' + functionName + ' ' + this.getId(), 532 null, this.getMode() + '.' + this.getId(), 533 'user_' + uid + ((this.sync != null) ? 534 '-client_' + this.sync.clientId : '-nosync')); 535 536 // Logs checksum error for file 537 try 538 { 539 EditorUi.logEvent({category: 'CHECKSUM-ERROR-SYNC-FILE-' + this.getHash(), 540 action: functionName, label: 'user_' + uid + ((this.sync != null) ? 541 '-client_' + this.sync.clientId : '-nosync')}); 542 } 543 catch (e) 544 { 545 // ignore 546 } 547 } 548 } 549 catch (e) 550 { 551 // ignore 552 } 553}; 554 555/** 556 * Adds the listener for automatically saving the diagram for local changes. 557 */ 558DrawioFile.prototype.sendErrorReport = function(title, details, error, max) 559{ 560 try 561 { 562 var shadow = this.compressReportData( 563 this.getAnonymizedXmlForPages( 564 this.shadowPages), 25000); 565 var data = this.compressReportData( 566 this.getAnonymizedXmlForPages( 567 this.ui.pages), 25000); 568 var user = this.getCurrentUser(); 569 var uid = (user != null) ? this.ui.hashValue(user.id) : 'unknown'; 570 var cid = (this.sync != null) ? '-client_' + this.sync.clientId : '-nosync'; 571 var filename = this.getTitle(); 572 var dot = filename.lastIndexOf('.'); 573 var ext = 'xml'; 574 575 if (dot > 0) 576 { 577 ext = filename.substring(dot); 578 } 579 580 var stack = (error != null) ? error.stack : new Error().stack; 581 582 EditorUi.sendReport(title + ' ' + new Date().toISOString() + ':' + 583 '\n\nAppVersion=' + navigator.appVersion + 584 '\nFile=' + this.ui.hashValue(this.getId()) + ' (' + this.getMode() + ')' + 585 ((this.isModified()) ? ' modified' : '') + 586 '\nSize/Type=' + this.getSize() + ' (' + ext + ')' + 587 '\nUser=' + uid + cid + 588 '\nPrefix=' + this.ui.editor.graph.model.prefix + 589 '\nSync=' + DrawioFile.SYNC + 590 ((this.sync != null) ? (((this.sync.enabled) ? ' enabled' : '') + 591 ((this.sync.isConnected()) ? ' connected' : '')) : '') + 592 '\nPlugins=' + ((mxSettings.settings != null) ? mxSettings.getPlugins() : 'null') + 593 '\n\nStats:\n' + JSON.stringify(this.stats, null, 2) + 594 ((details != null) ? ('\n\n' + details) : '') + 595 ((error != null) ? ('\n\nError: ' + error.message) : '') + 596 '\n\nStack:\n' + stack + 597 '\n\nShadow:\n' + shadow + 598 '\n\nData:\n' + data, max); 599 } 600 catch (e) 601 { 602 // ignore 603 } 604}; 605 606/** 607 * Adds the listener for automatically saving the diagram for local changes. 608 */ 609DrawioFile.prototype.reloadFile = function(success, error) 610{ 611 try 612 { 613 this.ui.spinner.stop(); 614 615 var fn = mxUtils.bind(this, function() 616 { 617 this.stats.fileReloaded++; 618 619 // Restores view state and current page 620 var viewState = this.ui.editor.graph.getViewState(); 621 var selection = this.ui.editor.graph.getSelectionCells(); 622 var page = this.ui.currentPage; 623 624 this.ui.loadFile(this.getHash(), true, null, mxUtils.bind(this, function() 625 { 626 if (this.ui.fileLoadedError == null) 627 { 628 this.ui.restoreViewState(page, viewState, selection); 629 630 if (this.backupPatch != null) 631 { 632 this.patch([this.backupPatch]); 633 } 634 635 // Carry-over stats 636 var file = this.ui.getCurrentFile(); 637 638 if (file != null) 639 { 640 file.stats = this.stats; 641 } 642 643 if (success != null) 644 { 645 success(); 646 } 647 } 648 }), true); 649 }); 650 651 if (this.isModified() && this.backupPatch == null) 652 { 653 this.ui.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function() 654 { 655 this.handleFileSuccess(DrawioFile.SYNC == 'manual'); 656 }), fn, mxResources.get('cancel'), mxResources.get('discardChanges')); 657 } 658 else 659 { 660 fn(); 661 } 662 } 663 catch (e) 664 { 665 if (error != null) 666 { 667 error(e); 668 } 669 } 670}; 671 672/** 673 * Shows a conflict dialog to the user. 674 */ 675DrawioFile.prototype.copyFile = function(success, error) 676{ 677 this.ui.editor.editAsNew(this.ui.getFileData(true), 678 this.ui.getCopyFilename(this)); 679}; 680 681/** 682 * Returns true if the patches in the given array are empty. 683 */ 684DrawioFile.prototype.ignorePatches = function(patches) 685{ 686 var ignore = true; 687 688 for (var i = 0; i < patches.length && ignore; i++) 689 { 690 ignore = ignore && Object.keys(patches[i]).length == 0; 691 } 692 693 return ignore; 694}; 695 696/** 697 * Applies the given patches to the file. 698 */ 699DrawioFile.prototype.patch = function(patches, resolver, undoable) 700{ 701 // Saves state of undo history 702 var undoMgr = this.ui.editor.undoManager; 703 var history = undoMgr.history.slice(); 704 var nextAdd = undoMgr.indexOfNextAdd; 705 706 // Hides graph during updates 707 var graph = this.ui.editor.graph; 708 graph.container.style.visibility = 'hidden'; 709 710 // Ignores change events 711 var prev = this.changeListenerEnabled; 712 this.changeListenerEnabled = undoable; 713 714 // Folding and math change require special handling 715 var fold = graph.foldingEnabled; 716 var math = graph.mathEnabled; 717 718 // Updates text editor if cell changes during validation 719 var redraw = graph.cellRenderer.redraw; 720 721 graph.cellRenderer.redraw = function(state) 722 { 723 if (state.view.graph.isEditing(state.cell)) 724 { 725 state.view.graph.scrollCellToVisible(state.cell); 726 state.view.graph.cellEditor.resize(); 727 } 728 729 redraw.apply(this, arguments); 730 }; 731 732 graph.model.beginUpdate(); 733 try 734 { 735 // Applies patches 736 for (var i = 0; i < patches.length; i++) 737 { 738 this.ui.pages = this.ui.patchPages(this.ui.pages, 739 patches[i], true, resolver, this.isModified()); 740 } 741 742 // Always needs at least one page 743 if (this.ui.pages.length == 0) 744 { 745 this.ui.pages.push(this.ui.createPage()); 746 } 747 748 // Checks if current page was removed 749 if (mxUtils.indexOf(this.ui.pages, this.ui.currentPage) < 0) 750 { 751 this.ui.selectPage(this.ui.pages[0], true); 752 } 753 } 754 finally 755 { 756 // Changes visibility before action states are updated via model event 757 graph.container.style.visibility = ''; 758 graph.model.endUpdate(); 759 760 // Restores previous state 761 graph.cellRenderer.redraw = redraw; 762 this.changeListenerEnabled = prev; 763 764 // Restores history state 765 if (!undoable) 766 { 767 undoMgr.history = history; 768 undoMgr.indexOfNextAdd = nextAdd; 769 undoMgr.fireEvent(new mxEventObject(mxEvent.CLEAR)); 770 } 771 772 if (this.ui.currentPage == null || this.ui.currentPage.needsUpdate) 773 { 774 // Updates the graph and background 775 if (math != graph.mathEnabled) 776 { 777 this.ui.editor.updateGraphComponents(); 778 graph.refresh(); 779 } 780 else 781 { 782 if (fold != graph.foldingEnabled) 783 { 784 graph.view.revalidate(); 785 } 786 else 787 { 788 graph.view.validate(); 789 } 790 791 graph.sizeDidChange(); 792 } 793 } 794 795 this.ui.updateTabContainer(); 796 this.ui.editor.fireEvent(new mxEventObject('pagesPatched', 'patches', patches)); 797 } 798}; 799 800/** 801 * Adds the listener for automatically saving the diagram for local changes. 802 */ 803DrawioFile.prototype.save = function(revision, success, error, unloading, overwrite, manual) 804{ 805 try 806 { 807 if (!this.isEditable()) 808 { 809 if (error != null) 810 { 811 error({message: mxResources.get('readOnly')}); 812 } 813 else 814 { 815 throw new Error(mxResources.get('readOnly')); 816 } 817 } 818 else if (!overwrite && this.invalidChecksum) 819 { 820 if (error != null) 821 { 822 error({message: mxResources.get('checksum')}); 823 } 824 else 825 { 826 throw new Error(mxResources.get('checksum')); 827 } 828 } 829 else 830 { 831 this.updateFileData(); 832 this.clearAutosave(); 833 834 if (success != null) 835 { 836 success(); 837 } 838 } 839 } 840 catch (e) 841 { 842 if (error != null) 843 { 844 error(e); 845 } 846 else 847 { 848 throw e; 849 } 850 } 851}; 852 853/** 854 * Translates this point by the given vector. 855 * 856 * @param {number} dx X-coordinate of the translation. 857 * @param {number} dy Y-coordinate of the translation. 858 */ 859DrawioFile.prototype.updateFileData = function() 860{ 861 this.setData(this.ui.getFileData(null, null, null, null, null, null, null, null, this, !this.isCompressed())); 862}; 863 864/** 865 * Translates this point by the given vector. 866 * 867 * @param {number} dx X-coordinate of the translation. 868 * @param {number} dy Y-coordinate of the translation. 869 */ 870DrawioFile.prototype.isCompressedStorage = function() 871{ 872 return true; 873}; 874 875/** 876 * Translates this point by the given vector. 877 * 878 * @param {number} dx X-coordinate of the translation. 879 * @param {number} dy Y-coordinate of the translation. 880 */ 881DrawioFile.prototype.isCompressed = function() 882{ 883 var compressed = (this.ui.fileNode != null) ? this.ui.fileNode.getAttribute('compressed') : null; 884 885 if (compressed != null) 886 { 887 return compressed != 'false'; 888 } 889 else 890 { 891 return this.isCompressedStorage() && Editor.compressXml; 892 } 893}; 894 895/** 896 * Translates this point by the given vector. 897 * 898 * @param {number} dx X-coordinate of the translation. 899 * @param {number} dy Y-coordinate of the translation. 900 */ 901DrawioFile.prototype.saveAs = function(filename, success, error) { }; 902 903/** 904 * Translates this point by the given vector. 905 * 906 * @param {number} dx X-coordinate of the translation. 907 * @param {number} dy Y-coordinate of the translation. 908 */ 909DrawioFile.prototype.saveFile = function(title, revision, success, error) { }; 910 911/** 912 * Returns true if copy, export and print are not allowed for this file. 913 */ 914DrawioFile.prototype.getPublicUrl = function(fn) 915{ 916 fn(null); 917}; 918 919/** 920 * Returns true if copy, export and print are not allowed for this file. 921 */ 922DrawioFile.prototype.isRestricted = function() 923{ 924 return false; 925}; 926 927/** 928 * Translates this point by the given vector. 929 * 930 * @param {number} dx X-coordinate of the translation. 931 * @param {number} dy Y-coordinate of the translation. 932 */ 933DrawioFile.prototype.isModified = function() 934{ 935 return this.modified; 936}; 937 938/** 939 * Translates this point by the given vector. 940 * 941 * @param {number} dx X-coordinate of the translation. 942 * @param {number} dy Y-coordinate of the translation. 943 */ 944DrawioFile.prototype.getShadowModified = function() 945{ 946 return this.shadowModified; 947}; 948 949/** 950 * Translates this point by the given vector. 951 * 952 * @param {number} dx X-coordinate of the translation. 953 * @param {number} dy Y-coordinate of the translation. 954 */ 955DrawioFile.prototype.setShadowModified = function(value) 956{ 957 this.shadowModified = value; 958}; 959 960/** 961 * Translates this point by the given vector. 962 * 963 * @param {number} dx X-coordinate of the translation. 964 * @param {number} dy Y-coordinate of the translation. 965 */ 966DrawioFile.prototype.setModified = function(value) 967{ 968 this.modified = value; 969 this.shadowModified = value; 970}; 971 972/** 973 * Specifies if the autosave checkbox should be shown in the document 974 * properties dialog. Default is false. 975 */ 976DrawioFile.prototype.isAutosaveOptional = function() 977{ 978 return false; 979}; 980 981/** 982 * Translates this point by the given vector. 983 * 984 * @param {number} dx X-coordinate of the translation. 985 * @param {number} dy Y-coordinate of the translation. 986 */ 987DrawioFile.prototype.isAutosave = function() 988{ 989 return !this.inConflictState && this.ui.editor.autosave; 990}; 991 992/** 993 * Translates this point by the given vector. 994 * 995 * @param {number} dx X-coordinate of the translation. 996 * @param {number} dy Y-coordinate of the translation. 997 */ 998DrawioFile.prototype.isRenamable = function() 999{ 1000 return false; 1001}; 1002 1003/** 1004 * Translates this point by the given vector. 1005 * 1006 * @param {number} dx X-coordinate of the translation. 1007 * @param {number} dy Y-coordinate of the translation. 1008 */ 1009DrawioFile.prototype.rename = function(title, success, error) { }; 1010 1011/** 1012 * Translates this point by the given vector. 1013 * 1014 * @param {number} dx X-coordinate of the translation. 1015 * @param {number} dy Y-coordinate of the translation. 1016 */ 1017DrawioFile.prototype.isMovable = function() 1018{ 1019 return false; 1020}; 1021 1022/** 1023 * Translates this point by the given vector. 1024 * 1025 * @param {number} dx X-coordinate of the translation. 1026 * @param {number} dy Y-coordinate of the translation. 1027 */ 1028DrawioFile.prototype.isTrashed = function() 1029{ 1030 return false; 1031}; 1032 1033/** 1034 * Translates this point by the given vector. 1035 * 1036 * @param {number} dx X-coordinate of the translation. 1037 * @param {number} dy Y-coordinate of the translation. 1038 */ 1039DrawioFile.prototype.move = function(folderId, success, error) { }; 1040 1041/** 1042 * Translates this point by the given vector. 1043 * 1044 * @param {number} dx X-coordinate of the translation. 1045 * @param {number} dy Y-coordinate of the translation. 1046 */ 1047DrawioFile.prototype.share = function() 1048{ 1049 this.ui.alert(mxResources.get('sharingAvailable'), null, 380); 1050}; 1051 1052/** 1053 * Returns the hash of the file which consists of a prefix for the storage 1054 * type and the ID of the file. 1055 */ 1056DrawioFile.prototype.getHash = function() 1057{ 1058 return ''; 1059}; 1060 1061/** 1062 * Returns the ID of the file. 1063 */ 1064DrawioFile.prototype.getId = function() 1065{ 1066 return ''; 1067}; 1068 1069/** 1070 * Returns true if the file is editable. 1071 */ 1072DrawioFile.prototype.isEditable = function() 1073{ 1074 return !this.ui.editor.isChromelessView() || this.ui.editor.editable; 1075}; 1076 1077/** 1078 * Returns the location as a new object. 1079 * @type mx.Point 1080 */ 1081DrawioFile.prototype.getUi = function() 1082{ 1083 return this.ui; 1084}; 1085 1086/** 1087 * Returns the current title of the file. 1088 */ 1089DrawioFile.prototype.getTitle = function() 1090{ 1091 return ''; 1092}; 1093 1094/** 1095 * Sets the current data of the file. 1096 */ 1097DrawioFile.prototype.setData = function(data) 1098{ 1099 this.data = data; 1100}; 1101 1102/** 1103 * Returns the current data of the file. 1104 */ 1105DrawioFile.prototype.getData = function() 1106{ 1107 return this.data; 1108}; 1109 1110/** 1111 * Opens this file in the editor. 1112 */ 1113DrawioFile.prototype.open = function() 1114{ 1115 this.stats.opened++; 1116 var data = this.getData(); 1117 1118 if (data != null) 1119 { 1120 //Remove external fonts of previous file 1121 function removeExtFont(elems) 1122 { 1123 for (var i = 0; elems != null && i < elems.length; i++) 1124 { 1125 var e = elems[i]; 1126 1127 if (e.id != null && e.id.indexOf('extFont_') == 0) 1128 { 1129 e.parentNode.removeChild(e); 1130 } 1131 } 1132 }; 1133 1134 removeExtFont(document.querySelectorAll('head > style[id]')); 1135 removeExtFont(document.querySelectorAll('head > link[id]')); 1136 1137 this.ui.setFileData(data); 1138 1139 // Updates shadow in case any page IDs have been updated 1140 // only if the file has not been modified and reopened 1141 if (!this.isModified()) 1142 { 1143 this.shadowData = mxUtils.getXml(this.ui.getXmlFileData()); 1144 this.shadowPages = null; 1145 } 1146 } 1147 1148 this.installListeners(); 1149 1150 if (this.isSyncSupported()) 1151 { 1152 this.startSync(); 1153 } 1154}; 1155 1156/** 1157 * Hook for subclassers. 1158 */ 1159DrawioFile.prototype.isSyncSupported = function() 1160{ 1161 return false; 1162}; 1163 1164/** 1165 * Hook for subclassers. 1166 */ 1167DrawioFile.prototype.isRevisionHistorySupported = function() 1168{ 1169 return false; 1170}; 1171 1172/** 1173 * Hook for subclassers. 1174 */ 1175DrawioFile.prototype.getRevisions = function(success, error) 1176{ 1177 success(null); 1178}; 1179 1180/** 1181 * Hook for subclassers to get the latest descriptor of this file 1182 * and return it in the success handler. 1183 */ 1184DrawioFile.prototype.loadDescriptor = function(success, error) 1185{ 1186 success(null); 1187}; 1188 1189/** 1190 * Hook for subclassers to get the latest etag of this file 1191 * and return it in the success handler. 1192 */ 1193DrawioFile.prototype.loadPatchDescriptor = function(success, error) 1194{ 1195 this.loadDescriptor(mxUtils.bind(this, function(desc) 1196 { 1197 success(desc); 1198 }), error); 1199}; 1200 1201/** 1202 * Adds the listener for automatically saving the diagram for local changes. 1203 */ 1204DrawioFile.prototype.patchDescriptor = function(desc, patch) 1205{ 1206 this.setDescriptorEtag(desc, this.getDescriptorEtag(patch)); 1207 this.descriptorChanged(); 1208}; 1209 1210/** 1211 * Creates a starts the synchronization. 1212 */ 1213DrawioFile.prototype.startSync = function() 1214{ 1215 if ((DrawioFile.SYNC == 'auto' && urlParams['stealth'] != '1') && 1216 (urlParams['rt'] == '1' || !this.ui.editor.chromeless || 1217 this.ui.editor.editable)) 1218 { 1219 if (this.sync == null) 1220 { 1221 this.sync = new DrawioFileSync(this); 1222 } 1223 1224 this.sync.start(); 1225 } 1226}; 1227 1228/** 1229 * Hook for subclassers to check if an error is a conflict. 1230 */ 1231DrawioFile.prototype.isConflict = function() 1232{ 1233 return false; 1234}; 1235 1236/** 1237 * Gets the channel ID for sync messages. 1238 */ 1239DrawioFile.prototype.getChannelId = function() 1240{ 1241 // Slash, space and plus replaced with underscore 1242 return Graph.compress(this.getHash()).replace(/[\/ +]/g, '_'); 1243}; 1244 1245/** 1246 * Gets the channel ID from the given descriptor. 1247 */ 1248DrawioFile.prototype.getChannelKey = function(desc) 1249{ 1250 return null; 1251}; 1252 1253/** 1254 * Returns the current etag. 1255 */ 1256DrawioFile.prototype.getCurrentUser = function() 1257{ 1258 return null; 1259}; 1260 1261/** 1262 * Hook for subclassers to get the latest version of this file 1263 * and return it in the success handler. 1264 */ 1265DrawioFile.prototype.getLatestVersion = function(success, error) 1266{ 1267 success(null); 1268}; 1269 1270/** 1271 * Returns the last modified date of this file. 1272 */ 1273DrawioFile.prototype.getLastModifiedDate = function() 1274{ 1275 return new Date(); 1276}; 1277 1278/** 1279 * Sets the current revision ID. 1280 */ 1281DrawioFile.prototype.setCurrentRevisionId = function(id) 1282{ 1283 this.setDescriptorRevisionId(this.getDescriptor(), id); 1284}; 1285 1286/** 1287 * Returns the current revision ID. 1288 */ 1289DrawioFile.prototype.getCurrentRevisionId = function() 1290{ 1291 return this.getDescriptorRevisionId(this.getDescriptor()); 1292}; 1293 1294/** 1295 * Sets the current etag. 1296 */ 1297DrawioFile.prototype.setCurrentEtag = function(etag) 1298{ 1299 this.setDescriptorEtag(this.getDescriptor(), etag); 1300}; 1301 1302/** 1303 * Returns the current etag. 1304 */ 1305DrawioFile.prototype.getCurrentEtag = function() 1306{ 1307 return this.getDescriptorEtag(this.getDescriptor()); 1308}; 1309 1310/** 1311 * Returns the descriptor from this file. 1312 */ 1313DrawioFile.prototype.getDescriptor = function() 1314{ 1315 return null; 1316}; 1317 1318/** 1319 * Sets the descriptor for this file. 1320 */ 1321DrawioFile.prototype.setDescriptor = function() { }; 1322 1323/** 1324 * Updates the revision ID on the given descriptor. 1325 */ 1326DrawioFile.prototype.setDescriptorRevisionId = function(desc, id) 1327{ 1328 this.setDescriptorEtag(desc, id); 1329}; 1330 1331/** 1332 * Returns the revision ID from the given descriptor. 1333 */ 1334DrawioFile.prototype.getDescriptorRevisionId = function(desc) 1335{ 1336 return this.getDescriptorEtag(desc); 1337}; 1338 1339/** 1340 * Updates the etag on the given descriptor. 1341 */ 1342DrawioFile.prototype.setDescriptorEtag = function(desc, etag) { }; 1343 1344/** 1345 * Returns the etag from the given descriptor. 1346 */ 1347DrawioFile.prototype.getDescriptorEtag = function(desc) 1348{ 1349 return null; 1350}; 1351 1352/** 1353 * Returns the secret from the given descriptor. This must be stored 1354 * in a custom property and generated by the saving client so that a 1355 * token can be obtained from the cache for writing the patch after 1356 * saving the file. If this cannot be saved in a custom property then 1357 * null must be returned so that no deltas are used for updating the 1358 * file (the file is reloaded every time instead). This is needed to 1359 * make sure nobody with read-only permissions can write a patch to 1360 * the cache before the saving client wrote the patch and inject 1361 * data into the file via other clients merging that data. 1362 */ 1363DrawioFile.prototype.getDescriptorSecret = function(desc) 1364{ 1365 return null; 1366}; 1367 1368/** 1369 * Installs the change listener. 1370 */ 1371DrawioFile.prototype.installListeners = function() 1372{ 1373 if (this.changeListener == null) 1374 { 1375 this.changeListener = mxUtils.bind(this, function(sender, eventObject) 1376 { 1377 var edit = (eventObject != null) ? eventObject.getProperty('edit') : null; 1378 1379 if (this.changeListenerEnabled && this.isEditable() && (edit == null || !edit.ignoreEdit)) 1380 { 1381 this.fileChanged(); 1382 } 1383 }); 1384 1385 this.ui.editor.graph.model.addListener(mxEvent.CHANGE, this.changeListener); 1386 1387 // Some options trigger autosave 1388 this.ui.editor.graph.addListener('gridSizeChanged', this.changeListener); 1389 this.ui.editor.graph.addListener('shadowVisibleChanged', this.changeListener); 1390 this.ui.addListener('pageFormatChanged', this.changeListener); 1391 this.ui.addListener('pageScaleChanged', this.changeListener); 1392 this.ui.addListener('backgroundColorChanged', this.changeListener); 1393 this.ui.addListener('backgroundImageChanged', this.changeListener); 1394 this.ui.addListener('foldingEnabledChanged', this.changeListener); 1395 this.ui.addListener('mathEnabledChanged', this.changeListener); 1396 this.ui.addListener('gridEnabledChanged', this.changeListener); 1397 this.ui.addListener('guidesEnabledChanged', this.changeListener); 1398 this.ui.addListener('tooltipsEnabledChanged', this.changeListener); 1399 this.ui.addListener('pageViewChanged', this.changeListener); 1400 this.ui.addListener('connectionPointsChanged', this.changeListener); 1401 this.ui.addListener('connectionArrowsChanged', this.changeListener); 1402 } 1403}; 1404 1405/** 1406 * Returns the location as a new object. 1407 * @type mx.Point 1408 */ 1409DrawioFile.prototype.addAllSavedStatus = function(status) 1410{ 1411 if (this.ui.statusContainer != null && this.ui.getCurrentFile() == this) 1412 { 1413 status = (status != null) ? status : mxUtils.htmlEntities(mxResources.get(this.allChangesSavedKey)); 1414 this.ui.editor.setStatus('<div title="'+ status + '">' + status + '</div>'); 1415 var links = this.ui.statusContainer.getElementsByTagName('div'); 1416 1417 if (links.length > 0 && this.isRevisionHistorySupported()) 1418 { 1419 links[0].style.cursor = 'pointer'; 1420 links[0].style.textDecoration = 'underline'; 1421 1422 mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function() 1423 { 1424 this.ui.actions.get('revisionHistory').funct(); 1425 })); 1426 } 1427 } 1428}; 1429 1430/** 1431 * Adds the listener for automatically saving the diagram for local changes. 1432 */ 1433DrawioFile.prototype.saveDraft = function() 1434{ 1435 try 1436 { 1437 if (this.draftId == null) 1438 { 1439 this.draftId = Editor.guid(); 1440 } 1441 1442 var draft = {type: 'draft', 1443 created: this.created, 1444 modified: new Date().getTime(), 1445 data: this.ui.getFileData(), 1446 title: this.getTitle(), 1447 aliveCheck: this.ui.draftAliveCheck}; 1448 this.ui.setDatabaseItem('.draft_' + this.draftId, 1449 JSON.stringify(draft)); 1450 1451 EditorUi.debug('draft saved', this.draftId, draft); 1452 } 1453 catch (e) 1454 { 1455 // Removes any stored draft 1456 this.removeDraft(); 1457 } 1458}; 1459 1460/** 1461 * Adds the listener for automatically saving the diagram for local changes. 1462 */ 1463DrawioFile.prototype.removeDraft = function() 1464{ 1465 try 1466 { 1467 if (this.draftId != null) 1468 { 1469 this.ui.removeDatabaseItem('.draft_' + this.draftId); 1470 EditorUi.debug('draft deleted', '.draft_' + this.draftId); 1471 } 1472 } 1473 catch (e) 1474 { 1475 // ignore 1476 } 1477}; 1478 1479/** 1480 * Adds the listener for automatically saving the diagram for local changes. 1481 */ 1482DrawioFile.prototype.addUnsavedStatus = function(err) 1483{ 1484 if (!this.inConflictState && this.ui.statusContainer != null && this.ui.getCurrentFile() == this) 1485 { 1486 if (err instanceof Error && err.message != null && err.message != '') 1487 { 1488 var status = mxUtils.htmlEntities(mxResources.get('unsavedChanges')); 1489 1490 this.ui.editor.setStatus('<div title="'+ status + '" class="geStatusAlert">' + 1491 status + ' (' + mxUtils.htmlEntities(err.message) + ')</div>'); 1492 } 1493 else 1494 { 1495 var msg = this.getErrorMessage(err); 1496 1497 if (msg == null && this.lastSaved != null) 1498 { 1499 var str = this.ui.timeSince(new Date(this.lastSaved)); 1500 1501 // Only show if more than a minute ago 1502 if (str != null) 1503 { 1504 msg = mxResources.get('lastSaved', [str]); 1505 } 1506 } 1507 1508 if (msg != null && msg.length > 60) 1509 { 1510 msg = msg.substring(0, 60) + '...'; 1511 } 1512 1513 var status = mxUtils.htmlEntities(mxResources.get('unsavedChangesClickHereToSave')) + 1514 ((msg != null && msg != '') ? ' (' + mxUtils.htmlEntities(msg) + ')' : ''); 1515 this.ui.editor.setStatus('<div title="'+ status + '" class="geStatusAlertOrange">' + status + 1516 ' <img src="' + Editor.saveImage + '"/></div>'); 1517 1518 // Installs click handler for saving 1519 var links = this.ui.statusContainer.getElementsByTagName('div'); 1520 1521 if (links != null && links.length > 0) 1522 { 1523 links[0].style.cursor = 'pointer'; 1524 1525 mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function() 1526 { 1527 this.ui.actions.get((this.ui.mode == null || !this.isEditable()) ? 1528 'saveAs' : 'save').funct(); 1529 })); 1530 } 1531 else 1532 { 1533 var status = mxUtils.htmlEntities(mxResources.get('unsavedChanges')); 1534 1535 this.ui.editor.setStatus('<div title="'+ status + '" class="geStatusAlert">' + status + 1536 ' (' + mxUtils.htmlEntities(err.message) + ')</div>'); 1537 } 1538 1539 if (EditorUi.enableDrafts && (this.getMode() == null || EditorUi.isElectronApp)) 1540 { 1541 if (this.saveDraftThread != null) 1542 { 1543 window.clearTimeout(this.saveDraftThread); 1544 } 1545 1546 this.saveDraftThread = window.setTimeout(mxUtils.bind(this, function() 1547 { 1548 this.saveDraft(); 1549 }), 0); 1550 } 1551 } 1552 } 1553}; 1554 1555/** 1556 * Halts all timers and shows a conflict status message. The optional error 1557 * handler is invoked first. 1558 */ 1559DrawioFile.prototype.addConflictStatus = function(fn, message) 1560{ 1561 if (this.invalidChecksum && message == null) 1562 { 1563 message = mxResources.get('checksum'); 1564 } 1565 1566 this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) + 1567 ((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : '')); 1568 this.ui.spinner.stop(); 1569 this.clearAutosave(); 1570 1571 var links = (this.ui.statusContainer != null) ? this.ui.statusContainer.getElementsByTagName('div') : null; 1572 1573 if (links != null && links.length > 0) 1574 { 1575 links[0].style.cursor = 'pointer'; 1576 1577 mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function(evt) 1578 { 1579 if (mxEvent.getSource(evt).nodeName != 'IMG') 1580 { 1581 fn(); 1582 } 1583 })); 1584 } 1585 else 1586 { 1587 this.ui.alert(mxUtils.htmlEntities(mxResources.get('fileChangedSync')), fn); 1588 } 1589}; 1590 1591/** 1592 * Halts all timers and shows a conflict status message. The optional error 1593 * handler is invoked first. 1594 */ 1595DrawioFile.prototype.setConflictStatus = function(message) 1596{ 1597 this.ui.editor.setStatus('<div title="'+ message + '" class="geStatusAlert">' + message + 1598 ' <a href="https://www.diagrams.net/doc/faq/synchronize" title="' + mxResources.get('help') + 1599 '" target="_blank">' + '<img src="' + Editor.helpImage + '"/></a></div>'); 1600}; 1601 1602/** 1603 * Shows a conflict dialog to the user. 1604 */ 1605DrawioFile.prototype.showRefreshDialog = function(success, error, message) 1606{ 1607 if (message == null) 1608 { 1609 message = mxResources.get('checksum'); 1610 } 1611 1612 if (this.ui.editor.isChromelessView() && !this.ui.editor.editable) 1613 { 1614 this.ui.alert(mxResources.get('fileChangedSync'), mxUtils.bind(this, function() 1615 { 1616 this.reloadFile(success, error); 1617 })); 1618 } 1619 else 1620 { 1621 // Allows for escape key to be pressed while dialog is showing 1622 this.addConflictStatus(mxUtils.bind(this, function() 1623 { 1624 this.showRefreshDialog(success, error); 1625 }), message); 1626 1627 this.ui.showError(mxResources.get('error') + ' (' + message + ')', 1628 mxResources.get('fileChangedSyncDialog'), 1629 mxResources.get('makeCopy'), mxUtils.bind(this, function() 1630 { 1631 this.copyFile(success, error); 1632 }), null, mxResources.get('synchronize'), mxUtils.bind(this, function() 1633 { 1634 this.reloadFile(success, error); 1635 }), mxResources.get('cancel'), mxUtils.bind(this, function() 1636 { 1637 this.ui.hideDialog(); 1638 }), 360, 150); 1639 } 1640}; 1641 1642/** 1643 * Shows a dialog with no synchronize option. 1644 */ 1645DrawioFile.prototype.showCopyDialog = function(success, error, overwrite) 1646{ 1647 this.inConflictState = false; 1648 this.invalidChecksum = false; 1649 this.addUnsavedStatus(); 1650 1651 this.ui.showError(mxResources.get('externalChanges'), 1652 mxResources.get('fileChangedOverwriteDialog'), 1653 mxResources.get('makeCopy'), mxUtils.bind(this, function() 1654 { 1655 this.copyFile(success, error); 1656 }), null, mxResources.get('overwrite'), overwrite, 1657 mxResources.get('cancel'), mxUtils.bind(this, function() 1658 { 1659 this.ui.hideDialog(); 1660 }), 360, 150); 1661}; 1662 1663/** 1664 * Shows a conflict dialog to the user. 1665 */ 1666DrawioFile.prototype.showConflictDialog = function(overwrite, synchronize) 1667{ 1668 this.ui.showError(mxResources.get('externalChanges'), 1669 mxResources.get('fileChangedSyncDialog'), 1670 mxResources.get('overwrite'), overwrite, null, 1671 mxResources.get('synchronize'), synchronize, 1672 mxResources.get('cancel'), mxUtils.bind(this, function() 1673 { 1674 this.ui.hideDialog(); 1675 this.handleFileError(null, false); 1676 }), 340, 150); 1677}; 1678 1679/** 1680 * Checks if the client is authorized and calls the next step. 1681 */ 1682DrawioFile.prototype.redirectToNewApp = function(error, details) 1683{ 1684 this.ui.spinner.stop(); 1685 1686 if (!this.redirectDialogShowing) 1687 { 1688 this.redirectDialogShowing = true; 1689 1690 var url = window.location.protocol + '//' + window.location.host + '/' + this.ui.getSearch( 1691 ['create', 'title', 'mode', 'url', 'drive', 'splash', 'state']) + '#' + this.getHash(); 1692 var msg = mxResources.get('redirectToNewApp'); 1693 1694 if (details != null) 1695 { 1696 msg += ' (' + details + ')'; 1697 } 1698 1699 var redirect = mxUtils.bind(this, function() 1700 { 1701 var fn = mxUtils.bind(this, function() 1702 { 1703 this.redirectDialogShowing = false; 1704 1705 if (window.location.href == url) 1706 { 1707 window.location.reload(); 1708 } 1709 else 1710 { 1711 window.location.href = url; 1712 } 1713 }); 1714 1715 if (error == null && this.isModified()) 1716 { 1717 this.ui.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function() 1718 { 1719 this.redirectDialogShowing = false; 1720 }), fn, mxResources.get('cancel'), mxResources.get('discardChanges')); 1721 } 1722 else 1723 { 1724 fn(); 1725 } 1726 }); 1727 1728 if (error != null) 1729 { 1730 if (this.isModified()) 1731 { 1732 this.ui.confirm(msg, mxUtils.bind(this, function() 1733 { 1734 this.redirectDialogShowing = false; 1735 error(); 1736 }), redirect, mxResources.get('cancel'), mxResources.get('discardChanges')); 1737 } 1738 else 1739 { 1740 this.ui.confirm(msg, redirect, mxUtils.bind(this, function() 1741 { 1742 this.redirectDialogShowing = false; 1743 error(); 1744 })); 1745 } 1746 } 1747 else 1748 { 1749 this.ui.alert(mxResources.get('redirectToNewApp'), redirect); 1750 } 1751 } 1752}; 1753 1754/** 1755 * Adds the listener for automatically saving the diagram for local changes. 1756 */ 1757DrawioFile.prototype.handleFileSuccess = function(saved) 1758{ 1759 this.ui.spinner.stop(); 1760 1761 if (this.ui.getCurrentFile() == this) 1762 { 1763 if (this.isModified()) 1764 { 1765 this.fileChanged(); 1766 } 1767 else if (saved) 1768 { 1769 if (this.isTrashed()) 1770 { 1771 this.addAllSavedStatus(mxUtils.htmlEntities(mxResources.get(this.allChangesSavedKey)) + ' (' + 1772 mxUtils.htmlEntities(mxResources.get('fileMovedToTrash')) + ')'); 1773 } 1774 else 1775 { 1776 this.addAllSavedStatus(); 1777 } 1778 1779 if (this.sync != null) 1780 { 1781 this.sync.resetUpdateStatusThread(); 1782 1783 if (this.sync.remoteFileChanged) 1784 { 1785 this.sync.remoteFileChanged = false; 1786 this.sync.fileChangedNotify(); 1787 } 1788 } 1789 } 1790 else 1791 { 1792 this.ui.editor.setStatus(''); 1793 } 1794 } 1795}; 1796 1797/** 1798 * Adds the listener for automatically saving the diagram for local changes. 1799 */ 1800DrawioFile.prototype.handleFileError = function(err, manual) 1801{ 1802 this.ui.spinner.stop(); 1803 1804 if (this.ui.getCurrentFile() == this) 1805 { 1806 if (this.inConflictState) 1807 { 1808 this.handleConflictError(err, manual); 1809 } 1810 else 1811 { 1812 if (this.isModified()) 1813 { 1814 this.addUnsavedStatus(err); 1815 } 1816 1817 if (manual) 1818 { 1819 this.ui.handleError(err, (err != null) ? mxResources.get('errorSavingFile') : null); 1820 } 1821 else if (!this.isModified()) 1822 { 1823 var msg = this.getErrorMessage(err); 1824 1825 if (msg != null && msg.length > 60) 1826 { 1827 msg = msg.substring(0, 60) + '...'; 1828 } 1829 1830 this.ui.editor.setStatus('<div class="geStatusAlert">' + 1831 mxUtils.htmlEntities(mxResources.get('error')) + ((msg != null) ? 1832 ' (' + mxUtils.htmlEntities(msg) + ')' : '') + '</div>'); 1833 } 1834 } 1835 } 1836}; 1837 1838/** 1839 * Adds the listener for automatically saving the diagram for local changes. 1840 */ 1841DrawioFile.prototype.handleConflictError = function(err, manual) 1842{ 1843 var success = mxUtils.bind(this, function() 1844 { 1845 this.handleFileSuccess(true); 1846 }); 1847 1848 var error = mxUtils.bind(this, function(err2) 1849 { 1850 this.handleFileError(err2, true); 1851 }); 1852 1853 var overwrite = mxUtils.bind(this, function() 1854 { 1855 if (this.ui.spinner.spin(document.body, mxResources.get(this.savingSpinnerKey))) 1856 { 1857 this.ui.editor.setStatus(''); 1858 var isRepoFile = (this.constructor == GitHubFile) || (this.constructor == GitLabFile); 1859 this.save(true, success, error, null, true, (isRepoFile && 1860 err != null) ? err.commitMessage : null); 1861 } 1862 }); 1863 1864 var synchronize = mxUtils.bind(this, function() 1865 { 1866 if (this.ui.spinner.spin(document.body, mxResources.get('updatingDocument'))) 1867 { 1868 this.synchronizeFile(mxUtils.bind(this, function() 1869 { 1870 this.ui.spinner.stop(); 1871 1872 if (this.ui.spinner.spin(document.body, mxResources.get(this.savingSpinnerKey))) 1873 { 1874 var isRepoFile = (this.constructor == GitHubFile) || (this.constructor == GitLabFile); 1875 this.save(true, success, error, null, null, (isRepoFile && 1876 err != null) ? err.commitMessage : null); 1877 } 1878 }), error); 1879 } 1880 }) 1881 1882 if (DrawioFile.SYNC == 'none') 1883 { 1884 this.showCopyDialog(success, error, overwrite); 1885 } 1886 else if (this.invalidChecksum) 1887 { 1888 this.showRefreshDialog(success, error, this.getErrorMessage(err)); 1889 } 1890 else if (manual) 1891 { 1892 this.showConflictDialog(overwrite, synchronize); 1893 } 1894 else 1895 { 1896 this.addConflictStatus(mxUtils.bind(this, function() 1897 { 1898 this.ui.editor.setStatus(mxUtils.htmlEntities( 1899 mxResources.get('updatingDocument'))); 1900 this.synchronizeFile(success, error); 1901 }), this.getErrorMessage(err)); 1902 } 1903}; 1904 1905/** 1906 * Adds the listener for automatically saving the diagram for local changes. 1907 */ 1908DrawioFile.prototype.getErrorMessage = function(err) 1909{ 1910 var msg = (err != null) ? ((err.error != null) ? err.error.message : err.message) : null; 1911 1912 if (msg == null && err != null && err.code == App.ERROR_TIMEOUT) 1913 { 1914 msg = mxResources.get('timeout'); 1915 } 1916 1917 return msg; 1918}; 1919 1920/** 1921 * Returns true if the oldest unsaved change is older than <EditorUi.warnInterval>. 1922 */ 1923DrawioFile.prototype.isOverdue = function() 1924{ 1925 return this.ageStart != null && (Date.now() - this.ageStart.getTime()) >= this.ui.warnInterval; 1926}; 1927 1928/** 1929 * Adds the listener for automatically saving the diagram for local changes. 1930 */ 1931DrawioFile.prototype.fileChanged = function() 1932{ 1933 this.lastChanged = new Date(); 1934 this.setModified(true); 1935 1936 if (this.isAutosave()) 1937 { 1938 if (this.savingStatusKey != null) 1939 { 1940 this.addAllSavedStatus(mxUtils.htmlEntities(mxResources.get(this.savingStatusKey)) + '...'); 1941 } 1942 1943 this.ui.scheduleSanityCheck(); 1944 1945 if (this.ageStart == null) 1946 { 1947 this.ageStart = new Date(); 1948 } 1949 1950 //Send changes immidiately if P2P is enabled 1951 this.sendFileChanges(); 1952 1953 this.autosave(this.autosaveDelay, this.maxAutosaveDelay, mxUtils.bind(this, function(resp) 1954 { 1955 this.ui.stopSanityCheck(); 1956 1957 // Does not update status if another autosave was scheduled 1958 if (this.autosaveThread == null) 1959 { 1960 this.handleFileSuccess(true); 1961 this.ageStart = null; 1962 } 1963 else if (this.isModified()) 1964 { 1965 this.ui.scheduleSanityCheck(); 1966 this.ageStart = this.lastChanged; 1967 } 1968 }), mxUtils.bind(this, function(err) 1969 { 1970 this.handleFileError(err); 1971 })); 1972 } 1973 else 1974 { 1975 this.ageStart = null; 1976 1977 if ((!this.isAutosaveOptional() || !this.ui.editor.autosave) && 1978 !this.inConflictState) 1979 { 1980 this.addUnsavedStatus(); 1981 } 1982 } 1983}; 1984 1985/** 1986 * Returns true if the notification to update should be sent 1987 * together with the save request. 1988 */ 1989DrawioFile.prototype.isOptimisticSync = function() 1990{ 1991 return false; 1992}; 1993 1994/** 1995 * Creates a secret and token pair for writing a patch to the cache. 1996 */ 1997DrawioFile.prototype.createSecret = function(success) 1998{ 1999 var secret = Editor.guid(32); 2000 2001 if (this.sync != null && !this.isOptimisticSync()) 2002 { 2003 this.sync.createToken(secret, mxUtils.bind(this, function(token) 2004 { 2005 success(secret, token); 2006 }), mxUtils.bind(this, function() 2007 { 2008 success(secret); 2009 })); 2010 } 2011 else 2012 { 2013 success(secret); 2014 } 2015}; 2016 2017/** 2018 * Invokes sync and updates shadow document. 2019 */ 2020DrawioFile.prototype.fileSaving = function() 2021{ 2022 if (this.sync != null && this.isOptimisticSync()) 2023 { 2024 this.sync.fileSaving(); 2025 } 2026 2027 if (urlParams['test'] == '1') 2028 { 2029 EditorUi.debug('DrawioFile.fileSaving', [this]); 2030 } 2031}; 2032 2033DrawioFile.prototype.sendFileChanges = function() 2034{ 2035 try 2036 { 2037 if (this.p2pCollab != null && this.sync != null) 2038 { 2039 //TODO Should we check for modified? 2040 this.updateFileData(); //TODO Calling this function ealy could have side effects + overhead of calling it twice (here and in save) 2041 this.sync.sendFileChanges(this.ui.getPagesForNode( 2042 mxUtils.parseXml(this.getData()).documentElement), 2043 this.desc); 2044 2045 if (urlParams['test'] == '1') 2046 { 2047 EditorUi.debug('DrawioFile.sendFileChanges', [this]); 2048 } 2049 } 2050 } 2051 catch (e) 2052 { 2053 console.log(e); 2054 } 2055}; 2056 2057/** 2058 * Invokes sync and updates shadow document. 2059 */ 2060DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error, token) 2061{ 2062 this.lastSaved = new Date(); 2063 this.ageStart = null; 2064 2065 try 2066 { 2067 this.stats.saved++; 2068 this.inConflictState = false; 2069 this.invalidChecksum = false; 2070 2071 if (this.sync == null || this.isOptimisticSync()) 2072 { 2073 this.shadowData = savedData; 2074 this.shadowPages = null; 2075 2076 if (this.sync != null) 2077 { 2078 this.sync.lastModified = this.getLastModifiedDate(); 2079 this.sync.resetUpdateStatusThread(); 2080 } 2081 2082 if (success != null) 2083 { 2084 success(); 2085 } 2086 } 2087 else 2088 { 2089 this.sync.fileSaved(this.ui.getPagesForNode( 2090 mxUtils.parseXml(savedData).documentElement), 2091 lastDesc, success, error, token); 2092 } 2093 } 2094 catch (e) 2095 { 2096 this.inConflictState = true; 2097 this.invalidChecksum = true; 2098 this.descriptorChanged(); 2099 2100 if (error != null) 2101 { 2102 error(e); 2103 } 2104 2105 try 2106 { 2107 if (this.errorReportsEnabled) 2108 { 2109 this.sendErrorReport('Error in fileSaved', null, e); 2110 } 2111 else 2112 { 2113 var user = this.getCurrentUser(); 2114 var uid = (user != null) ? user.id : 'unknown'; 2115 2116 EditorUi.logError('Error in fileSaved', null, 2117 this.getMode() + '.' + this.getId(), 2118 uid, e); 2119 } 2120 } 2121 catch (e2) 2122 { 2123 // ignore 2124 } 2125 } 2126 2127 if (urlParams['test'] == '1') 2128 { 2129 EditorUi.debug('DrawioFile.fileSaved', [this]); 2130 } 2131}; 2132 2133/** 2134 * Adds the listener for automatically saving the diagram for local changes. 2135 */ 2136DrawioFile.prototype.autosave = function(delay, maxDelay, success, error) 2137{ 2138 if (this.lastAutosave == null) 2139 { 2140 this.lastAutosave = Date.now(); 2141 } 2142 2143 var tmp = (Date.now() - this.lastAutosave < maxDelay) ? delay : 0; 2144 this.clearAutosave(); 2145 2146 // Starts new timer or executes immediately if not unsaved for maxDelay 2147 var thread = window.setTimeout(mxUtils.bind(this, function() 2148 { 2149 this.lastAutosave = null; 2150 2151 if (this.autosaveThread == thread) 2152 { 2153 this.autosaveThread = null; 2154 } 2155 2156 // Workaround for duplicate save if UI is blocking 2157 // after save while pending autosave triggers 2158 if (this.isModified() && this.isAutosaveNow()) 2159 { 2160 var rev = this.isAutosaveRevision(); 2161 2162 if (rev) 2163 { 2164 this.lastAutosaveRevision = new Date().getTime(); 2165 } 2166 2167 this.save(rev, mxUtils.bind(this, function(resp) 2168 { 2169 this.autosaveCompleted(); 2170 2171 if (success != null) 2172 { 2173 success(resp); 2174 } 2175 }), mxUtils.bind(this, function(resp) 2176 { 2177 if (error != null) 2178 { 2179 error(resp); 2180 } 2181 })); 2182 } 2183 else 2184 { 2185 if (!this.isModified()) 2186 { 2187 this.ui.editor.setStatus(''); 2188 } 2189 2190 if (success != null) 2191 { 2192 success(null); 2193 } 2194 } 2195 }), tmp); 2196 2197 this.autosaveThread = thread; 2198}; 2199 2200/** 2201 * Returns true if an autosave is required at the time of execution. 2202 * This implementation returns true. 2203 */ 2204DrawioFile.prototype.isAutosaveNow = function() 2205{ 2206 return true; 2207}; 2208 2209/** 2210 * Hooks for subclassers after the autosave has completed. 2211 */ 2212DrawioFile.prototype.autosaveCompleted = function() { }; 2213 2214/** 2215 * Adds the listener for automatically saving the diagram for local changes. 2216 */ 2217DrawioFile.prototype.clearAutosave = function() 2218{ 2219 if (this.autosaveThread != null) 2220 { 2221 window.clearTimeout(this.autosaveThread); 2222 this.autosaveThread = null; 2223 } 2224}; 2225 2226/** 2227 * Returns the location as a new object. 2228 * @type mx.Point 2229 */ 2230DrawioFile.prototype.isAutosaveRevision = function() 2231{ 2232 var now = new Date().getTime(); 2233 2234 return (this.lastAutosaveRevision == null) || (now - this.lastAutosaveRevision) > this.maxAutosaveRevisionDelay; 2235}; 2236 2237/** 2238 * Translates this point by the given vector. 2239 * 2240 * @param {number} dx X-coordinate of the translation. 2241 * @param {number} dy Y-coordinate of the translation. 2242 */ 2243DrawioFile.prototype.descriptorChanged = function() 2244{ 2245 this.fireEvent(new mxEventObject('descriptorChanged')); 2246}; 2247 2248/** 2249 * Translates this point by the given vector. 2250 * 2251 * @param {number} dx X-coordinate of the translation. 2252 * @param {number} dy Y-coordinate of the translation. 2253 */ 2254DrawioFile.prototype.contentChanged = function() 2255{ 2256 this.fireEvent(new mxEventObject('contentChanged')); 2257}; 2258 2259/** 2260 * Returns the location as a new object. 2261 */ 2262DrawioFile.prototype.close = function(unloading) 2263{ 2264 this.updateFileData(); 2265 this.stats.closed++; 2266 2267 if (this.isAutosave() && this.isModified()) 2268 { 2269 this.save(this.isAutosaveRevision(), null, null, unloading); 2270 } 2271 2272 this.destroy(); 2273}; 2274 2275/** 2276 * Returns the location as a new object. 2277 */ 2278DrawioFile.prototype.hasSameExtension = function(title, newTitle) 2279{ 2280 if (title != null && newTitle != null) 2281 { 2282 var dot = title.lastIndexOf('.'); 2283 var ext = (dot > 0) ? title.substring(dot) : ''; 2284 dot = newTitle.lastIndexOf('.'); 2285 2286 return ext === ((dot > 0) ? newTitle.substring(dot) : ''); 2287 } 2288 2289 return title == newTitle; 2290}; 2291 2292/** 2293 * Removes the change listener. 2294 */ 2295DrawioFile.prototype.removeListeners = function() 2296{ 2297 if (this.changeListener != null) 2298 { 2299 this.ui.editor.graph.model.removeListener(this.changeListener); 2300 this.ui.editor.graph.removeListener(this.changeListener); 2301 this.ui.removeListener(this.changeListener); 2302 this.changeListener = null; 2303 } 2304}; 2305 2306/** 2307 * Stops any pending autosaves and removes all listeners. 2308 */ 2309DrawioFile.prototype.destroy = function() 2310{ 2311 this.clearAutosave(); 2312 this.removeListeners(); 2313 this.stats.destroyed++; 2314 2315 if (this.sync != null) 2316 { 2317 this.sync.destroy(); 2318 this.sync = null; 2319 } 2320}; 2321 2322/** 2323 * Are comments supported 2324 */ 2325DrawioFile.prototype.commentsSupported = function() 2326{ 2327 return false; //The default is false and files that support it must explicitly state that 2328}; 2329 2330/** 2331 * Show refresh button? 2332 */ 2333DrawioFile.prototype.commentsRefreshNeeded = function() 2334{ 2335 return true; 2336}; 2337 2338/** 2339 * Show save button? 2340 */ 2341DrawioFile.prototype.commentsSaveNeeded = function() 2342{ 2343 return false; 2344}; 2345 2346/** 2347 * Get comments of the file 2348 */ 2349DrawioFile.prototype.getComments = function(success, error) 2350{ 2351 success([]); //placeholder 2352}; 2353 2354/** 2355 * Add a comment to the file 2356 */ 2357DrawioFile.prototype.addComment = function(comment, success, error) 2358{ 2359 success(Date.now()); //placeholder 2360}; 2361 2362/** 2363 * Can add a reply to a reply 2364 */ 2365DrawioFile.prototype.canReplyToReplies = function() 2366{ 2367 return true; 2368}; 2369 2370/** 2371 * Can add comments (The permission to comment to this file) 2372 */ 2373DrawioFile.prototype.canComment = function() 2374{ 2375 return true; 2376}; 2377 2378/** 2379 * Get a new comment object 2380 */ 2381DrawioFile.prototype.newComment = function(content, user) 2382{ 2383 return new DrawioComment(this, null, content, Date.now(), Date.now(), false, user); 2384}; 2385