1/** 2 * Copyright (c) 2006-2020, JGraph Ltd 3 * Copyright (c) 2006-2020, draw.io AG 4 */ 5 6/** 7 * Constructs a new point for the optional x and y coordinates. If no 8 * coordinates are given, then the default values for <x> and <y> are used. 9 * @constructor 10 * @class Implements a basic 2D point. Known subclassers = {@link mxRectangle}. 11 * @param {number} x X-coordinate of the point. 12 * @param {number} y Y-coordinate of the point. 13 */ 14App = function(editor, container, lightbox) 15{ 16 EditorUi.call(this, editor, container, (lightbox != null) ? lightbox : 17 (urlParams['lightbox'] == '1' || (uiTheme == 'min' && 18 urlParams['chrome'] != '0'))); 19 20 // Logs unloading of window with modifications for Google Drive file 21 if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp) 22 { 23 window.onunload = mxUtils.bind(this, function() 24 { 25 var file = this.getCurrentFile(); 26 27 if (file != null && file.isModified()) 28 { 29 var evt = {category: 'DISCARD-FILE-' + file.getHash(), 30 action: ((file.savingFile) ? 'saving' : '') + 31 ((file.savingFile && file.savingFileTime != null) ? '_' + 32 Math.round((Date.now() - file.savingFileTime.getTime()) / 1000) : '') + 33 ((file.saveLevel != null) ? ('-sl_' + file.saveLevel) : '') + 34 '-age_' + ((file.ageStart != null) ? Math.round((Date.now() - file.ageStart.getTime()) / 1000) : 'x') + 35 ((this.editor.autosave) ? '' : '-nosave') + 36 ((file.isAutosave()) ? '' : '-noauto') + 37 '-open_' + ((file.opened != null) ? Math.round((Date.now() - file.opened.getTime()) / 1000) : 'x') + 38 '-save_' + ((file.lastSaved != null) ? Math.round((Date.now() - file.lastSaved.getTime()) / 1000) : 'x') + 39 '-change_' + ((file.lastChanged != null) ? Math.round((Date.now() - file.lastChanged.getTime()) / 1000) : 'x') + 40 '-alive_' + Math.round((Date.now() - App.startTime.getTime()) / 1000), 41 label: (file.sync != null) ? ('client_' + file.sync.clientId) : 'nosync'}; 42 43 if (file.constructor == DriveFile && file.desc != null && this.drive != null) 44 { 45 evt.label += ((this.drive.user != null) ? ('-user_' + this.drive.user.id) : '-nouser') + '-rev_' + 46 file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate + '-size_' + file.getSize() + 47 '-mime_' + file.desc.mimeType; 48 } 49 50 EditorUi.logEvent(evt); 51 } 52 }); 53 } 54 55 // Logs changes to autosave 56 this.editor.addListener('autosaveChanged', mxUtils.bind(this, function() 57 { 58 var file = this.getCurrentFile(); 59 60 if (file != null) 61 { 62 EditorUi.logEvent({category: ((this.editor.autosave) ? 'ON' : 'OFF') + 63 '-AUTOSAVE-FILE-' + file.getHash(), action: 'changed', 64 label: 'autosave_' + ((this.editor.autosave) ? 'on' : 'off')}); 65 } 66 })); 67 68 // Pre-fetches images 69 if (mxClient.IS_SVG) 70 { 71 mxGraph.prototype.warningImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAE7SURBVHjaYvz//z8DJQAggBjwGXDuHMP/tWuD/uPTCxBAOA0AaQRK/f/+XeJ/cbHlf1wGAAQQTgPu3QNLgfHSpZo4DQAIIKwGwGyH4e/fFbG6AiQJEEAs2Ew2NFzH8OOHBMO6dT/A/KCg7wxGRh+wuhQggDBcALMdFIAcHBxgDGJjcwVIIUAAYbhAUXEdVos4OO4DXcGBIQ4QQCguQPY7sgtgAYruCpAgQACx4LJdU1OCwctLEcyWlLwPJF+AXQE0EMUBAAEEdwF6yMOiD4RRY0QT7gqQAEAAseDzu6XldYYPH9DD4joQa8L5AAEENgWb7SBcXa0JDQMBrK4AcQACiAlfyOMCEFdAnAYQQEz4FLa0XGf4/v0H0IIPONUABBAjyBmMjIwMS5cK/L927QORbtBkaG29DtYLEGAAH6f7oq3Zc+kAAAAASUVORK5CYII='; 72 } 73 else 74 { 75 var img = new Image(); 76 img.src = mxGraph.prototype.warningImage.src; 77 } 78 79 // Global helper method to deal with popup blockers 80 window.openWindow = mxUtils.bind(this, function(url, pre, fallback) 81 { 82 if (urlParams['openInSameWin'] == '1' || navigator.standalone) 83 { 84 fallback(); 85 return; 86 } 87 88 var wnd = null; 89 90 try 91 { 92 wnd = window.open(url); 93 } 94 catch (e) 95 { 96 // ignore 97 } 98 99 if (wnd == null || wnd === undefined) 100 { 101 this.showDialog(new PopupDialog(this, url, pre, fallback).container, 320, 140, true, true); 102 } 103 else if (pre != null) 104 { 105 pre(); 106 } 107 }); 108 109 // Initial state for toolbar items is disabled 110 this.updateDocumentTitle(); 111 this.updateUi(); 112 113 // Global helper method to display error messages 114 window.showOpenAlert = mxUtils.bind(this, function(message) 115 { 116 // Cancel must be called before showing error message 117 if (window.openFile != null) 118 { 119 window.openFile.cancel(true); 120 } 121 122 this.handleError(message); 123 }); 124 125 // Handles opening files via drag and drop 126 if (!this.editor.chromeless || this.editor.editable) 127 { 128 this.addFileDropHandler([document]); 129 } 130 131 // Process the queue for waiting plugins 132 if (App.DrawPlugins != null) 133 { 134 for (var i = 0; i < App.DrawPlugins.length; i++) 135 { 136 try 137 { 138 App.DrawPlugins[i](this); 139 } 140 catch (e) 141 { 142 if (window.console != null) 143 { 144 console.log('Plugin Error:', e, App.DrawPlugins[i]); 145 } 146 } 147 finally 148 { 149 App.embedModePluginsCount--; 150 this.initializeEmbedMode(); 151 } 152 } 153 154 // Installs global callback for plugins 155 window.Draw.loadPlugin = mxUtils.bind(this, function(callback) 156 { 157 try 158 { 159 callback(this); 160 } 161 finally 162 { 163 App.embedModePluginsCount--; 164 this.initializeEmbedMode(); 165 } 166 }); 167 168 //Set a timeout in case a plugin doesn't load quickly or doesn't load at all 169 setTimeout(mxUtils.bind(this, function() 170 { 171 //Force finish loading if its not yet called 172 if (App.embedModePluginsCount > 0) 173 { 174 App.embedModePluginsCount = 0; 175 this.initializeEmbedMode(); 176 } 177 }), 5000); //5 sec timeout 178 } 179 180 this.load(); 181}; 182 183/** 184 * Timeout error 185 */ 186App.ERROR_TIMEOUT = 'timeout'; 187 188/** 189 * Busy error 190 */ 191App.ERROR_BUSY = 'busy'; 192 193/** 194 * Unknown error 195 */ 196App.ERROR_UNKNOWN = 'unknown'; 197 198/** 199 * Google drive mode 200 */ 201App.MODE_GOOGLE = 'google'; 202 203/** 204 * Dropbox mode 205 */ 206App.MODE_DROPBOX = 'dropbox'; 207 208/** 209 * OneDrive Mode 210 */ 211App.MODE_ONEDRIVE = 'onedrive'; 212 213/** 214 * Github Mode 215 */ 216App.MODE_GITHUB = 'github'; 217 218/** 219 * Gitlab mode 220 */ 221App.MODE_GITLAB = 'gitlab'; 222 223/** 224 * Device Mode 225 */ 226App.MODE_DEVICE = 'device'; 227 228/** 229 * Browser Mode 230 */ 231App.MODE_BROWSER = 'browser'; 232 233/** 234 * Trello App Mode 235 */ 236App.MODE_TRELLO = 'trello'; 237 238/** 239 * Notion App Mode 240 */ 241App.MODE_NOTION = 'notion'; 242 243/** 244 * Embed App Mode 245 */ 246App.MODE_EMBED = 'embed'; 247 248/** 249 * Atlas App Mode 250 */ 251App.MODE_ATLAS = 'atlas'; 252 253/** 254 * Sets the delay for autosave in milliseconds. Default is 2000. 255 */ 256App.DROPBOX_APPKEY = window.DRAWIO_DROPBOX_ID; 257 258/** 259 * Sets URL to load the Dropbox SDK from 260 */ 261App.DROPBOX_URL = window.DRAWIO_BASE_URL + '/js/dropbox/Dropbox-sdk.min.js'; 262 263/** 264 * Sets URL to load the Dropbox dropins JS from. 265 */ 266App.DROPINS_URL = 'https://www.dropbox.com/static/api/2/dropins.js'; 267 268/** 269 * OneDrive Client JS (file/folder picker). This is a slightly modified version to allow using accessTokens 270 * But it doesn't work for IE11, so we fallback to the original one 271 */ 272App.ONEDRIVE_URL = mxClient.IS_IE11? 'https://js.live.net/v7.2/OneDrive.js' : window.DRAWIO_BASE_URL + '/js/onedrive/OneDrive.js'; 273 274/** 275 * Trello URL 276 */ 277App.TRELLO_URL = 'https://api.trello.com/1/client.js'; 278 279/** 280 * Trello JQuery dependency 281 */ 282App.TRELLO_JQUERY_URL = window.DRAWIO_BASE_URL + '/js/jquery/jquery-3.3.1.min.js'; 283 284/** 285 * Specifies the key for the pusher project. 286 */ 287App.PUSHER_KEY = '1e756b07a690c5bdb054'; 288 289/** 290 * Specifies the key for the pusher project. 291 */ 292App.PUSHER_CLUSTER = 'eu'; 293 294/** 295 * Specifies the URL for the pusher API. 296 */ 297App.PUSHER_URL = 'https://js.pusher.com/4.3/pusher.min.js'; 298 299/** 300 * Socket.io library 301 */ 302App.SOCKET_IO_URL = window.DRAWIO_BASE_URL + '/js/socket.io/socket.io.min.js'; 303App.SIMPLE_PEER_URL = window.DRAWIO_BASE_URL + '/js/socket.io/simplepeer9.10.0.min.js'; 304App.SOCKET_IO_SRV = 'http://localhost:3030'; 305 306/** 307 * Google APIs to load. The realtime API is needed to notify collaborators of conversion 308 * of the realtime files, but after Dec 11 it's read-only and hence no longer needed. 309 */ 310App.GOOGLE_APIS = 'drive-share'; 311 312/** 313 * Function: authorize 314 * 315 * Authorizes the client, gets the userId and calls <open>. 316 */ 317App.startTime = new Date(); 318 319/** 320 * Defines plugin IDs for loading via p URL parameter. Update the table at 321 * https://www.diagrams.net/doc/faq/supported-url-parameters 322 */ 323App.pluginRegistry = {'4xAKTrabTpTzahoLthkwPNUn': 'plugins/explore.js', 324 'ex': 'plugins/explore.js', 'p1': 'plugins/p1.js', 325 'ac': 'plugins/connect.js', 'acj': 'plugins/connectJira.js', 326 'ac148': 'plugins/cConf-1-4-8.js', 'ac148cmnt': 'plugins/cConf-comments.js', 327 'voice': 'plugins/voice.js', 328 'tips': 'plugins/tooltips.js', 'svgdata': 'plugins/svgdata.js', 329 'electron': 'plugins/electron.js', 330 'number': 'plugins/number.js', 'sql': 'plugins/sql.js', 331 'props': 'plugins/props.js', 'text': 'plugins/text.js', 332 'anim': 'plugins/animation.js', 'update': 'plugins/update.js', 333 'trees': 'plugins/trees/trees.js', 'import': 'plugins/import.js', 334 'replay': 'plugins/replay.js', 'anon': 'plugins/anonymize.js', 335 'tr': 'plugins/trello.js', 'f5': 'plugins/rackF5.js', 336 'tickets': 'plugins/tickets.js', 'flow': 'plugins/flow.js', 337 'webcola': 'plugins/webcola/webcola.js', 'rnd': 'plugins/random.js', 338 'page': 'plugins/page.js', 'gd': 'plugins/googledrive.js', 339 'tags': 'plugins/tags.js'}; 340 341App.publicPlugin = [ 342 'ex', 343 'voice', 344 'tips', 345 'svgdata', 346 'number', 347 'sql', 348 'props', 349 'text', 350 'anim', 351 'update', 352 'trees', 353// 'import', 354 'replay', 355 'anon', 356 'tickets', 357 'flow', 358 'webcola', 359// 'rnd', 'page', 'gd', 360 'tags' 361]; 362 363/** 364 * Loads all given scripts and invokes onload after 365 * all scripts have finished loading. 366 */ 367App.loadScripts = function(scripts, onload) 368{ 369 var n = scripts.length; 370 371 for (var i = 0; i < scripts.length; i++) 372 { 373 mxscript(scripts[i], function() 374 { 375 if (--n == 0 && onload != null) 376 { 377 onload(); 378 } 379 }); 380 } 381}; 382 383/** 384 * Function: getStoredMode 385 * 386 * Returns the current mode. 387 */ 388App.getStoredMode = function() 389{ 390 var mode = null; 391 392 if (mode == null && isLocalStorage) 393 { 394 mode = localStorage.getItem('.mode'); 395 } 396 397 if (mode == null && typeof(Storage) != 'undefined') 398 { 399 var cookies = document.cookie.split(";"); 400 401 for (var i = 0; i < cookies.length; i++) 402 { 403 // Removes spaces around cookie 404 var cookie = mxUtils.trim(cookies[i]); 405 406 if (cookie.substring(0, 5) == 'MODE=') 407 { 408 mode = cookie.substring(5); 409 break; 410 } 411 } 412 413 if (mode != null && isLocalStorage) 414 { 415 // Moves to local storage 416 var expiry = new Date(); 417 expiry.setYear(expiry.getFullYear() - 1); 418 document.cookie = 'MODE=; expires=' + expiry.toUTCString(); 419 localStorage.setItem('.mode', mode); 420 } 421 } 422 423 return mode; 424}; 425 426/** 427 * Static Application initializer executed at load-time. 428 */ 429(function() 430{ 431 if (!mxClient.IS_CHROMEAPP) 432 { 433 if (urlParams['offline'] != '1') 434 { 435 // Switches to dropbox mode for db.draw.io 436 if (window.location.hostname == 'db.draw.io' && urlParams['mode'] == null) 437 { 438 urlParams['mode'] = 'dropbox'; 439 } 440 441 App.mode = urlParams['mode']; 442 } 443 444 if (App.mode == null) 445 { 446 // Stored mode overrides preferred mode 447 App.mode = App.getStoredMode(); 448 } 449 450 /** 451 * Lazy loading backends. 452 */ 453 if (window.mxscript != null) 454 { 455 // Loads gapi for all browsers but IE8 and below if not disabled or if enabled and in embed mode 456 if (urlParams['embed'] != '1') 457 { 458 if (typeof window.DriveClient === 'function') 459 { 460 if (urlParams['gapi'] != '0' && isSvgBrowser && 461 (document.documentMode == null || document.documentMode >= 10)) 462 { 463 // Immediately loads client 464 if (App.mode == App.MODE_GOOGLE || (urlParams['state'] != null && 465 window.location.hash == '') || (window.location.hash != null && 466 window.location.hash.substring(0, 2) == '#G')) 467 { 468 mxscript('https://apis.google.com/js/api.js'); 469 } 470 // Keeps lazy loading for fallback to authenticated Google file if not public in loadFile 471 else if (urlParams['chrome'] == '0' && (window.location.hash == null || 472 window.location.hash.substring(0, 45) !== '#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D')) 473 { 474 // Disables loading of client 475 window.DriveClient = null; 476 } 477 } 478 else 479 { 480 // Disables loading of client 481 window.DriveClient = null; 482 } 483 } 484 485 // Loads dropbox for all browsers but IE8 and below (no CORS) if not disabled or if enabled and in embed mode 486 // KNOWN: Picker does not work in IE11 (https://dropbox.zendesk.com/requests/1650781) 487 if (typeof window.DropboxClient === 'function') 488 { 489 if (urlParams['db'] != '0' && isSvgBrowser && 490 (document.documentMode == null || document.documentMode > 9)) 491 { 492 // Immediately loads client 493 if (App.mode == App.MODE_DROPBOX || (window.location.hash != null && 494 window.location.hash.substring(0, 2) == '#D')) 495 { 496 mxscript(App.DROPBOX_URL, function() 497 { 498 // Must load this after the dropbox SDK since they use the same namespace 499 mxscript(App.DROPINS_URL, null, 'dropboxjs', App.DROPBOX_APPKEY, true); 500 }); 501 } 502 else if (urlParams['chrome'] == '0') 503 { 504 window.DropboxClient = null; 505 } 506 } 507 else 508 { 509 // Disables loading of client 510 window.DropboxClient = null; 511 } 512 } 513 514 // Loads OneDrive for all browsers but IE6/IOS if not disabled or if enabled and in embed mode 515 if (typeof window.OneDriveClient === 'function') 516 { 517 if (urlParams['od'] != '0' && (navigator.userAgent == null || 518 navigator.userAgent.indexOf('MSIE') < 0 || document.documentMode >= 10)) 519 { 520 // Immediately loads client 521 if (App.mode == App.MODE_ONEDRIVE || (window.location.hash != null && 522 window.location.hash.substring(0, 2) == '#W')) 523 { 524 //Editor.oneDriveInlinePicker can be set with configuration which is done later, so load it all time 525 mxscript(App.ONEDRIVE_URL); 526 } 527 else if (urlParams['chrome'] == '0') 528 { 529 window.OneDriveClient = null; 530 } 531 } 532 else 533 { 534 // Disables loading of client 535 window.OneDriveClient = null; 536 } 537 } 538 539 // Loads Trello for all browsers but < IE10 if not disabled or if enabled and in embed mode 540 if (typeof window.TrelloClient === 'function') 541 { 542 if (urlParams['tr'] == '1' && isSvgBrowser && !mxClient.IS_IE11 && 543 (document.documentMode == null || document.documentMode >= 10)) 544 { 545 // Immediately loads client 546 if (App.mode == App.MODE_TRELLO || (window.location.hash != null && 547 window.location.hash.substring(0, 2) == '#T')) 548 { 549 mxscript(App.TRELLO_JQUERY_URL, function() 550 { 551 mxscript(App.TRELLO_URL); 552 }); 553 } 554 else if (urlParams['chrome'] == '0') 555 { 556 window.TrelloClient = null; 557 } 558 } 559 else 560 { 561 // Disables loading of client 562 window.TrelloClient = null; 563 } 564 } 565 } 566 } 567 } 568})(); 569 570/** 571 * Clears the PWA cache. 572 */ 573App.clearServiceWorker = function(success) 574{ 575 navigator.serviceWorker.getRegistrations().then(function(registrations) 576 { 577 if (registrations != null && registrations.length > 0) 578 { 579 for (var i = 0; i < registrations.length; i++) 580 { 581 registrations[i].unregister(); 582 } 583 584 if (success != null) 585 { 586 success(); 587 } 588 } 589 }); 590}; 591 592/** 593 * Program flow starts here. 594 * 595 * Optional callback is called with the app instance. 596 */ 597App.main = function(callback, createUi) 598{ 599 // Logs uncaught errors 600 window.onerror = function(message, url, linenumber, colno, err) 601 { 602 EditorUi.logError('Global: ' + ((message != null) ? message : ''), 603 url, linenumber, colno, err, null, true); 604 }; 605 606 // Blocks stand-alone mode for certain subdomains 607 if (window.top == window.self && 608 (/ac\.draw\.io$/.test(window.location.hostname) || 609 /ac-ent\.draw\.io$/.test(window.location.hostname) || 610 /aj\.draw\.io$/.test(window.location.hostname))) 611 { 612 document.body.innerHTML = '<div style="margin-top:10%;text-align:center;">Stand-alone mode not allowed for this domain.</div>'; 613 614 return; 615 } 616 617 // Removes info text in embed mode 618 if (urlParams['embed'] == '1' || urlParams['lightbox'] == '1') 619 { 620 var geInfo = document.getElementById('geInfo'); 621 622 if (geInfo != null) 623 { 624 geInfo.parentNode.removeChild(geInfo); 625 } 626 } 627 628 // Redirects to the latest AWS icons 629 if (document.referrer != null && urlParams['libs'] == 'aws3' && 630 document.referrer.substring(0, 42) == 'https://aws.amazon.com/architecture/icons/') 631 { 632 urlParams['libs'] = 'aws4'; 633 } 634 635 if (window.mxscript != null) 636 { 637 // Checks for script content changes to avoid CSP errors in production 638 if (urlParams['dev'] == '1' && CryptoJS != null && App.mode != App.MODE_DROPBOX && App.mode != App.MODE_TRELLO) 639 { 640 var scripts = document.getElementsByTagName('script'); 641 642 // Checks bootstrap script 643 if (scripts != null && scripts.length > 0) 644 { 645 var content = mxUtils.getTextContent(scripts[0]); 646 647 if (CryptoJS.MD5(content).toString() != 'b02227617087e21bd49f2faa15164112') 648 { 649 console.log('Change bootstrap script MD5 in the previous line:', CryptoJS.MD5(content).toString()); 650 alert('[Dev] Bootstrap script change requires update of CSP'); 651 } 652 } 653 654 // Checks main script 655 if (scripts != null && scripts.length > 1) 656 { 657 var content = mxUtils.getTextContent(scripts[scripts.length - 1]); 658 659 if (CryptoJS.MD5(content).toString() != 'd53805dd6f0bbba2da4966491ca0a505') 660 { 661 console.log('Change main script MD5 in the previous line:', CryptoJS.MD5(content).toString()); 662 alert('[Dev] Main script change requires update of CSP'); 663 } 664 } 665 } 666 667 try 668 { 669 // Removes PWA cache on www.draw.io to force use of new domain via redirect 670 if (Editor.enableServiceWorker && (urlParams['offline'] == '0' || 671 /www\.draw\.io$/.test(window.location.hostname) || 672 (urlParams['offline'] != '1' && urlParams['dev'] == '1'))) 673 { 674 App.clearServiceWorker(function() 675 { 676 if (urlParams['offline'] == '0') 677 { 678 alert('Cache cleared'); 679 } 680 }); 681 } 682 else if (Editor.enableServiceWorker) 683 { 684 // Runs as progressive web app if service workers are supported 685 navigator.serviceWorker.register('/service-worker.js'); 686 } 687 } 688 catch (e) 689 { 690 if (window.console != null) 691 { 692 console.error(e); 693 } 694 } 695 696 // Loads Pusher API 697 if (('ArrayBuffer' in window) && !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && 698 DrawioFile.SYNC == 'auto' && (urlParams['embed'] != '1' || urlParams['embedRT'] == '1') && urlParams['local'] != '1' && 699 (urlParams['chrome'] != '0' || urlParams['rt'] == '1') && 700 urlParams['stealth'] != '1' && urlParams['offline'] != '1') 701 { 702 // TODO: Check if async loading is fast enough 703 mxscript(App.PUSHER_URL); 704 705 if (urlParams['rtCursors'] == '1') 706 { 707 mxscript(App.SOCKET_IO_URL); 708 mxscript(App.SIMPLE_PEER_URL); 709 } 710 } 711 712 // Loads plugins 713 if (urlParams['plugins'] != '0' && urlParams['offline'] != '1') 714 { 715 // mxSettings is not yet initialized in configure mode, redirect parameter 716 // to p URL parameter in caller for plugins in embed mode 717 var plugins = (mxSettings.settings != null) ? mxSettings.getPlugins() : null; 718 719 // Configured plugins in embed mode with configure=1 URL should be loaded so we 720 // look ahead here and parse the config to fetch the list of custom plugins 721 if (mxSettings.settings == null && isLocalStorage && typeof(JSON) !== 'undefined') 722 { 723 try 724 { 725 var temp = JSON.parse(localStorage.getItem(mxSettings.key)); 726 727 if (temp != null) 728 { 729 plugins = temp.plugins; 730 } 731 } 732 catch (e) 733 { 734 // ignore 735 } 736 } 737 738 var temp = urlParams['p']; 739 App.initPluginCallback(); 740 741 if (temp != null) 742 { 743 // Mapping from key to URL in App.plugins 744 App.loadPlugins(temp.split(';')); 745 } 746 747 if (plugins != null && plugins.length > 0 && urlParams['plugins'] != '0') 748 { 749 // Loading plugins inside the asynchronous block below stops the page from loading so a 750 // hardcoded message for the warning dialog is used since the resources are loadd below 751 var warning = 'The page has requested to load the following plugin(s):\n \n {1}\n \n Would you like to load these plugin(s) now?\n \n NOTE : Only allow plugins to run if you fully understand the security implications of doing so.\n'; 752 var tmp = window.location.protocol + '//' + window.location.host; 753 var local = true; 754 755 for (var i = 0; i < plugins.length && local; i++) 756 { 757 if (plugins[i].charAt(0) != '/' && plugins[i].substring(0, tmp.length) != tmp) 758 { 759 local = false; 760 } 761 } 762 763 if (local || mxUtils.confirm(mxResources.replacePlaceholders(warning, [plugins.join('\n')]).replace(/\\n/g, '\n'))) 764 { 765 for (var i = 0; i < plugins.length; i++) 766 { 767 try 768 { 769 if (App.pluginsLoaded[plugins[i]] == null) 770 { 771 App.pluginsLoaded[plugins[i]] = true; 772 App.embedModePluginsCount++; 773 774 if (plugins[i].charAt(0) == '/') 775 { 776 plugins[i] = PLUGINS_BASE_PATH + plugins[i]; 777 } 778 779 mxscript(plugins[i]); 780 } 781 } 782 catch (e) 783 { 784 // ignore 785 } 786 } 787 } 788 } 789 } 790 791 // Loads gapi for all browsers but IE8 and below if not disabled or if enabled and in embed mode 792 // Special case: Cannot load in asynchronous code below 793 if (typeof window.DriveClient === 'function' && 794 (typeof gapi === 'undefined' && (((urlParams['embed'] != '1' && urlParams['gapi'] != '0') || 795 (urlParams['embed'] == '1' && urlParams['gapi'] == '1')) && isSvgBrowser && 796 isLocalStorage && (document.documentMode == null || document.documentMode >= 10)))) 797 { 798 mxscript('https://apis.google.com/js/api.js?onload=DrawGapiClientCallback', null, null, null, mxClient.IS_SVG); 799 } 800 // Disables client 801 else if (typeof window.gapi === 'undefined') 802 { 803 window.DriveClient = null; 804 } 805 } 806 807 /** 808 * Asynchronous MathJax extension. 809 */ 810 if (urlParams['math'] != '0') 811 { 812 Editor.initMath(); 813 } 814 815 function doLoad(bundle) 816 { 817 // Prefetches asynchronous requests so that below code runs synchronous 818 // Loading the correct bundle (one file) via the fallback system in mxResources. The stylesheet 819 // is compiled into JS in the build process and is only needed for local development. 820 mxUtils.getAll((urlParams['dev'] != '1') ? [bundle] : [bundle, 821 STYLE_PATH + '/default.xml'], function(xhr) 822 { 823 // Adds bundle text to resources 824 mxResources.parse(xhr[0].getText()); 825 826 // Configuration mode 827 if (isLocalStorage && localStorage != null && window.location.hash != null && 828 window.location.hash.substring(0, 9) == '#_CONFIG_') 829 { 830 try 831 { 832 var trustedPlugins = {}; 833 834 for (var key in App.pluginRegistry) 835 { 836 trustedPlugins[App.pluginRegistry[key]] = true; 837 } 838 839 // Only allows trusted plugins 840 function checkPlugins(plugins) 841 { 842 if (plugins != null) 843 { 844 for (var i = 0; i < plugins.length; i++) 845 { 846 if (!trustedPlugins[plugins[i]]) 847 { 848 throw new Error(mxResources.get('invalidInput') + ' "' + plugins[i]) + '"'; 849 } 850 } 851 } 852 853 return true; 854 }; 855 856 var value = JSON.parse(Graph.decompress(window.location.hash.substring(9))); 857 858 if (value != null && checkPlugins(value.plugins)) 859 { 860 EditorUi.debug('Setting configuration', JSON.stringify(value)); 861 862 if (value.merge != null) 863 { 864 var temp = localStorage.getItem(Editor.configurationKey); 865 866 if (temp != null) 867 { 868 869 try 870 { 871 var config = JSON.parse(temp); 872 873 for (var key in value.merge) 874 { 875 config[key] = value.merge[key]; 876 } 877 878 value = config; 879 } 880 catch (e) 881 { 882 window.location.hash = ''; 883 alert(e); 884 } 885 } 886 else 887 { 888 value = value.merge; 889 } 890 } 891 892 if (confirm(mxResources.get('configLinkWarn')) && 893 confirm(mxResources.get('configLinkConfirm'))) 894 { 895 localStorage.setItem(Editor.configurationKey, JSON.stringify(value)); 896 window.location.hash = ''; 897 window.location.reload(); 898 } 899 } 900 901 window.location.hash = ''; 902 } 903 catch (e) 904 { 905 window.location.hash = ''; 906 alert(e); 907 } 908 } 909 910 // Prepares themes with mapping from old default-style to old XML file 911 if (xhr.length > 1) 912 { 913 Graph.prototype.defaultThemes['default-style2'] = xhr[1].getDocumentElement(); 914 Graph.prototype.defaultThemes['darkTheme'] = xhr[1].getDocumentElement(); 915 } 916 917 // Main 918 function realMain() 919 { 920 var ui = (createUi != null) ? createUi() : new App(new Editor( 921 urlParams['chrome'] == '0' || uiTheme == 'min', 922 null, null, null, urlParams['chrome'] != '0')); 923 924 if (window.mxscript != null) 925 { 926 // Loads dropbox for all browsers but IE8 and below (no CORS) if not disabled or if enabled and in embed mode 927 // KNOWN: Picker does not work in IE11 (https://dropbox.zendesk.com/requests/1650781) 928 if (typeof window.DropboxClient === 'function' && 929 (window.Dropbox == null && window.DrawDropboxClientCallback != null && 930 (((urlParams['embed'] != '1' && urlParams['db'] != '0') || 931 (urlParams['embed'] == '1' && urlParams['db'] == '1')) && 932 isSvgBrowser && (document.documentMode == null || document.documentMode > 9)))) 933 { 934 mxscript(App.DROPBOX_URL, function() 935 { 936 // Must load this after the dropbox SDK since they use the same namespace 937 mxscript(App.DROPINS_URL, function() 938 { 939 DrawDropboxClientCallback(); 940 }, 'dropboxjs', App.DROPBOX_APPKEY); 941 }); 942 } 943 // Disables client 944 else if (typeof window.Dropbox === 'undefined' || typeof window.Dropbox.choose === 'undefined') 945 { 946 window.DropboxClient = null; 947 } 948 949 // Loads OneDrive for all browsers but IE6/IOS if not disabled or if enabled and in embed mode 950 if (typeof window.OneDriveClient === 'function' && 951 (typeof OneDrive === 'undefined' && window.DrawOneDriveClientCallback != null && 952 (((urlParams['embed'] != '1' && urlParams['od'] != '0') || (urlParams['embed'] == '1' && 953 urlParams['od'] == '1')) && (navigator.userAgent == null || 954 navigator.userAgent.indexOf('MSIE') < 0 || document.documentMode >= 10)))) 955 { 956 //Editor.oneDriveInlinePicker can be set with configuration which is done later, so load it all time 957 mxscript(App.ONEDRIVE_URL, window.DrawOneDriveClientCallback); 958 } 959 // Disables client 960 else if (typeof window.OneDrive === 'undefined') 961 { 962 window.OneDriveClient = null; 963 } 964 965 // Loads Trello for all browsers but < IE10 if not disabled or if enabled and in embed mode 966 if (typeof window.TrelloClient === 'function' && !mxClient.IS_IE11 && 967 typeof window.Trello === 'undefined' && window.DrawTrelloClientCallback != null && 968 urlParams['tr'] == '1' && (navigator.userAgent == null || 969 navigator.userAgent.indexOf('MSIE') < 0 || document.documentMode >= 10)) 970 { 971 mxscript(App.TRELLO_JQUERY_URL, function() 972 { 973 // Must load this after the dropbox SDK since they use the same namespace 974 mxscript(App.TRELLO_URL, function() 975 { 976 DrawTrelloClientCallback(); 977 }); 978 }); 979 } 980 // Disables client 981 else if (typeof window.Trello === 'undefined') 982 { 983 window.TrelloClient = null; 984 } 985 986 } 987 988 if (callback != null) 989 { 990 callback(ui); 991 } 992 993 /** 994 * For developers only 995 */ 996 if (urlParams['chrome'] != '0' && urlParams['test'] == '1') 997 { 998 EditorUi.debug('App.start', [ui, (new Date().getTime() - t0.getTime()) + 'ms']); 999 1000 if (urlParams['export'] != null) 1001 { 1002 EditorUi.debug('Export:', EXPORT_URL); 1003 } 1004 } 1005 }; 1006 1007 if (urlParams['dev'] == '1' || EditorUi.isElectronApp) //TODO check if we can remove these scripts loading from index.html 1008 { 1009 realMain(); 1010 } 1011 else 1012 { 1013 mxStencilRegistry.allowEval = false; 1014 App.loadScripts(['js/shapes-14-6-5.min.js', 'js/stencils.min.js', 1015 'js/extensions.min.js'], realMain); 1016 } 1017 }, function(xhr) 1018 { 1019 var st = document.getElementById('geStatus'); 1020 1021 if (st != null) 1022 { 1023 st.innerHTML = 'Error loading page. <a>Please try refreshing.</a>'; 1024 1025 // Tries reload with default resources in case any language resources were not available 1026 st.getElementsByTagName('a')[0].onclick = function() 1027 { 1028 mxLanguage = 'en'; 1029 doLoad(mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) || 1030 mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage)); 1031 }; 1032 } 1033 }); 1034 }; 1035 1036 function doMain() 1037 { 1038 // Optional override for autosaveDelay and defaultEdgeLength 1039 try 1040 { 1041 if (mxSettings.settings != null) 1042 { 1043 document.body.style.backgroundColor = (uiTheme == 'dark' || 1044 mxSettings.settings.darkMode) ? Editor.darkColor : '#ffffff'; 1045 1046 if (mxSettings.settings.autosaveDelay != null) 1047 { 1048 var val = parseInt(mxSettings.settings.autosaveDelay); 1049 1050 if (!isNaN(val) && val > 0) 1051 { 1052 DrawioFile.prototype.autosaveDelay = val; 1053 EditorUi.debug('Setting autosaveDelay', val); 1054 } 1055 else 1056 { 1057 EditorUi.debug('Invalid autosaveDelay', val); 1058 } 1059 } 1060 1061 if (mxSettings.settings.defaultEdgeLength != null) 1062 { 1063 var val = parseInt(mxSettings.settings.defaultEdgeLength); 1064 1065 if (!isNaN(val) && val > 0) 1066 { 1067 Graph.prototype.defaultEdgeLength = val; 1068 EditorUi.debug('Using defaultEdgeLength', val); 1069 } 1070 else 1071 { 1072 EditorUi.debug('Invalid defaultEdgeLength', val); 1073 } 1074 } 1075 } 1076 } 1077 catch (e) 1078 { 1079 if (window.console != null) 1080 { 1081 console.error(e); 1082 } 1083 } 1084 1085 // Prefetches default fonts with URLs 1086 if (Menus.prototype.defaultFonts != null) 1087 { 1088 for (var i = 0; i < Menus.prototype.defaultFonts.length; i++) 1089 { 1090 var value = Menus.prototype.defaultFonts[i]; 1091 1092 if (typeof value !== 'string' && 1093 value.fontFamily != null && 1094 value.fontUrl != null) 1095 { 1096 Graph.addFont(value.fontFamily, value.fontUrl); 1097 } 1098 } 1099 } 1100 1101 // Adds required resources (disables loading of fallback properties, this can only 1102 // be used if we know that all keys are defined in the language specific file) 1103 mxResources.loadDefaultBundle = false; 1104 doLoad(mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) || 1105 mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage)); 1106 }; 1107 1108 // Sends load event if configuration is requested and waits for configure message 1109 if (urlParams['configure'] == '1') 1110 { 1111 var op = window.opener || window.parent; 1112 1113 var configHandler = function(evt) 1114 { 1115 if (evt.source == op) 1116 { 1117 try 1118 { 1119 var data = JSON.parse(evt.data); 1120 1121 if (data != null && data.action == 'configure') 1122 { 1123 mxEvent.removeListener(window, 'message', configHandler); 1124 Editor.configure(data.config, true); 1125 mxSettings.load(); 1126 doMain(); 1127 } 1128 } 1129 catch (e) 1130 { 1131 if (window.console != null) 1132 { 1133 console.log('Error in configure message: ' + e, evt.data); 1134 } 1135 } 1136 } 1137 }; 1138 1139 // Receives XML message from opener and puts it into the graph 1140 mxEvent.addListener(window, 'message', configHandler); 1141 op.postMessage(JSON.stringify({event: 'configure'}), '*'); 1142 } 1143 else 1144 { 1145 if (Editor.config == null) 1146 { 1147 // Loads configuration from global scope or local storage 1148 if (window.DRAWIO_CONFIG != null) 1149 { 1150 try 1151 { 1152 EditorUi.debug('Using global configuration', window.DRAWIO_CONFIG); 1153 Editor.configure(window.DRAWIO_CONFIG); 1154 mxSettings.load(); 1155 } 1156 catch (e) 1157 { 1158 if (window.console != null) 1159 { 1160 console.error(e); 1161 } 1162 } 1163 } 1164 1165 // Loads configuration from local storage 1166 if (isLocalStorage && localStorage != null && urlParams['embed'] != '1') 1167 { 1168 var configData = localStorage.getItem(Editor.configurationKey); 1169 1170 if (configData != null) 1171 { 1172 try 1173 { 1174 configData = JSON.parse(configData); 1175 1176 if (configData != null) 1177 { 1178 EditorUi.debug('Using local configuration', configData); 1179 Editor.configure(configData); 1180 mxSettings.load(); 1181 } 1182 } 1183 catch (e) 1184 { 1185 if (window.console != null) 1186 { 1187 console.error(e); 1188 } 1189 } 1190 } 1191 } 1192 } 1193 1194 doMain(); 1195 } 1196}; 1197 1198//Extends EditorUi 1199mxUtils.extend(App, EditorUi); 1200 1201/** 1202 * Executes the first step for connecting to Google Drive. 1203 */ 1204App.prototype.defaultUserPicture = IMAGE_PATH + '/default-user.jpg'; 1205 1206/** 1207 * 1208 */ 1209App.prototype.shareImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowOTgwMTE3NDA3MjA2ODExODhDNkFGMDBEQkQ0RTgwOSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxMjU2NzdEMTcwRDIxMUUxQjc0MDkxRDhCNUQzOEFGRCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxMjU2NzdEMDcwRDIxMUUxQjc0MDkxRDhCNUQzOEFGRCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNjgwMTE3NDA3MjA2ODExODcxRkM4MUY1OTFDMjQ5OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNzgwMTE3NDA3MjA2ODExODhDNkFGMDBEQkQ0RTgwOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrM/fs0AAADgSURBVHjaYmDAA/7//88MwgzkAKDGFiD+BsQ/QWxSNaf9RwN37twpI8WAS+gGfP78+RpQSoRYA36iG/D379+vQClNdLVMOMz4gi7w79+/n0CKg1gD9qELvH379hzIHGK9oA508ieY8//8+fO5rq4uFCilRKwL1JmYmNhhHEZGRiZ+fn6Q2meEbDYG4u3/cYCfP38uA7kOm0ZOIJ7zn0jw48ePPiDFhmzArv8kgi9fvuwB+w5qwH9ykjswbFSZyM4sEMDPBDTlL5BxkFSd7969OwZ2BZKYGhDzkmjOJ4AAAwBhpRqGnEFb8QAAAABJRU5ErkJggg=='; 1210 1211/** 1212 * 1213 */ 1214App.prototype.chevronUpImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/chevron-up.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDg2NEE3NUY1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDg2NEE3NjA1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0ODY0QTc1RDUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0ODY0QTc1RTUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pg+qUokAAAAMUExURQAAANnZ2b+/v////5bgre4AAAAEdFJOU////wBAKqn0AAAAL0lEQVR42mJgRgMMRAswMKAKMDDARBjg8lARBoR6KImkH0wTbygT6YaS4DmAAAMAYPkClOEDDD0AAAAASUVORK5CYII='; 1215 1216/** 1217 * 1218 */ 1219App.prototype.chevronDownImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/chevron-down.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDg2NEE3NUI1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDg2NEE3NUM1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0ODY0QTc1OTUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0ODY0QTc1QTUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsCtve8AAAAMUExURQAAANnZ2b+/v////5bgre4AAAAEdFJOU////wBAKqn0AAAALUlEQVR42mJgRgMMRAkwQEXBNAOcBSPhclB1cNVwfcxI+vEZykSpoSR6DiDAAF23ApT99bZ+AAAAAElFTkSuQmCC'; 1220 1221/** 1222 * 1223 */ 1224App.prototype.formatShowImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/format-show.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdCREY5REY1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdCREY5RTA1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0JERjlERDU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0JERjlERTU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlnMQ/8AAAAJUExURQAAAP///3FxcTfTiAsAAAACdFJOU/8A5bcwSgAAACFJREFUeNpiYEQDDEQJMMABTAAixcQ00ALoDiPRcwABBgB6DADly9Yx8wAAAABJRU5ErkJggg=='; 1225 1226/** 1227 * 1228 */ 1229App.prototype.formatHideImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/format-hide.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdCREY5REI1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdCREY5REM1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0JERjlEOTU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0JERjlEQTU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqjT9SMAAAAGUExURQAAAP///6XZn90AAAACdFJOU/8A5bcwSgAAAB9JREFUeNpiYEQDDEQJMMABTAAmNdAC6A4j0XMAAQYAcbwA1Xvj1CgAAAAASUVORK5CYII='; 1230 1231/** 1232 * 1233 */ 1234App.prototype.fullscreenImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/fullscreen.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAABpJREFUCNdjgAAbGxAy4AEh5gNwBBGByoIBAIueBd12TUjqAAAAAElFTkSuQmCC'; 1235 1236/** 1237 * Interval to show dialog for unsaved data if autosave is on. 1238 * Default is 300000 (5 minutes). 1239 */ 1240App.prototype.warnInterval = 300000; 1241 1242/** 1243 * 1244 */ 1245App.prototype.compactMode = false; 1246 1247/** 1248 * 1249 */ 1250App.prototype.fullscreenMode = false; 1251 1252/** 1253 * Overriden UI settings depending on mode. 1254 */ 1255if (urlParams['embed'] != '1') 1256{ 1257 App.prototype.menubarHeight = 64; 1258} 1259else 1260{ 1261 App.prototype.footerHeight = 0; 1262} 1263 1264/** 1265 * Queue for loading plugins and wait for UI instance 1266 */ 1267App.initPluginCallback = function() 1268{ 1269 if (App.DrawPlugins == null) 1270 { 1271 // Workaround for need to load plugins now but wait for UI instance 1272 App.DrawPlugins = []; 1273 1274 // Global entry point for plugins is Draw.loadPlugin. This is the only 1275 // long-term supported solution for access to the EditorUi instance. 1276 window.Draw = new Object(); 1277 window.Draw.loadPlugin = function(callback) 1278 { 1279 App.DrawPlugins.push(callback); 1280 }; 1281 } 1282}; 1283 1284/** 1285 * 1286 */ 1287App.pluginsLoaded = {}; 1288App.embedModePluginsCount = 0; 1289 1290/** 1291 * Queue for loading plugins and wait for UI instance 1292 */ 1293App.loadPlugins = function(plugins, useInclude) 1294{ 1295 EditorUi.debug('Loading plugins', plugins); 1296 1297 for (var i = 0; i < plugins.length; i++) 1298 { 1299 if (plugins[i] != null && plugins[i].length > 0) 1300 { 1301 try 1302 { 1303 var url = PLUGINS_BASE_PATH + App.pluginRegistry[plugins[i]]; 1304 1305 if (url != null) 1306 { 1307 if (App.pluginsLoaded[url] == null) 1308 { 1309 App.pluginsLoaded[url] = true; 1310 App.embedModePluginsCount++; 1311 1312 if (typeof window.drawDevUrl === 'undefined') 1313 { 1314 if (useInclude) 1315 { 1316 mxinclude(url); 1317 } 1318 else 1319 { 1320 mxscript(url); 1321 } 1322 } 1323 else 1324 { 1325 if (useInclude) 1326 { 1327 mxinclude(url); 1328 } 1329 else 1330 { 1331 mxscript(drawDevUrl + url); 1332 } 1333 } 1334 } 1335 } 1336 else if (window.console != null) 1337 { 1338 console.log('Unknown plugin:', plugins[i]); 1339 } 1340 } 1341 catch (e) 1342 { 1343 if (window.console != null) 1344 { 1345 console.log('Error loading plugin:', plugins[i], e); 1346 } 1347 } 1348 } 1349 } 1350}; 1351 1352/** 1353 * Delay embed mode initialization until all plugins are loaded 1354 */ 1355App.prototype.initializeEmbedMode = function() 1356{ 1357 if (urlParams['embed'] == '1') 1358 { 1359 if (window.location.hostname == 'app.diagrams.net') 1360 { 1361 this.showBanner('EmbedDeprecationFooter', 'app.diagrams.net will stop working for embed mode. Please use embed.diagrams.net.'); 1362 } 1363 1364 if (App.embedModePluginsCount > 0 || this.initEmbedDone) 1365 { 1366 return; //Wait for plugins to load, or this is a duplicate call due to timeout 1367 } 1368 else 1369 { 1370 this.initEmbedDone = true; 1371 } 1372 1373 EditorUi.prototype.initializeEmbedMode.apply(this, arguments); 1374 } 1375}; 1376 1377/** 1378 * TODO: Define viewer protocol and implement new viewer style toolbar 1379 */ 1380App.prototype.initializeViewerMode = function() 1381{ 1382 var parent = window.opener || window.parent; 1383 1384 if (parent != null) 1385 { 1386 this.editor.graph.addListener(mxEvent.SIZE, mxUtils.bind(this, function() 1387 { 1388 parent.postMessage(JSON.stringify(this.createLoadMessage('size')), '*'); 1389 })); 1390 } 1391}; 1392 1393/** 1394 * Translates this point by the given vector. 1395 * 1396 * @param {number} dx X-coordinate of the translation. 1397 * @param {number} dy Y-coordinate of the translation. 1398 */ 1399App.prototype.init = function() 1400{ 1401 EditorUi.prototype.init.apply(this, arguments); 1402 1403 /** 1404 * Specifies the default filename. 1405 */ 1406 this.defaultLibraryName = mxResources.get('untitledLibrary'); 1407 1408 /** 1409 * Holds the listener for description changes. 1410 */ 1411 this.descriptorChangedListener = mxUtils.bind(this, this.descriptorChanged); 1412 1413 /** 1414 * Creates github client. 1415 */ 1416 this.gitHub = (!mxClient.IS_IE || document.documentMode == 10 || 1417 mxClient.IS_IE11 || mxClient.IS_EDGE) && 1418 (urlParams['gh'] != '0' && (urlParams['embed'] != '1' || 1419 urlParams['gh'] == '1')) ? new GitHubClient(this) : null; 1420 1421 if (this.gitHub != null) 1422 { 1423 this.gitHub.addListener('userChanged', mxUtils.bind(this, function() 1424 { 1425 this.updateUserElement(); 1426 this.restoreLibraries(); 1427 1428 this.showBanner('GithubFooter', 'Click to install GitHub app', mxUtils.bind(this, function() 1429 { 1430 this.openLink('https://github.com/apps/draw-io-app'); 1431 })); 1432 })); 1433 } 1434 1435 /** 1436 * Creates gitlab client. 1437 */ 1438 this.gitLab = (!mxClient.IS_IE || document.documentMode == 10 || 1439 mxClient.IS_IE11 || mxClient.IS_EDGE) && 1440 (urlParams['gl'] != '0' && (urlParams['embed'] != '1' || 1441 urlParams['gl'] == '1')) ? new GitLabClient(this) : null; 1442 1443 if (this.gitLab != null) 1444 { 1445 this.gitLab.addListener('userChanged', mxUtils.bind(this, function() 1446 { 1447 this.updateUserElement(); 1448 this.restoreLibraries(); 1449 })); 1450 } 1451 1452 /** 1453 * Creates notion client. 1454 */ 1455 this.notion = 1456 //TODO disabled by default 1457 /*(!mxClient.IS_IE || document.documentMode == 10 || 1458 mxClient.IS_IE11 || mxClient.IS_EDGE) && 1459 (urlParams['ntn'] != '0' && (urlParams['embed'] != '1' || 1460 urlParams['ntn'] == '1')) */ 1461 urlParams['ntn'] == '1' ? new NotionClient(this) : null; 1462 1463 if (this.notion != null) 1464 { 1465 this.notion.addListener('userChanged', mxUtils.bind(this, function() 1466 { 1467 this.updateUserElement(); 1468 this.restoreLibraries(); 1469 })); 1470 } 1471 1472 /** 1473 * Lazy-loading for individual backends 1474 */ 1475 if (urlParams['embed'] != '1' || urlParams['od'] == '1') 1476 { 1477 /** 1478 * Creates onedrive client if all required libraries are available. 1479 */ 1480 var initOneDriveClient = mxUtils.bind(this, function() 1481 { 1482 if (typeof OneDrive !== 'undefined') 1483 { 1484 /** 1485 * Holds the x-coordinate of the point. 1486 */ 1487 this.oneDrive = new OneDriveClient(this); 1488 1489 this.oneDrive.addListener('userChanged', mxUtils.bind(this, function() 1490 { 1491 this.updateUserElement(); 1492 this.restoreLibraries(); 1493 })); 1494 1495 // Notifies listeners of new client 1496 this.fireEvent(new mxEventObject('clientLoaded', 'client', this.oneDrive)); 1497 } 1498 else if (window.DrawOneDriveClientCallback == null) 1499 { 1500 window.DrawOneDriveClientCallback = initOneDriveClient; 1501 } 1502 }); 1503 1504 initOneDriveClient(); 1505 } 1506 1507 /** 1508 * Lazy-loading for Trello 1509 */ 1510 if (urlParams['embed'] != '1' || urlParams['tr'] == '1') 1511 { 1512 /** 1513 * Creates Trello client if all required libraries are available. 1514 */ 1515 var initTrelloClient = mxUtils.bind(this, function() 1516 { 1517 if (typeof window.Trello !== 'undefined') 1518 { 1519 try 1520 { 1521 this.trello = new TrelloClient(this); 1522 1523 //TODO we have no user info from Trello so we don't set a user 1524 this.trello.addListener('userChanged', mxUtils.bind(this, function() 1525 { 1526 this.updateUserElement(); 1527 this.restoreLibraries(); 1528 })); 1529 1530 // Notifies listeners of new client 1531 this.fireEvent(new mxEventObject('clientLoaded', 'client', this.trello)); 1532 } 1533 catch (e) 1534 { 1535 if (window.console != null) 1536 { 1537 console.error(e); 1538 } 1539 } 1540 } 1541 else if (window.DrawTrelloClientCallback == null) 1542 { 1543 window.DrawTrelloClientCallback = initTrelloClient; 1544 } 1545 }); 1546 1547 initTrelloClient(); 1548 } 1549 1550 /** 1551 * Creates drive client with all required libraries are available. 1552 */ 1553 if (urlParams['embed'] != '1' || urlParams['gapi'] == '1') 1554 { 1555 var initDriveClient = mxUtils.bind(this, function() 1556 { 1557 /** 1558 * Creates google drive client if all required libraries are available. 1559 */ 1560 if (typeof gapi !== 'undefined') 1561 { 1562 var doInit = mxUtils.bind(this, function() 1563 { 1564 this.drive = new DriveClient(this); 1565 1566 this.drive.addListener('userChanged', mxUtils.bind(this, function() 1567 { 1568 this.updateUserElement(); 1569 this.restoreLibraries(); 1570 this.checkLicense(); 1571 })) 1572 1573 // Notifies listeners of new client 1574 this.fireEvent(new mxEventObject('clientLoaded', 'client', this.drive)); 1575 }); 1576 1577 if (window.DrawGapiClientCallback != null) 1578 { 1579 gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + App.GOOGLE_APIS, doInit); 1580 1581 /** 1582 * Clears any callbacks. 1583 */ 1584 window.DrawGapiClientCallback = null; 1585 } 1586 else 1587 { 1588 doInit(); 1589 } 1590 } 1591 else if (window.DrawGapiClientCallback == null) 1592 { 1593 window.DrawGapiClientCallback = initDriveClient; 1594 } 1595 }); 1596 1597 initDriveClient(); 1598 } 1599 1600 if (urlParams['embed'] != '1' || urlParams['db'] == '1') 1601 { 1602 /** 1603 * Creates dropbox client if all required libraries are available. 1604 */ 1605 var initDropboxClient = mxUtils.bind(this, function() 1606 { 1607 if (typeof Dropbox === 'function' && typeof Dropbox.choose !== 'undefined') 1608 { 1609 /** 1610 * Clears dropbox client callback. 1611 */ 1612 window.DrawDropboxClientCallback = null; 1613 1614 /** 1615 * Holds the x-coordinate of the point. 1616 */ 1617 try 1618 { 1619 this.dropbox = new DropboxClient(this); 1620 1621 this.dropbox.addListener('userChanged', mxUtils.bind(this, function() 1622 { 1623 this.updateUserElement(); 1624 this.restoreLibraries(); 1625 })); 1626 1627 // Notifies listeners of new client 1628 this.fireEvent(new mxEventObject('clientLoaded', 'client', this.dropbox)); 1629 } 1630 catch (e) 1631 { 1632 if (window.console != null) 1633 { 1634 console.error(e); 1635 } 1636 } 1637 } 1638 else if (window.DrawDropboxClientCallback == null) 1639 { 1640 window.DrawDropboxClientCallback = initDropboxClient; 1641 } 1642 }); 1643 1644 initDropboxClient(); 1645 } 1646 1647 if (urlParams['embed'] != '1') 1648 { 1649 /** 1650 * Holds the background element. 1651 */ 1652 this.bg = this.createBackground(); 1653 document.body.appendChild(this.bg); 1654 this.diagramContainer.style.visibility = 'hidden'; 1655 this.formatContainer.style.visibility = 'hidden'; 1656 this.hsplit.style.display = 'none'; 1657 this.sidebarContainer.style.display = 'none'; 1658 this.sidebarFooterContainer.style.display = 'none'; 1659 1660 // Sets the initial mode 1661 if (urlParams['local'] == '1') 1662 { 1663 this.setMode(App.MODE_DEVICE); 1664 } 1665 else 1666 { 1667 this.mode = App.mode; 1668 } 1669 1670 // Add to Home Screen dialog for mobile devices 1671 if ('serviceWorker' in navigator && !this.editor.isChromelessView() && 1672 (mxClient.IS_ANDROID || mxClient.IS_IOS)) 1673 { 1674 window.addEventListener('beforeinstallprompt', mxUtils.bind(this, function(e) 1675 { 1676 this.showBanner('AddToHomeScreenFooter', mxResources.get('installApp'), function() 1677 { 1678 e.prompt(); 1679 }); 1680 })); 1681 } 1682 1683 if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOffline() && 1684 !mxClient.IS_ANDROID && !mxClient.IS_IOS && urlParams['open'] == null && 1685 (!this.editor.chromeless || this.editor.editable)) 1686 { 1687 this.editor.addListener('fileLoaded', mxUtils.bind(this, function() 1688 { 1689 var file = this.getCurrentFile(); 1690 var mode = (file != null) ? file.getMode() : null; 1691 1692 if (urlParams['extAuth'] != '1' && (mode == App.MODE_DEVICE || mode == App.MODE_BROWSER)) 1693 { 1694 this.showDownloadDesktopBanner(); 1695 } 1696 else if (urlParams['embed'] != '1' && this.getServiceName() == 'draw.io') 1697 1698 { 1699 // just app.diagrams.net users 1700 // this.showNameConfBanner(); 1701 } 1702 })); 1703 } 1704 1705 if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && urlParams['embed'] != '1' && DrawioFile.SYNC == 'auto' && 1706 urlParams['local'] != '1' && urlParams['stealth'] != '1' && !this.isOffline() && 1707 (!this.editor.chromeless || this.editor.editable)) 1708 { 1709 // Checks if the cache is alive 1710 var acceptResponse = true; 1711 1712 var timeoutThread = window.setTimeout(mxUtils.bind(this, function() 1713 { 1714 acceptResponse = false; 1715 1716 // Switches to manual sync if cache cannot be reached 1717 DrawioFile.SYNC = 'manual'; 1718 1719 var file = this.getCurrentFile(); 1720 1721 if (file != null && file.sync != null) 1722 { 1723 file.sync.destroy(); 1724 file.sync = null; 1725 1726 var status = mxUtils.htmlEntities(mxResources.get('timeout')); 1727 this.editor.setStatus('<div title="'+ status + 1728 '" class="geStatusAlert">' + status + '</div>'); 1729 } 1730 1731 EditorUi.logEvent({category: 'TIMEOUT-CACHE-CHECK', action: 'timeout', label: 408}); 1732 }), Editor.cacheTimeout); 1733 1734 var t0 = new Date().getTime(); 1735 1736 mxUtils.get(EditorUi.cacheUrl + '?alive', mxUtils.bind(this, function(req) 1737 { 1738 window.clearTimeout(timeoutThread); 1739 })); 1740 } 1741 } 1742 else if (this.menubar != null) 1743 { 1744 this.menubar.container.style.paddingTop = '0px'; 1745 } 1746 1747 this.updateHeader(); 1748 1749 if (this.menubar != null) 1750 { 1751 this.buttonContainer = document.createElement('div'); 1752 this.buttonContainer.style.display = 'inline-block'; 1753 this.buttonContainer.style.paddingRight = '48px'; 1754 this.buttonContainer.style.position = 'absolute'; 1755 this.buttonContainer.style.right = '0px'; 1756 1757 this.menubar.container.appendChild(this.buttonContainer); 1758 } 1759 1760 if ((uiTheme == 'atlas' || urlParams['atlas'] == '1') && this.menubar != null) 1761 { 1762 if (this.toggleElement != null) 1763 { 1764 this.toggleElement.click(); 1765 this.toggleElement.style.display = 'none'; 1766 } 1767 1768 this.icon = document.createElement('img'); 1769 this.icon.setAttribute('src', IMAGE_PATH + '/logo-flat-small.png'); 1770 this.icon.setAttribute('title', mxResources.get('draw.io')); 1771 this.icon.style.padding = urlParams['atlas'] == '1'? '7px' : '6px'; 1772 this.icon.style.cursor = 'pointer'; 1773 1774 mxEvent.addListener(this.icon, 'click', mxUtils.bind(this, function(evt) 1775 { 1776 this.appIconClicked(evt); 1777 })); 1778 1779 this.menubar.container.insertBefore(this.icon, this.menubar.container.firstChild); 1780 } 1781 1782 if (this.editor.graph.isViewer()) 1783 { 1784 this.initializeViewerMode(); 1785 } 1786}; 1787 1788/** 1789 * Schedules a sanity check. 1790 */ 1791App.prototype.scheduleSanityCheck = function() 1792{ 1793 if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && 1794 this.sanityCheckThread == null) 1795 { 1796 this.sanityCheckThread = window.setTimeout(mxUtils.bind(this, function() 1797 { 1798 this.sanityCheckThread = null; 1799 this.sanityCheck(); 1800 }), this.warnInterval); 1801 } 1802}; 1803 1804/** 1805 * Stops sanity checks. 1806 */ 1807App.prototype.stopSanityCheck = function() 1808{ 1809 if (this.sanityCheckThread != null) 1810 { 1811 window.clearTimeout(this.sanityCheckThread); 1812 this.sanityCheckThread = null; 1813 } 1814}; 1815 1816/** 1817 * Shows a warning after some time with unsaved changes and autosave. 1818 */ 1819App.prototype.sanityCheck = function() 1820{ 1821 var file = this.getCurrentFile(); 1822 1823 if (file != null && file.isModified() && file.isAutosave() && file.isOverdue()) 1824 { 1825 var evt = {category: 'WARN-FILE-' + file.getHash(), 1826 action: ((file.savingFile) ? 'saving' : '') + 1827 ((file.savingFile && file.savingFileTime != null) ? '_' + 1828 Math.round((Date.now() - file.savingFileTime.getTime()) / 1000) : '') + 1829 ((file.saveLevel != null) ? ('-sl_' + file.saveLevel) : '') + 1830 '-age_' + ((file.ageStart != null) ? Math.round((Date.now() - file.ageStart.getTime()) / 1000) : 'x') + 1831 ((this.editor.autosave) ? '' : '-nosave') + 1832 ((file.isAutosave()) ? '' : '-noauto') + 1833 '-open_' + ((file.opened != null) ? Math.round((Date.now() - file.opened.getTime()) / 1000) : 'x') + 1834 '-save_' + ((file.lastSaved != null) ? Math.round((Date.now() - file.lastSaved.getTime()) / 1000) : 'x') + 1835 '-change_' + ((file.lastChanged != null) ? Math.round((Date.now() - file.lastChanged.getTime()) / 1000) : 'x')+ 1836 '-alive_' + Math.round((Date.now() - App.startTime.getTime()) / 1000), 1837 label: (file.sync != null) ? ('client_' + file.sync.clientId) : 'nosync'}; 1838 1839 if (file.constructor == DriveFile && file.desc != null && this.drive != null) 1840 { 1841 evt.label += ((this.drive.user != null) ? ('-user_' + this.drive.user.id) : '-nouser') + '-rev_' + 1842 file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate + '-size_' + file.getSize() + 1843 '-mime_' + file.desc.mimeType; 1844 } 1845 1846 EditorUi.logEvent(evt); 1847 1848 var msg = mxResources.get('ensureDataSaved'); 1849 1850 if (file.lastSaved != null) 1851 { 1852 var str = this.timeSince(file.lastSaved); 1853 1854 if (str == null) 1855 { 1856 str = mxResources.get('lessThanAMinute'); 1857 } 1858 1859 msg = mxResources.get('lastSaved', [str]); 1860 } 1861 1862 // Resets possible stale state 1863 this.spinner.stop(); 1864 1865 this.showError(mxResources.get('unsavedChanges'), msg, mxResources.get('ignore'), 1866 mxUtils.bind(this, function() 1867 { 1868 this.hideDialog(); 1869 }), null, mxResources.get('save'), mxUtils.bind(this, function() 1870 { 1871 this.stopSanityCheck(); 1872 this.actions.get((this.mode == null || !file.isEditable()) ? 1873 'saveAs' : 'save').funct(); 1874 }), null, null, 360, 120, null, mxUtils.bind(this, function() 1875 { 1876 this.scheduleSanityCheck(); 1877 })); 1878 } 1879}; 1880 1881/** 1882 * Returns true if the current domain is for the new drive app. 1883 */ 1884App.prototype.isDriveDomain = function() 1885{ 1886 return urlParams['drive'] != '0' && 1887 (window.location.hostname == 'test.draw.io' || 1888 window.location.hostname == 'www.draw.io' || 1889 window.location.hostname == 'drive.draw.io' || 1890 window.location.hostname == 'app.diagrams.net' || 1891 window.location.hostname == 'jgraph.github.io'); 1892}; 1893 1894/** 1895 * Returns the pusher instance for notifications. Creates the instance of none exists. 1896 */ 1897App.prototype.getPusher = function() 1898{ 1899 if (this.pusher == null && typeof window.Pusher === 'function') 1900 { 1901 this.pusher = new Pusher(App.PUSHER_KEY, 1902 { 1903 cluster: App.PUSHER_CLUSTER, 1904 encrypted: true 1905 }); 1906 } 1907 1908 return this.pusher; 1909}; 1910 1911/** 1912 * Shows a footer to download the desktop version once per session. 1913 */ 1914App.prototype.showNameChangeBanner = function() 1915{ 1916 this.showBanner('DiagramsFooter', 'draw.io is now diagrams.net', mxUtils.bind(this, function() 1917 { 1918 this.openLink('https://www.diagrams.net/blog/move-diagrams-net'); 1919 })); 1920}; 1921 1922/** 1923 * Shows a footer to download the desktop version once per session. 1924 */ 1925App.prototype.showNameConfBanner = function() 1926{ 1927 this.showBanner('ConfFooter', 'Try draw.io for Confluence', mxUtils.bind(this, function() 1928 { 1929 this.openLink('https://marketplace.atlassian.com/apps/1210933/draw-io-diagrams-for-confluence'); 1930 }), true); 1931}; 1932 1933/** 1934 * Shows a footer to download the desktop version once per session. 1935 */ 1936App.prototype.showDownloadDesktopBanner = function() 1937{ 1938 this.showBanner('DesktopFooter', mxResources.get('downloadDesktop'), mxUtils.bind(this, function() 1939 { 1940 this.openLink('https://get.diagrams.net/'); 1941 })); 1942}; 1943 1944/** 1945 * Shows a footer to download the desktop version once per session. 1946 */ 1947App.prototype.showRatingBanner = function() 1948{ 1949 if (!this.bannerShowing && !this['hideBanner' + 'ratingFooter'] && 1950 (!isLocalStorage || mxSettings.settings == null || 1951 mxSettings.settings['close' + 'ratingFooter'] == null)) 1952 { 1953 var banner = document.createElement('div'); 1954 banner.style.cssText = 'position:absolute;bottom:10px;left:50%;max-width:90%;padding:18px 34px 12px 20px;' + 1955 'font-size:16px;font-weight:bold;white-space:nowrap;cursor:pointer;z-index:' + mxPopupMenu.prototype.zIndex + ';'; 1956 mxUtils.setPrefixedStyle(banner.style, 'box-shadow', '1px 1px 2px 0px #ddd'); 1957 mxUtils.setPrefixedStyle(banner.style, 'transform', 'translate(-50%,120%)'); 1958 mxUtils.setPrefixedStyle(banner.style, 'transition', 'all 1s ease'); 1959 banner.className = 'geBtn gePrimaryBtn'; 1960 1961 var img = document.createElement('img'); 1962 img.setAttribute('src', Dialog.prototype.closeImage); 1963 img.setAttribute('title', mxResources.get('close')); 1964 img.setAttribute('border', '0'); 1965 img.style.cssText = 'position:absolute;right:10px;top:12px;filter:invert(1);padding:6px;margin:-6px;cursor:default;'; 1966 banner.appendChild(img); 1967 1968 var star = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZ' + 1969 'XdvcmtzIENTM5jWRgMAAAQRdEVYdFhNTDpjb20uYWRvYmUueG1wADw/eHBhY2tldCBiZWdpbj0iICAgIiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8i' + 1970 'IHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1jMDM0IDQ2LjI3Mjk3NiwgU2F0IEphbiAyNyAyMDA3IDIyOjExOjQxICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDI' + 1971 'vMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZW' + 1972 'F0b3JUb29sPkFkb2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVEYXRlPjIwMDgtMDItMTdUMDI6MzY6NDVaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpNb2RpZ' + 1973 'nlEYXRlPjIwMDktMDMtMTdUMTQ6MTI6MDJaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOmRjPSJo' + 1974 'dHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo' + 1975 'gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIC' + 1976 'AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI' + 1977 'CAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIIImu8AAAAAVdEVYdENyZWF0aW9uIFRpbWUAMi8xNy8wOCCcqlgAAAHuSURBVDiNlZJBi1JRGIbfk+fc0ZuMXorJe4XujWoMdREaA23HICj6AQeLINr0C4I27ab2' + 1978 '7VqOI9+q/sH8gMDceG1RkIwgClEXFMbRc5zTZgZURmG+5fu9PN/7Hg6wZohoh4h21nn4uqXW+q0xZgzg+SrPlTXX73uet+26bp6ICpcGaK1fua57M5vN3tZav7gUgIiSqVTqcRAEm0EQbCaTyQoRXb3Iy4hoG8CT6XSaY4xtMMa' + 1979 'SQohMPp8v+r7vAEC3243CMGwqpfoApsaYE8uyfgM45ABOjDEvXdfNlMvlzFINAIDneY7neZVzvdlsDgaDQYtzfsjOIjtKqU+e5+0Wi0V3VV8ACMOw3+/3v3HOX0sp/7K53te11h/S6fRuoVAIhBAL76OUOm2320dRFH0VQuxJKf' + 1980 '8BAFu+UKvVvpRKpWe2bYt5fTweq0ajQUKIN1LK43N94SMR0Y1YLLYlhBBKqQUw51wkEol7WmuzoC8FuJtIJLaUUoii6Ljb7f4yxpz6vp9zHMe2bfvacDi8BeDHKkBuNps5rVbr52QyaVuW9ZExttHpdN73ej0/Ho+nADxYCdBaV' + 1981 '0aj0RGAz5ZlHUgpx2erR/V6/d1wOHwK4CGA/QsBnPN9AN+llH+WkqFare4R0QGAO/M6M8Ysey81/wGqa8MlVvHPNAAAAABJRU5ErkJggg=='; 1982 1983 mxUtils.write(banner, 'Please rate us'); 1984 document.body.appendChild(banner); 1985 1986 var star1 = document.createElement('img'); 1987 star1.setAttribute('border', '0'); 1988 star1.setAttribute('align', 'absmiddle'); 1989 star1.setAttribute('title', '1 star'); 1990 star1.setAttribute('style', 'margin-top:-6px;cursor:pointer;margin-left:8px;'); 1991 star1.setAttribute('src', star); 1992 banner.appendChild(star1); 1993 1994 var star2 = document.createElement('img'); 1995 star2.setAttribute('border', '0'); 1996 star2.setAttribute('align', 'absmiddle'); 1997 star2.setAttribute('title', '2 star'); 1998 star2.setAttribute('style', 'margin-top:-6px;margin-left:3px;cursor:pointer;'); 1999 star2.setAttribute('src', star); 2000 banner.appendChild(star2); 2001 2002 var star3 = document.createElement('img'); 2003 star3.setAttribute('border', '0'); 2004 star3.setAttribute('align', 'absmiddle'); 2005 star3.setAttribute('title', '3 star'); 2006 star3.setAttribute('style', 'margin-top:-6px;margin-left:3px;cursor:pointer;'); 2007 star3.setAttribute('src', star); 2008 banner.appendChild(star3); 2009 2010 var star4 = document.createElement('img'); 2011 star4.setAttribute('border', '0'); 2012 star4.setAttribute('align', 'absmiddle'); 2013 star4.setAttribute('title', '4 star'); 2014 star4.setAttribute('style', 'margin-top:-6px;margin-left:3px;cursor:pointer;'); 2015 star4.setAttribute('src', star); 2016 banner.appendChild(star4); 2017 2018 this.bannerShowing = true; 2019 2020 var onclose = mxUtils.bind(this, function() 2021 { 2022 if (banner.parentNode != null) 2023 { 2024 banner.parentNode.removeChild(banner); 2025 this.bannerShowing = false; 2026 2027 this['hideBanner' + 'ratingFooter'] = true; 2028 2029 if (isLocalStorage && mxSettings.settings != null) 2030 { 2031 mxSettings.settings['close' + 'ratingFooter'] = Date.now(); 2032 mxSettings.save(); 2033 } 2034 } 2035 }); 2036 2037 mxEvent.addListener(img, 'click', mxUtils.bind(this, function(e) 2038 { 2039 mxEvent.consume(e); 2040 onclose(); 2041 })); 2042 mxEvent.addListener(star1, 'click', mxUtils.bind(this, function(e) 2043 { 2044 mxEvent.consume(e); 2045 onclose(); 2046 })); 2047 mxEvent.addListener(star2, 'click', mxUtils.bind(this, function(e) 2048 { 2049 mxEvent.consume(e); 2050 onclose(); 2051 })); 2052 mxEvent.addListener(star3, 'click', mxUtils.bind(this, function(e) 2053 { 2054 mxEvent.consume(e); 2055 onclose(); 2056 })); 2057 mxEvent.addListener(star4, 'click', mxUtils.bind(this, function(e) 2058 { 2059 mxEvent.consume(e); 2060 window.open('https://marketplace.atlassian.com/apps/1210933/draw-io-diagrams-for-confluence?hosting=datacenter&tab=reviews'); 2061 onclose(); 2062 })); 2063 2064 var hide = mxUtils.bind(this, function() 2065 { 2066 mxUtils.setPrefixedStyle(banner.style, 'transform', 'translate(-50%,120%)'); 2067 2068 window.setTimeout(mxUtils.bind(this, function() 2069 { 2070 onclose(); 2071 }), 1000); 2072 }); 2073 2074 window.setTimeout(mxUtils.bind(this, function() 2075 { 2076 mxUtils.setPrefixedStyle(banner.style, 'transform', 'translate(-50%,0%)'); 2077 }), 500); 2078 2079 window.setTimeout(hide, 60000); 2080 } 2081}; 2082 2083/** 2084 * Checks license in the case of Google Drive storage. 2085 * IMPORTANT: Do not change this function without consulting 2086 * the privacy lead. No personal information must be sent. 2087 */ 2088App.prototype.checkLicense = function() 2089{ 2090 var driveUser = this.drive.getUser(); 2091 var email = (driveUser != null) ? driveUser.email : null; 2092 2093 if (!this.isOffline() && !this.editor.chromeless && email != null && driveUser.id != null) 2094 { 2095 // Only the domain and hashed user ID are transmitted. This code was reviewed and deemed 2096 // compliant by dbenson 2021-09-01. 2097 var at = email.lastIndexOf('@'); 2098 var domain = (at >= 0) ? email.substring(at + 1) : ''; 2099 var userId = Editor.crc32(driveUser.id); 2100 2101 // Timestamp is workaround for cached response in certain environments 2102 mxUtils.post('/license', 'domain=' + encodeURIComponent(domain) + '&id=' + encodeURIComponent(userId) + 2103 '&ts=' + new Date().getTime(), 2104 mxUtils.bind(this, function(req) 2105 { 2106 try 2107 { 2108 if (req.getStatus() >= 200 && req.getStatus() <= 299) 2109 { 2110 var value = req.getText(); 2111 2112 if (value.length > 0) 2113 { 2114 var lic = JSON.parse(value); 2115 2116 if (lic != null) 2117 { 2118 this.handleLicense(lic, domain); 2119 } 2120 } 2121 } 2122 } 2123 catch (e) 2124 { 2125 // ignore 2126 } 2127 })); 2128 } 2129}; 2130 2131/** 2132 * Returns true if the current domain is for the new drive app. 2133 */ 2134App.prototype.handleLicense = function(lic, domain) 2135{ 2136 if (lic != null && lic.plugins != null) 2137 { 2138 App.loadPlugins(lic.plugins.split(';'), true); 2139 } 2140}; 2141 2142/** 2143 * 2144 */ 2145App.prototype.getEditBlankXml = function() 2146{ 2147 var file = this.getCurrentFile(); 2148 2149 if (file != null && this.editor.isChromelessView() && this.editor.graph.isLightboxView()) 2150 { 2151 return file.getData(); 2152 } 2153 else 2154 { 2155 return this.getFileData(true); 2156 } 2157}; 2158 2159/** 2160 * Updates action states depending on the selection. 2161 */ 2162App.prototype.updateActionStates = function() 2163{ 2164 EditorUi.prototype.updateActionStates.apply(this, arguments); 2165 2166 this.actions.get('revisionHistory').setEnabled(this.isRevisionHistoryEnabled()); 2167}; 2168 2169/** 2170 * Adds the specified entry to the recent file list in local storage 2171 */ 2172App.prototype.addRecent = function(entry) 2173{ 2174 if (isLocalStorage && localStorage != null) 2175 { 2176 var recent = this.getRecent(); 2177 2178 if (recent == null) 2179 { 2180 recent = []; 2181 } 2182 else 2183 { 2184 for (var i = 0; i < recent.length; i++) 2185 { 2186 if (recent[i].id == entry.id) 2187 { 2188 recent.splice(i, 1); 2189 } 2190 } 2191 } 2192 2193 if (recent != null) 2194 { 2195 recent.unshift(entry); 2196 recent = recent.slice(0, 10); 2197 localStorage.setItem('.recent', JSON.stringify(recent)); 2198 } 2199 } 2200}; 2201 2202/** 2203 * Returns the recent file list from local storage 2204 */ 2205App.prototype.getRecent = function() 2206{ 2207 if (isLocalStorage && localStorage != null) 2208 { 2209 try 2210 { 2211 var recent = localStorage.getItem('.recent'); 2212 2213 if (recent != null) 2214 { 2215 return JSON.parse(recent); 2216 } 2217 } 2218 catch (e) 2219 { 2220 // ignore 2221 } 2222 2223 return null; 2224 } 2225}; 2226 2227/** 2228 * Clears the recent file list in local storage 2229 */ 2230App.prototype.resetRecent = function(entry) 2231{ 2232 if (isLocalStorage && localStorage != null) 2233 { 2234 try 2235 { 2236 localStorage.removeItem('.recent'); 2237 } 2238 catch (e) 2239 { 2240 // ignore 2241 } 2242 } 2243}; 2244 2245/** 2246 * Sets the onbeforeunload for the application 2247 */ 2248App.prototype.onBeforeUnload = function() 2249{ 2250 if (urlParams['embed'] == '1' && this.editor.modified) 2251 { 2252 return mxResources.get('allChangesLost'); 2253 } 2254 else 2255 { 2256 var file = this.getCurrentFile(); 2257 2258 if (file != null) 2259 { 2260 // KNOWN: Message is ignored by most browsers 2261 if (file.constructor == LocalFile && file.getHash() == '' && !file.isModified() && 2262 urlParams['nowarn'] != '1' && !this.isDiagramEmpty() && urlParams['url'] == null && 2263 !this.editor.isChromelessView() && file.fileHandle == null) 2264 { 2265 return mxResources.get('ensureDataSaved'); 2266 } 2267 else if (file.isModified()) 2268 { 2269 return mxResources.get('allChangesLost'); 2270 } 2271 else 2272 { 2273 file.close(true); 2274 } 2275 } 2276 } 2277}; 2278 2279/** 2280 * Translates this point by the given vector. 2281 * 2282 * @param {number} dx X-coordinate of the translation. 2283 * @param {number} dy Y-coordinate of the translation. 2284 */ 2285App.prototype.updateDocumentTitle = function() 2286{ 2287 if (!this.editor.graph.isLightboxView()) 2288 { 2289 var title = this.editor.appName; 2290 var file = this.getCurrentFile(); 2291 2292 if (this.isOfflineApp()) 2293 { 2294 title += ' app'; 2295 } 2296 2297 if (file != null) 2298 { 2299 var filename = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename; 2300 title = filename + ' - ' + title; 2301 } 2302 2303 if (document.title != title) 2304 { 2305 document.title = title; 2306 var graph = this.editor.graph; 2307 graph.invalidateDescendantsWithPlaceholders(graph.model.getRoot()); 2308 graph.view.validate(); 2309 } 2310 } 2311}; 2312 2313/** 2314 * Returns a thumbnail of the current file. 2315 */ 2316App.prototype.getThumbnail = function(width, fn) 2317{ 2318 var result = false; 2319 2320 try 2321 { 2322 var acceptResponse = true; 2323 2324 var timeoutThread = window.setTimeout(mxUtils.bind(this, function() 2325 { 2326 acceptResponse = false; 2327 fn(null); 2328 }), this.timeout); 2329 2330 var success = mxUtils.bind(this, function(canvas) 2331 { 2332 window.clearTimeout(timeoutThread); 2333 2334 if (acceptResponse) 2335 { 2336 fn(canvas); 2337 } 2338 }); 2339 2340 if (this.thumbImageCache == null) 2341 { 2342 this.thumbImageCache = new Object(); 2343 } 2344 2345 var graph = this.editor.graph; 2346 var bgImg = graph.backgroundImage; 2347 2348 // Exports PNG for first page while other page is visible by creating a graph 2349 // LATER: Add caching for the graph or SVG while not on first page 2350 // To avoid refresh during save dark theme uses separate graph instance 2351 var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme'; 2352 2353 if (this.pages != null && (darkTheme || this.currentPage != this.pages[0])) 2354 { 2355 var graphGetGlobalVariable = graph.getGlobalVariable; 2356 graph = this.createTemporaryGraph(graph.getStylesheet()); 2357 graph.setBackgroundImage = this.editor.graph.setBackgroundImage; 2358 var page = this.pages[0]; 2359 2360 if (this.currentPage == page) 2361 { 2362 graph.setBackgroundImage(bgImg); 2363 } 2364 else if (page.viewState != null && page.viewState != null) 2365 { 2366 bgImg = page.viewState.backgroundImage; 2367 graph.setBackgroundImage(bgImg); 2368 } 2369 2370 graph.getGlobalVariable = function(name) 2371 { 2372 if (name == 'page') 2373 { 2374 return page.getName(); 2375 } 2376 else if (name == 'pagenumber') 2377 { 2378 return 1; 2379 } 2380 2381 return graphGetGlobalVariable.apply(this, arguments); 2382 }; 2383 2384 graph.getGlobalVariable = graphGetGlobalVariable; 2385 document.body.appendChild(graph.container); 2386 graph.model.setRoot(page.root); 2387 } 2388 2389 // Uses client-side canvas export 2390 if (mxClient.IS_CHROMEAPP || this.useCanvasForExport) 2391 { 2392 this.editor.exportToCanvas(mxUtils.bind(this, function(canvas) 2393 { 2394 try 2395 { 2396 // Removes temporary graph from DOM 2397 if (graph != this.editor.graph && graph.container.parentNode != null) 2398 { 2399 graph.container.parentNode.removeChild(graph.container); 2400 } 2401 } 2402 catch (e) 2403 { 2404 canvas = null; 2405 } 2406 2407 success(canvas); 2408 }), width, this.thumbImageCache, '#ffffff', function() 2409 { 2410 // Continues with null in error case 2411 success(); 2412 }, null, null, null, null, null, null, graph, null, null, null, 2413 null, 'diagram', null); 2414 2415 result = true; 2416 } 2417 else if (this.canvasSupported && this.getCurrentFile() != null) 2418 { 2419 var canvas = document.createElement('canvas'); 2420 var bounds = graph.getGraphBounds(); 2421 var t = graph.view.translate; 2422 var s = graph.view.scale; 2423 2424 if (bgImg != null) 2425 { 2426 bounds = mxRectangle.fromRectangle(bounds); 2427 bounds.add(new mxRectangle( 2428 (t.x + bgImg.x) * s, (t.y + bgImg.y) * s, 2429 bgImg.width * s, bgImg.height * s)); 2430 } 2431 2432 var scale = width / bounds.width; 2433 2434 // Limits scale to 1 or 2 * width / height 2435 scale = Math.min(1, Math.min((width * 3) / (bounds.height * 4), scale)); 2436 2437 var x0 = Math.floor(bounds.x); 2438 var y0 = Math.floor(bounds.y); 2439 2440 canvas.setAttribute('width', Math.ceil(scale * (bounds.width + 4))); 2441 canvas.setAttribute('height', Math.ceil(scale * (bounds.height + 4))); 2442 2443 var ctx = canvas.getContext('2d'); 2444 2445 // Configures the canvas 2446 ctx.scale(scale, scale); 2447 ctx.translate(-x0, -y0); 2448 2449 // Paint white background instead of transparent 2450 var bg = graph.background; 2451 2452 if (bg == null || bg == '' || bg == mxConstants.NONE) 2453 { 2454 bg = '#ffffff'; 2455 } 2456 2457 // Paints background 2458 ctx.save(); 2459 ctx.fillStyle = bg; 2460 ctx.fillRect(x0, y0, Math.ceil(bounds.width + 4), Math.ceil(bounds.height + 4)); 2461 ctx.restore(); 2462 2463 // Paints background image 2464 if (bgImg != null) 2465 { 2466 var img = new Image(); 2467 img.src = bgImg.src; 2468 2469 ctx.drawImage(img, bgImg.x * scale, bgImg.y * scale, 2470 bgImg.width * scale, bgImg.height * scale); 2471 } 2472 2473 var htmlCanvas = new mxJsCanvas(canvas); 2474 2475 // NOTE: htmlCanvas passed into async canvas is only used for image 2476 // and canvas caching (canvas caching not used in this case as we do 2477 // not render text). To reuse that cache via the thumbImageCache we 2478 // pass that into the async canvas and override the image cache in 2479 // the newly created html canvas with that of the thumbImageCache. 2480 // LATER: Is clear thumbImageCache needed if file changes? 2481 var asynCanvas = new mxAsyncCanvas(this.thumbImageCache); 2482 htmlCanvas.images = this.thumbImageCache.images; 2483 2484 // Render graph 2485 var imgExport = new mxImageExport(); 2486 2487 imgExport.drawShape = function(state, canvas) 2488 { 2489 if (state.shape instanceof mxShape && state.shape.checkBounds()) 2490 { 2491 canvas.save(); 2492 canvas.translate(0.5, 0.5); 2493 state.shape.paint(canvas); 2494 canvas.translate(-0.5, -0.5); 2495 canvas.restore(); 2496 } 2497 }; 2498 2499 imgExport.drawText = function(state, canvas) 2500 { 2501 // No text output for thumbnails 2502 }; 2503 2504 imgExport.drawState(graph.getView().getState(graph.model.root), asynCanvas); 2505 2506 asynCanvas.finish(mxUtils.bind(this, function() 2507 { 2508 try 2509 { 2510 imgExport.drawState(graph.getView().getState(graph.model.root), htmlCanvas); 2511 2512 // Removes temporary graph from DOM 2513 if (graph != this.editor.graph && graph.container.parentNode != null) 2514 { 2515 graph.container.parentNode.removeChild(graph.container); 2516 } 2517 } 2518 catch (e) 2519 { 2520 canvas = null; 2521 } 2522 2523 success(canvas); 2524 })); 2525 2526 result = true; 2527 } 2528 } 2529 catch (e) 2530 { 2531 result = false; 2532 2533 // Removes temporary graph from DOM 2534 if (graph != null && graph != this.editor.graph && graph.container.parentNode != null) 2535 { 2536 graph.container.parentNode.removeChild(graph.container); 2537 } 2538 } 2539 2540 if (!result) 2541 { 2542 window.clearTimeout(timeoutThread); 2543 } 2544 2545 return result; 2546}; 2547 2548/** 2549 * Translates this point by the given vector. 2550 * 2551 * @param {number} dx X-coordinate of the translation. 2552 * @param {number} dy Y-coordinate of the translation. 2553 */ 2554App.prototype.createBackground = function() 2555{ 2556 var bg = this.createDiv('background'); 2557 bg.style.position = 'absolute'; 2558 bg.style.background = 'white'; 2559 bg.style.left = '0px'; 2560 bg.style.top = '0px'; 2561 bg.style.bottom = '0px'; 2562 bg.style.right = '0px'; 2563 2564 mxUtils.setOpacity(bg, 100); 2565 2566 return bg; 2567}; 2568 2569/** 2570 * Translates this point by the given vector. 2571 * 2572 * @param {number} dx X-coordinate of the translation. 2573 * @param {number} dy Y-coordinate of the translation. 2574 */ 2575(function() 2576{ 2577 var editorUiSetMode = EditorUi.prototype.setMode; 2578 2579 App.prototype.setMode = function(mode, remember) 2580 { 2581 editorUiSetMode.apply(this, arguments); 2582 2583 // Note: UseLocalStorage affects the file dialogs 2584 // and should not be modified if mode is undefined 2585 if (this.mode != null) 2586 { 2587 Editor.useLocalStorage = this.mode == App.MODE_BROWSER; 2588 } 2589 2590 if (this.appIcon != null) 2591 { 2592 var file = this.getCurrentFile(); 2593 mode = (file != null) ? file.getMode() : mode; 2594 2595 if (mode == App.MODE_GOOGLE) 2596 { 2597 this.appIcon.setAttribute('title', mxResources.get('openIt', [mxResources.get('googleDrive')])); 2598 this.appIcon.style.cursor = 'pointer'; 2599 } 2600 else if (mode == App.MODE_DROPBOX) 2601 { 2602 this.appIcon.setAttribute('title', mxResources.get('openIt', [mxResources.get('dropbox')])); 2603 this.appIcon.style.cursor = 'pointer'; 2604 } 2605 else if (mode == App.MODE_ONEDRIVE) 2606 { 2607 this.appIcon.setAttribute('title', mxResources.get('openIt', [mxResources.get('oneDrive')])); 2608 this.appIcon.style.cursor = 'pointer'; 2609 } 2610 else 2611 { 2612 this.appIcon.removeAttribute('title'); 2613 this.appIcon.style.cursor = (mode == App.MODE_DEVICE) ? 'pointer' : 'default'; 2614 } 2615 } 2616 2617 if (remember) 2618 { 2619 try 2620 { 2621 if (isLocalStorage) 2622 { 2623 localStorage.setItem('.mode', mode); 2624 } 2625 else if (typeof(Storage) != 'undefined') 2626 { 2627 var expiry = new Date(); 2628 expiry.setYear(expiry.getFullYear() + 1); 2629 document.cookie = 'MODE=' + mode + '; expires=' + expiry.toUTCString(); 2630 } 2631 } 2632 catch (e) 2633 { 2634 // ignore possible access denied 2635 } 2636 } 2637 }; 2638})(); 2639 2640/** 2641 * Function: authorize 2642 * 2643 * Authorizes the client, gets the userId and calls <open>. 2644 */ 2645App.prototype.appIconClicked = function(evt) 2646{ 2647 if (mxEvent.isAltDown(evt)) 2648 { 2649 this.showSplash(true); 2650 } 2651 else 2652 { 2653 var file = this.getCurrentFile(); 2654 var mode = (file != null) ? file.getMode() : null; 2655 2656 if (mode == App.MODE_GOOGLE) 2657 { 2658 if (file != null && file.desc != null && file.desc.parents != null && 2659 file.desc.parents.length > 0 && !mxEvent.isShiftDown(evt)) 2660 { 2661 // Opens containing folder 2662 this.openLink('https://drive.google.com/drive/folders/' + file.desc.parents[0].id); 2663 } 2664 else if (file != null && file.getId() != null) 2665 { 2666 this.openLink('https://drive.google.com/open?id=' + file.getId()); 2667 } 2668 else 2669 { 2670 this.openLink('https://drive.google.com/?authuser=0'); 2671 } 2672 } 2673 else if (mode == App.MODE_ONEDRIVE) 2674 { 2675 if (file != null && file.meta != null && file.meta.webUrl != null) 2676 { 2677 var url = file.meta.webUrl; 2678 var name = encodeURIComponent(file.meta.name); 2679 2680 if (url.substring(url.length - name.length, url.length) == name) 2681 { 2682 url = url.substring(0, url.length - name.length); 2683 } 2684 2685 this.openLink(url); 2686 } 2687 else 2688 { 2689 this.openLink('https://onedrive.live.com/'); 2690 } 2691 } 2692 else if (mode == App.MODE_DROPBOX) 2693 { 2694 if (file != null && file.stat != null && file.stat.path_display != null) 2695 { 2696 var url = 'https://www.dropbox.com/home/Apps/drawio' + file.stat.path_display; 2697 2698 if (!mxEvent.isShiftDown(evt)) 2699 { 2700 url = url.substring(0, url.length - file.stat.name.length); 2701 } 2702 2703 this.openLink(url); 2704 } 2705 else 2706 { 2707 this.openLink('https://www.dropbox.com/'); 2708 } 2709 } 2710 else if (mode == App.MODE_TRELLO) 2711 { 2712 this.openLink('https://trello.com/'); 2713 } 2714 else if (mode == App.MODE_NOTION) 2715 { 2716 this.openLink('https://www.notion.so/'); 2717 } 2718 else if (mode == App.MODE_GITHUB) 2719 { 2720 if (file != null && file.constructor == GitHubFile) 2721 { 2722 this.openLink(file.meta.html_url); 2723 } 2724 else 2725 { 2726 this.openLink('https://github.com/'); 2727 } 2728 } 2729 else if (mode == App.MODE_GITLAB) 2730 { 2731 if (file != null && file.constructor == GitLabFile) 2732 { 2733 this.openLink(file.meta.html_url); 2734 } 2735 else 2736 { 2737 this.openLink(DRAWIO_GITLAB_URL); 2738 } 2739 } 2740 else if (mode == App.MODE_DEVICE) 2741 { 2742 this.openLink('https://get.draw.io/'); 2743 } 2744 } 2745 2746 mxEvent.consume(evt); 2747}; 2748 2749/** 2750 * Function: authorize 2751 * 2752 * Authorizes the client, gets the userId and calls <open>. 2753 */ 2754App.prototype.clearMode = function() 2755{ 2756 if (isLocalStorage) 2757 { 2758 localStorage.removeItem('.mode'); 2759 } 2760 else if (typeof(Storage) != 'undefined') 2761 { 2762 var expiry = new Date(); 2763 expiry.setYear(expiry.getFullYear() - 1); 2764 document.cookie = 'MODE=; expires=' + expiry.toUTCString(); 2765 } 2766}; 2767 2768/** 2769 * Translates this point by the given vector. 2770 * 2771 * @param {number} dx X-coordinate of the translation. 2772 * @param {number} dy Y-coordinate of the translation. 2773 */ 2774App.prototype.getDiagramId = function() 2775{ 2776 var id = window.location.hash; 2777 2778 // Strips the hash sign 2779 if (id != null && id.length > 0) 2780 { 2781 id = id.substring(1); 2782 } 2783 2784 // Workaround for Trello client appending data after hash 2785 if (id != null && id.length > 1 && id.charAt(0) == 'T') 2786 { 2787 var idx = id.indexOf('#'); 2788 2789 if (idx > 0) 2790 { 2791 id = id.substring(0, idx); 2792 } 2793 } 2794 2795 return id; 2796}; 2797 2798/** 2799 * Opens any file specified in the URL parameters. 2800 */ 2801App.prototype.open = function() 2802{ 2803 // Cross-domain window access is not allowed in FF, so if we 2804 // were opened from another domain then this will fail. 2805 try 2806 { 2807 // If the create URL param is used in embed mode then 2808 // we try to open the XML from window.opener[value]. 2809 // Use this for embedding via tab to bypass the timing 2810 // issues when passing messages without onload event. 2811 if (window.opener != null) 2812 { 2813 var value = urlParams['create']; 2814 2815 if (value != null) 2816 { 2817 value = decodeURIComponent(value); 2818 } 2819 2820 if (value != null && value.length > 0 && value.substring(0, 7) != 'http://' && 2821 value.substring(0, 8) != 'https://') 2822 { 2823 var doc = mxUtils.parseXml(window.opener[value]); 2824 this.editor.setGraphXml(doc.documentElement); 2825 } 2826 else if (window.opener.openFile != null) 2827 { 2828 window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml, filename, temp) 2829 { 2830 this.spinner.stop(); 2831 2832 if (filename == null) 2833 { 2834 var title = urlParams['title']; 2835 temp = true; 2836 2837 if (title != null) 2838 { 2839 filename = decodeURIComponent(title); 2840 } 2841 else 2842 { 2843 filename = this.defaultFilename; 2844 } 2845 } 2846 2847 // Replaces PNG with XML extension 2848 var dot = (!this.useCanvasForExport) ? filename.substring(filename.length - 4) == '.png' : -1; 2849 2850 if (dot > 0) 2851 { 2852 filename = filename.substring(0, filename.length - 4) + '.drawio'; 2853 } 2854 2855 this.fileLoaded((mxClient.IS_IOS) ? 2856 new StorageFile(this, xml, filename) : 2857 new LocalFile(this, xml, filename, temp)); 2858 })); 2859 } 2860 } 2861 } 2862 catch(e) 2863 { 2864 // ignore 2865 } 2866}; 2867 2868App.prototype.loadGapi = function(then) 2869{ 2870 if (typeof gapi !== 'undefined') 2871 { 2872 gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + App.GOOGLE_APIS, then); 2873 } 2874}; 2875 2876/** 2877 * Main function. Program starts here. 2878 * 2879 * @param {number} dx X-coordinate of the translation. 2880 * @param {number} dy Y-coordinate of the translation. 2881 */ 2882App.prototype.load = function() 2883{ 2884 // Checks if we're running in embedded mode 2885 if (urlParams['embed'] != '1') 2886 { 2887 if (this.spinner.spin(document.body, mxResources.get('starting'))) 2888 { 2889 try 2890 { 2891 this.stateArg = (urlParams['state'] != null && this.drive != null) ? JSON.parse(decodeURIComponent(urlParams['state'])) : null; 2892 } 2893 catch (e) 2894 { 2895 // ignores invalid state args 2896 } 2897 2898 this.editor.graph.setEnabled(this.getCurrentFile() != null); 2899 2900 // Passes the userId from the state parameter to the client 2901 if ((window.location.hash == null || window.location.hash.length == 0) && 2902 this.drive != null && this.stateArg != null && this.stateArg.userId != null) 2903 { 2904 this.drive.setUserId(this.stateArg.userId); 2905 } 2906 2907 // Legacy support for fileId parameter which is moved to the hash tag 2908 if (urlParams['fileId'] != null) 2909 { 2910 window.location.hash = 'G' + urlParams['fileId']; 2911 window.location.search = this.getSearch(['fileId']); 2912 } 2913 else 2914 { 2915 // Asynchronous or disabled loading of client 2916 if (this.drive == null) 2917 { 2918 if (this.mode == App.MODE_GOOGLE) 2919 { 2920 this.mode = null; 2921 } 2922 2923 this.start(); 2924 } 2925 else 2926 { 2927 this.loadGapi(mxUtils.bind(this, function() 2928 { 2929 this.start(); 2930 })); 2931 } 2932 } 2933 } 2934 } 2935 else 2936 { 2937 this.restoreLibraries(); 2938 2939 if (urlParams['gapi'] == '1') 2940 { 2941 this.loadGapi(function() {}); 2942 } 2943 } 2944}; 2945 2946/** 2947 * Adds the listener for automatically saving the diagram for local changes. 2948 */ 2949App.prototype.showRefreshDialog = function(title, message) 2950{ 2951 if (!this.showingRefreshDialog) 2952 { 2953 this.showingRefreshDialog = true; 2954 2955 this.showError(title || mxResources.get('externalChanges'), 2956 message || mxResources.get('redirectToNewApp'), 2957 mxResources.get('refresh'), mxUtils.bind(this, function() 2958 { 2959 var file = this.getCurrentFile(); 2960 2961 if (file != null) 2962 { 2963 file.setModified(false); 2964 } 2965 2966 this.spinner.spin(document.body, mxResources.get('connecting')); 2967 this.editor.graph.setEnabled(false); 2968 window.location.reload(); 2969 }), null, null, null, null, null, 340, 180); 2970 2971 // Adds important notice to dialog 2972 if (this.dialog != null && this.dialog.container != null) 2973 { 2974 var alert = this.createRealtimeNotice(); 2975 alert.style.left = '0'; 2976 alert.style.right = '0'; 2977 alert.style.borderRadius = '0'; 2978 alert.style.borderLeftStyle = 'none'; 2979 alert.style.borderRightStyle = 'none'; 2980 alert.style.marginBottom = '26px'; 2981 alert.style.padding = '8px 0 8px 0'; 2982 2983 this.dialog.container.appendChild(alert); 2984 } 2985 } 2986}; 2987 2988/** 2989 * Called in start after the spinner stops. 2990 */ 2991App.prototype.showAlert = function(message) 2992{ 2993 if (message != null && message.length > 0) 2994 { 2995 var div = document.createElement('div'); 2996 div.className = 'geAlert'; 2997 div.style.zIndex = 2e9; 2998 div.style.left = '50%'; 2999 div.style.top = '-100%'; 3000 //Limit width to 80% max with word wrapping 3001 div.style.maxWidth = '80%'; 3002 div.style.width = 'max-content'; 3003 div.style.whiteSpace = 'pre-wrap'; 3004 mxUtils.setPrefixedStyle(div.style, 'transform', 'translate(-50%,0%)'); 3005 mxUtils.setPrefixedStyle(div.style, 'transition', 'all 1s ease'); 3006 3007 div.innerHTML = message; 3008 3009 var close = document.createElement('a'); 3010 close.className = 'geAlertLink'; 3011 close.style.textAlign = 'right'; 3012 close.style.marginTop = '20px'; 3013 close.style.display = 'block'; 3014 close.setAttribute('title', mxResources.get('close')); 3015 close.innerHTML = mxResources.get('close'); 3016 div.appendChild(close); 3017 3018 mxEvent.addListener(close, 'click', function(evt) 3019 { 3020 if (div.parentNode != null) 3021 { 3022 div.parentNode.removeChild(div); 3023 mxEvent.consume(evt); 3024 } 3025 }); 3026 3027 document.body.appendChild(div); 3028 3029 // Delayed to get smoother animation after DOM rendering 3030 window.setTimeout(function() 3031 { 3032 div.style.top = '30px'; 3033 }, 10); 3034 3035 // Fades out the alert after 15 secs 3036 window.setTimeout(function() 3037 { 3038 mxUtils.setPrefixedStyle(div.style, 'transition', 'all 2s ease'); 3039 div.style.opacity = '0'; 3040 3041 window.setTimeout(function() 3042 { 3043 if (div.parentNode != null) 3044 { 3045 div.parentNode.removeChild(div); 3046 } 3047 }, 2000); 3048 }, 15000); 3049 } 3050}; 3051 3052/** 3053 * Translates this point by the given vector. 3054 * 3055 * @param {number} dx X-coordinate of the translation. 3056 * @param {number} dy Y-coordinate of the translation. 3057 */ 3058App.prototype.start = function() 3059{ 3060 if (this.bg != null && this.bg.parentNode != null) 3061 { 3062 this.bg.parentNode.removeChild(this.bg); 3063 } 3064 3065 this.restoreLibraries(); 3066 this.spinner.stop(); 3067 3068 try 3069 { 3070 // Handles all errors 3071 var ui = this; 3072 3073 window.onerror = function(message, url, linenumber, colno, err) 3074 { 3075 // Ignores Grammarly error [1344] 3076 if (message != 'ResizeObserver loop limit exceeded') 3077 { 3078 EditorUi.logError('Uncaught: ' + ((message != null) ? message : ''), 3079 url, linenumber, colno, err, null, true); 3080 ui.handleError({message: message}, mxResources.get('unknownError'), 3081 null, null, null, null, true); 3082 } 3083 }; 3084 3085 // Listens to changes of the hash if not in embed or client mode 3086 if (urlParams['client'] != '1' && urlParams['embed'] != '1') 3087 { 3088 // Installs listener to claim current draft if there is one 3089 try 3090 { 3091 if (isLocalStorage) 3092 { 3093 window.addEventListener('storage', mxUtils.bind(this, function(evt) 3094 { 3095 var file = this.getCurrentFile(); 3096 EditorUi.debug('storage event', evt, file); 3097 3098 if (file != null && evt.key == '.draft-alive-check' && evt.newValue != null && file.draftId != null) 3099 { 3100 this.draftAliveCheck = evt.newValue; 3101 file.saveDraft(); 3102 } 3103 })); 3104 } 3105 3106 if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOfflineApp() && 3107 urlParams['open'] == null && /www\.draw\.io$/.test(window.location.hostname) && 3108 (!this.editor.chromeless || this.editor.editable)) 3109 { 3110 this.showNameChangeBanner(); 3111 } 3112 } 3113 catch (e) 3114 { 3115 // ignore 3116 } 3117 3118 mxEvent.addListener(window, 'hashchange', mxUtils.bind(this, function(evt) 3119 { 3120 try 3121 { 3122 this.hideDialog(); 3123 var id = this.getDiagramId(); 3124 var file = this.getCurrentFile(); 3125 3126 if (file == null || file.getHash() != id) 3127 { 3128 this.loadFile(id, true); 3129 } 3130 } 3131 catch (e) 3132 { 3133 // Workaround for possible scrollWidth of null in Dialog ctor 3134 if (document.body != null) 3135 { 3136 this.handleError(e, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function() 3137 { 3138 var file = this.getCurrentFile(); 3139 window.location.hash = (file != null) ? file.getHash() : ''; 3140 })); 3141 } 3142 } 3143 })); 3144 } 3145 3146 // Descriptor for CSV import 3147 if ((window.location.hash == null || window.location.hash.length <= 1) && urlParams['desc'] != null) 3148 { 3149 try 3150 { 3151 this.loadDescriptor(JSON.parse(Graph.decompress(urlParams['desc'])), 3152 null, mxUtils.bind(this, function(e) 3153 { 3154 this.handleError(e, mxResources.get('errorLoadingFile')); 3155 })); 3156 } 3157 catch (e) 3158 { 3159 this.handleError(e, mxResources.get('errorLoadingFile')); 3160 } 3161 } 3162 // Redirects old url URL parameter to new #U format 3163 else if ((window.location.hash == null || window.location.hash.length <= 1) && urlParams['url'] != null) 3164 { 3165 this.loadFile('U' + urlParams['url'], true); 3166 } 3167 else if (this.getCurrentFile() == null) 3168 { 3169 var done = mxUtils.bind(this, function() 3170 { 3171 // Starts in client mode and waits for data 3172 if (urlParams['client'] == '1' && (window.location.hash == null || 3173 window.location.hash.length == 0 || window.location.hash.substring(0, 2) == '#P')) 3174 { 3175 var doLoadFile = mxUtils.bind(this, function(xml) 3176 { 3177 // Extracts graph model from PNG 3178 if (xml.substring(0, 22) == 'data:image/png;base64,') 3179 { 3180 xml = this.extractGraphModelFromPng(xml); 3181 } 3182 3183 var title = urlParams['title']; 3184 3185 if (title != null) 3186 { 3187 title = decodeURIComponent(title); 3188 } 3189 else 3190 { 3191 title = this.defaultFilename; 3192 } 3193 3194 var file = new LocalFile(this, xml, title, true); 3195 3196 if (window.location.hash != null && window.location.hash.substring(0, 2) == '#P') 3197 { 3198 file.getHash = function() 3199 { 3200 return window.location.hash.substring(1); 3201 }; 3202 } 3203 3204 this.fileLoaded(file); 3205 this.getCurrentFile().setModified(!this.editor.chromeless); 3206 }); 3207 3208 var parent = window.opener || window.parent; 3209 3210 if (parent != window) 3211 { 3212 var value = urlParams['create']; 3213 3214 if (value != null) 3215 { 3216 doLoadFile(parent[decodeURIComponent(value)]); 3217 } 3218 else 3219 { 3220 value = urlParams['data']; 3221 3222 if (value != null) 3223 { 3224 doLoadFile(decodeURIComponent(value)); 3225 } 3226 else 3227 { 3228 this.installMessageHandler(mxUtils.bind(this, function(xml, evt) 3229 { 3230 // Ignores messages from other windows 3231 if (evt.source == parent) 3232 { 3233 doLoadFile(xml); 3234 } 3235 })); 3236 } 3237 } 3238 } 3239 } 3240 // Checks if no earlier loading errors are showing 3241 else if (this.dialog == null) 3242 { 3243 if (urlParams['demo'] == '1') 3244 { 3245 var prev = Editor.useLocalStorage; 3246 this.createFile(this.defaultFilename, null, null, null, null, null, null, true); 3247 Editor.useLocalStorage = prev; 3248 } 3249 else 3250 { 3251 var waiting = false; 3252 3253 // Checks if we're waiting for some asynchronous file to be loaded 3254 // Cross-domain window access is not allowed in FF, so if we 3255 // were opened from another domain then this will fail. 3256 try 3257 { 3258 waiting = window.opener != null && window.opener.openFile != null; 3259 } 3260 catch(e) 3261 { 3262 // ignore 3263 } 3264 3265 if (waiting) 3266 { 3267 // Spinner is stopped in App.open 3268 this.spinner.spin(document.body, mxResources.get('loading')) 3269 } 3270 else 3271 { 3272 var id = this.getDiagramId(); 3273 3274 3275 if (EditorUi.enableDrafts && (urlParams['mode'] == null || EditorUi.isElectronApp) && 3276 this.getServiceName() == 'draw.io' && (id == null || id.length == 0) && 3277 !this.editor.isChromelessView()) 3278 { 3279 this.checkDrafts(); 3280 } 3281 else if (id != null && id.length > 0) 3282 { 3283 this.loadFile(id, null, null, mxUtils.bind(this, function() 3284 { 3285 var temp = decodeURIComponent(urlParams['viewbox'] || ''); 3286 3287 if (temp != '') 3288 { 3289 try 3290 { 3291 var bounds = JSON.parse(temp); 3292 this.editor.graph.fitWindow(bounds, bounds.border); 3293 } 3294 catch (e) 3295 { 3296 // Ignore invalid viewport 3297 console.error(e); 3298 } 3299 } 3300 })); 3301 } 3302 else if (urlParams['splash'] != '0') 3303 { 3304 this.loadFile(); 3305 } 3306 else 3307 { 3308 this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true); 3309 } 3310 } 3311 } 3312 } 3313 }); 3314 3315 var value = decodeURIComponent(urlParams['create'] || ''); 3316 3317 if ((window.location.hash == null || window.location.hash.length <= 1) && 3318 value != null && value.length > 0 && this.spinner.spin(document.body, mxResources.get('loading'))) 3319 { 3320 var reconnect = mxUtils.bind(this, function() 3321 { 3322 // Removes URL parameter and reloads the page 3323 if (this.spinner.spin(document.body, mxResources.get('reconnecting'))) 3324 { 3325 window.location.search = this.getSearch(['create', 'title']); 3326 }; 3327 }); 3328 3329 var showCreateDialog = mxUtils.bind(this, function(xml) 3330 { 3331 this.spinner.stop(); 3332 3333 // Resets mode for dialog - local file is only for preview 3334 if (urlParams['splash'] != '0') 3335 { 3336 this.fileLoaded(new LocalFile(this, xml, null)); 3337 3338 this.editor.graph.setEnabled(false); 3339 this.mode = urlParams['mode']; 3340 var title = urlParams['title']; 3341 3342 if (title != null) 3343 { 3344 title = decodeURIComponent(title); 3345 } 3346 else 3347 { 3348 title = this.defaultFilename; 3349 } 3350 3351 var serviceCount = this.getServiceCount(true); 3352 3353 if (isLocalStorage) 3354 { 3355 serviceCount++; 3356 } 3357 3358 var rowLimit = (serviceCount <= 4) ? 2 : (serviceCount > 6 ? 4 : 3); 3359 3360 var dlg = new CreateDialog(this, title, mxUtils.bind(this, function(filename, mode) 3361 { 3362 if (mode == null) 3363 { 3364 this.hideDialog(); 3365 var prev = Editor.useLocalStorage; 3366 this.createFile((filename.length > 0) ? filename : this.defaultFilename, 3367 this.getFileData(), null, null, null, true, null, true); 3368 Editor.useLocalStorage = prev; 3369 } 3370 else 3371 { 3372 this.pickFolder(mode, mxUtils.bind(this, function(folderId) 3373 { 3374 this.createFile(filename, this.getFileData(true), 3375 null, mode, null, true, folderId); 3376 })); 3377 } 3378 }), null, null, null, null, urlParams['browser'] == '1', 3379 null, null, true, rowLimit, null, null, null, 3380 this.editor.fileExtensions); 3381 this.showDialog(dlg.container, 400, (serviceCount > rowLimit) ? 390 : 270, 3382 true, false, mxUtils.bind(this, function(cancel) 3383 { 3384 if (cancel && this.getCurrentFile() == null) 3385 { 3386 this.showSplash(); 3387 } 3388 })); 3389 dlg.init(); 3390 } 3391 }); 3392 3393 value = decodeURIComponent(value); 3394 3395 if (value.substring(0, 7) != 'http://' && value.substring(0, 8) != 'https://') 3396 { 3397 // Cross-domain window access is not allowed in FF, so if we 3398 // were opened from another domain then this will fail. 3399 try 3400 { 3401 if (window.opener != null && window.opener[value] != null) 3402 { 3403 showCreateDialog(window.opener[value]); 3404 } 3405 else 3406 { 3407 this.handleError(null, mxResources.get('errorLoadingFile')); 3408 } 3409 } 3410 catch (e) 3411 { 3412 this.handleError(e, mxResources.get('errorLoadingFile')); 3413 } 3414 } 3415 else 3416 { 3417 this.loadTemplate(value, function(text) 3418 { 3419 showCreateDialog(text); 3420 }, mxUtils.bind(this, function() 3421 { 3422 this.handleError(null, mxResources.get('errorLoadingFile'), reconnect); 3423 })); 3424 } 3425 } 3426 else 3427 { 3428 // Passes the fileId from the state parameter to the hash tag and reloads 3429 // the page without the state parameter 3430 if ((window.location.hash == null || window.location.hash.length <= 1) && 3431 urlParams['state'] != null && this.stateArg != null && this.stateArg.action == 'open') 3432 { 3433 if (this.stateArg.ids != null) 3434 { 3435 if (window.history && window.history.replaceState) 3436 { 3437 // Removes state URL parameter without reloading the page 3438 window.history.replaceState(null, null, window.location.pathname + 3439 this.getSearch(['state'])); 3440 } 3441 3442 window.location.hash = 'G' + this.stateArg.ids[0]; 3443 } 3444 } 3445 else if ((window.location.hash == null || window.location.hash.length <= 1) && 3446 this.drive != null && this.stateArg != null && this.stateArg.action == 'create') 3447 { 3448 if (window.history && window.history.replaceState) 3449 { 3450 // Removes state URL parameter without reloading the page 3451 window.history.replaceState(null, null, window.location.pathname + 3452 this.getSearch(['state'])); 3453 } 3454 3455 this.setMode(App.MODE_GOOGLE); 3456 3457 if (urlParams['splash'] == '0') 3458 { 3459 this.createFile((urlParams['title'] != null) ? 3460 decodeURIComponent(urlParams['title']) : 3461 this.defaultFilename); 3462 } 3463 else 3464 { 3465 this.actions.get('new').funct(); 3466 } 3467 } 3468 else 3469 { 3470 // Removes open URL parameter. Hash is also updated in Init to load client. 3471 if (urlParams['open'] != null && window.history && window.history.replaceState) 3472 { 3473 window.history.replaceState(null, null, window.location.pathname + 3474 this.getSearch(['open'])); 3475 window.location.hash = urlParams['open']; 3476 } 3477 3478 done(); 3479 } 3480 } 3481 } 3482 } 3483 catch (e) 3484 { 3485 this.handleError(e); 3486 } 3487}; 3488 3489/** 3490 * Checks for orphaned drafts. 3491 */ 3492App.prototype.loadDraft = function(xml, success) 3493{ 3494 this.createFile(this.defaultFilename, xml, null, null, mxUtils.bind(this, function() 3495 { 3496 window.setTimeout(mxUtils.bind(this, function() 3497 { 3498 var file = this.getCurrentFile(); 3499 3500 if (file != null) 3501 { 3502 file.fileChanged(); 3503 3504 if (success != null) 3505 { 3506 success(); 3507 } 3508 } 3509 }), 0); 3510 }), null, null, true); 3511}; 3512 3513/** 3514 * Checks for orphaned drafts. 3515 */ 3516App.prototype.checkDrafts = function() 3517{ 3518 try 3519 { 3520 // Triggers storage event for other windows to mark active drafts 3521 var guid = Editor.guid(); 3522 localStorage.setItem('.draft-alive-check', guid); 3523 3524 window.setTimeout(mxUtils.bind(this, function() 3525 { 3526 localStorage.removeItem('.draft-alive-check'); 3527 3528 this.getDatabaseItems(mxUtils.bind(this, function(items) 3529 { 3530 // Collects orphaned drafts 3531 var drafts = []; 3532 3533 for (var i = 0; i < items.length; i++) 3534 { 3535 try 3536 { 3537 var key = items[i].key; 3538 3539 if (key != null && key.substring(0, 7) == '.draft_') 3540 { 3541 var obj = JSON.parse(items[i].data); 3542 3543 if (obj != null && obj.type == 'draft' && obj.aliveCheck != guid) 3544 { 3545 obj.key = key; 3546 drafts.push(obj); 3547 } 3548 } 3549 } 3550 catch (e) 3551 { 3552 // ignore 3553 } 3554 } 3555 3556 if (drafts.length == 1) 3557 { 3558 this.loadDraft(drafts[0].data, mxUtils.bind(this, function() 3559 { 3560 this.removeDatabaseItem(drafts[0].key); 3561 })); 3562 } 3563 else if (drafts.length > 1) 3564 { 3565 var ts = new Date(drafts[0].modified); 3566 3567 var dlg = new DraftDialog(this, (drafts.length > 1) ? mxResources.get('selectDraft') : 3568 mxResources.get('draftFound', [ts.toLocaleDateString() + ' ' + ts.toLocaleTimeString()]), 3569 (drafts.length > 1) ? null : drafts[0].data, mxUtils.bind(this, function(index) 3570 { 3571 this.hideDialog(); 3572 index = (index != '') ? index : 0; 3573 3574 this.loadDraft(drafts[index].data, mxUtils.bind(this, function() 3575 { 3576 this.removeDatabaseItem(drafts[index].key); 3577 })); 3578 }), mxUtils.bind(this, function(index, success) 3579 { 3580 index = (index != '') ? index : 0; 3581 3582 // Discard draft 3583 this.confirm(mxResources.get('areYouSure'), null, mxUtils.bind(this, function() 3584 { 3585 this.removeDatabaseItem(drafts[index].key); 3586 3587 if (success != null) 3588 { 3589 success(); 3590 } 3591 }), mxResources.get('no'), mxResources.get('yes')); 3592 }), null, null, null, (drafts.length > 1) ? drafts : null); 3593 this.showDialog(dlg.container, 640, 480, true, false, mxUtils.bind(this, function(cancel) 3594 { 3595 if (urlParams['splash'] != '0') 3596 { 3597 this.loadFile(); 3598 } 3599 else 3600 { 3601 this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true); 3602 } 3603 })); 3604 dlg.init(); 3605 } 3606 else if (urlParams['splash'] != '0') 3607 { 3608 this.loadFile(); 3609 } 3610 else 3611 { 3612 this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true); 3613 } 3614 }), mxUtils.bind(this, function() 3615 { 3616 if (urlParams['splash'] != '0') 3617 { 3618 this.loadFile(); 3619 } 3620 else 3621 { 3622 this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true); 3623 } 3624 })); 3625 }), 0); 3626 } 3627 catch (e) 3628 { 3629 // ignore 3630 } 3631}; 3632 3633/** 3634 * Translates this point by the given vector. 3635 * 3636 * @param {number} dx X-coordinate of the translation. 3637 * @param {number} dy Y-coordinate of the translation. 3638 */ 3639App.prototype.showSplash = function(force) 3640{ 3641 //Splash dialog shouldn't be shownn when running without a file menu 3642 if (urlParams['noFileMenu'] == '1') 3643 { 3644 return; 3645 } 3646 3647 var serviceCount = this.getServiceCount(true); 3648 3649 var showSecondDialog = mxUtils.bind(this, function() 3650 { 3651 var dlg = new SplashDialog(this); 3652 3653 this.showDialog(dlg.container, 340, (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp) ? 200 : 230, true, true, 3654 mxUtils.bind(this, function(cancel) 3655 { 3656 // Creates a blank diagram if the dialog is closed 3657 if (cancel && !mxClient.IS_CHROMEAPP) 3658 { 3659 var prev = Editor.useLocalStorage; 3660 this.createFile(this.defaultFilename + (EditorUi.isElectronApp? '.drawio' : ''), null, null, null, null, null, null, 3661 urlParams['local'] != '1'); 3662 Editor.useLocalStorage = prev; 3663 } 3664 }), true); 3665 }); 3666 3667 if (this.editor.isChromelessView()) 3668 { 3669 this.handleError({message: mxResources.get('noFileSelected')}, 3670 mxResources.get('errorLoadingFile'), mxUtils.bind(this, function() 3671 { 3672 this.showSplash(); 3673 })); 3674 } 3675 else if (!mxClient.IS_CHROMEAPP && (this.mode == null || force)) 3676 { 3677 var rowLimit = (serviceCount == 4) ? 2 : 3; 3678 3679 var dlg = new StorageDialog(this, mxUtils.bind(this, function() 3680 { 3681 this.hideDialog(); 3682 showSecondDialog(); 3683 }), rowLimit); 3684 3685 this.showDialog(dlg.container, (rowLimit < 3) ? 200 : 300, 3686 ((serviceCount > 3) ? 320 : 210), true, false); 3687 } 3688 else if (urlParams['create'] == null) 3689 { 3690 showSecondDialog(); 3691 } 3692}; 3693 3694/** 3695 * Translates this point by the given vector. 3696 * 3697 * @param {number} dx X-coordinate of the translation. 3698 * @param {number} dy Y-coordinate of the translation. 3699 */ 3700App.prototype.addLanguageMenu = function(elt, addLabel) 3701{ 3702 var img = null; 3703 var langMenu = this.menus.get('language'); 3704 3705 if (langMenu != null) 3706 { 3707 img = document.createElement('div'); 3708 img.setAttribute('title', mxResources.get('language')); 3709 img.className = 'geIcon geSprite geSprite-globe'; 3710 img.style.position = 'absolute'; 3711 img.style.cursor = 'pointer'; 3712 img.style.bottom = '20px'; 3713 img.style.right = '20px'; 3714 3715 if (addLabel) 3716 { 3717 img.style.direction = 'rtl'; 3718 img.style.textAlign = 'right'; 3719 img.style.right = '24px'; 3720 3721 var label = document.createElement('span'); 3722 label.style.display = 'inline-block'; 3723 label.style.fontSize = '12px'; 3724 label.style.margin = '5px 24px 0 0'; 3725 label.style.color = 'gray'; 3726 label.style.userSelect = 'none'; 3727 3728 mxUtils.write(label, mxResources.get('language')); 3729 img.appendChild(label); 3730 } 3731 3732 mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt) 3733 { 3734 this.editor.graph.popupMenuHandler.hideMenu(); 3735 var menu = new mxPopupMenu(this.menus.get('language').funct); 3736 menu.div.className += ' geMenubarMenu'; 3737 menu.smartSeparators = true; 3738 menu.showDisabled = true; 3739 menu.autoExpand = true; 3740 3741 // Disables autoexpand and destroys menu when hidden 3742 menu.hideMenu = mxUtils.bind(this, function() 3743 { 3744 mxPopupMenu.prototype.hideMenu.apply(menu, arguments); 3745 menu.destroy(); 3746 }); 3747 3748 var offset = mxUtils.getOffset(img); 3749 menu.popup(offset.x, offset.y + img.offsetHeight, null, evt); 3750 3751 // Allows hiding by clicking on document 3752 this.setCurrentMenu(menu); 3753 })); 3754 3755 elt.appendChild(img); 3756 } 3757 3758 return img; 3759}; 3760 3761/** 3762 * Loads the given file handle as a local file. 3763 */ 3764App.prototype.loadFileSystemEntry = function(fileHandle, success, error) 3765{ 3766 error = (error != null) ? error : mxUtils.bind(this, function(e) 3767 { 3768 this.handleError(e); 3769 }); 3770 3771 try 3772 { 3773 fileHandle.getFile().then(mxUtils.bind(this, function(file) 3774 { 3775 var reader = new FileReader(); 3776 3777 reader.onload = mxUtils.bind(this, function(e) 3778 { 3779 try 3780 { 3781 if (success != null) 3782 { 3783 var data = e.target.result; 3784 3785 if (file.type == 'image/png') 3786 { 3787 data = this.extractGraphModelFromPng(data); 3788 } 3789 3790 success(new LocalFile(this, data, file.name, null, fileHandle, file)); 3791 } 3792 else 3793 { 3794 this.openFileHandle(e.target.result, file.name, file, false, fileHandle); 3795 } 3796 } 3797 catch(e) 3798 { 3799 error(e); 3800 } 3801 }); 3802 3803 reader.onerror = error; 3804 3805 if ((file.type.substring(0, 5) === 'image' || 3806 file.type === 'application/pdf') && 3807 file.type.substring(0, 9) !== 'image/svg') 3808 { 3809 reader.readAsDataURL(file); 3810 } 3811 else 3812 { 3813 reader.readAsText(file); 3814 } 3815 }), error); 3816 } 3817 catch (e) 3818 { 3819 error(e); 3820 } 3821}; 3822 3823/** 3824 * Loads the given file handle as a local file. 3825 */ 3826App.prototype.createFileSystemOptions = function(name) 3827{ 3828 var ext = []; 3829 var temp = null; 3830 3831 if (name != null) 3832 { 3833 var idx = name.lastIndexOf('.'); 3834 3835 if (idx > 0) 3836 { 3837 temp = name.substring(idx + 1); 3838 } 3839 } 3840 3841 for (var i = 0; i < this.editor.diagramFileTypes.length; i++) 3842 { 3843 var obj = {description: mxResources.get(this.editor.diagramFileTypes[i].description) + 3844 ((mxClient.IS_MAC) ? ' (.' + this.editor.diagramFileTypes[i].extension + ')' : ''), 3845 accept: {}}; 3846 obj.accept[this.editor.diagramFileTypes[i].mimeType] = ['.' + this.editor.diagramFileTypes[i].extension]; 3847 3848 if (this.editor.diagramFileTypes[i].extension == temp) 3849 { 3850 ext.splice(0, 0, obj); 3851 } 3852 else 3853 { 3854 if (this.editor.diagramFileTypes[i].extension == temp) 3855 { 3856 ext.splice(0, 0, obj); 3857 } 3858 else 3859 { 3860 ext.push(obj); 3861 } 3862 } 3863 } 3864 3865 // TODO: Specify default filename 3866 return {types: ext, fileName: name}; 3867}; 3868 3869/** 3870 * Loads the given file handle as a local file. 3871 */ 3872App.prototype.showSaveFilePicker = function(success, error, opts) 3873{ 3874 error = (error != null) ? error : mxUtils.bind(this, function(e) 3875 { 3876 if (e.name != 'AbortError') 3877 { 3878 this.handleError(e); 3879 } 3880 }); 3881 3882 opts = (opts != null) ? opts : this.createFileSystemOptions(); 3883 3884 window.showSaveFilePicker(opts).then(mxUtils.bind(this, function(fileHandle) 3885 { 3886 if (fileHandle != null) 3887 { 3888 fileHandle.getFile().then(mxUtils.bind(this, function(desc) 3889 { 3890 success(fileHandle, desc); 3891 }), error); 3892 } 3893 }), error); 3894}; 3895 3896/** 3897 * Translates this point by the given vector. 3898 * 3899 * @param {number} dx X-coordinate of the translation. 3900 * @param {number} dy Y-coordinate of the translation. 3901 */ 3902App.prototype.pickFile = function(mode) 3903{ 3904 try 3905 { 3906 mode = (mode != null) ? mode : this.mode; 3907 3908 if (mode == App.MODE_GOOGLE) 3909 { 3910 if (this.drive != null && typeof(google) != 'undefined' && typeof(google.picker) != 'undefined') 3911 { 3912 this.drive.pickFile(); 3913 } 3914 else 3915 { 3916 this.openLink('https://drive.google.com'); 3917 } 3918 } 3919 else 3920 { 3921 var peer = this.getPeerForMode(mode); 3922 3923 if (peer != null) 3924 { 3925 peer.pickFile(); 3926 } 3927 else if (mode == App.MODE_DEVICE && EditorUi.nativeFileSupport) 3928 { 3929 window.showOpenFilePicker().then(mxUtils.bind(this, function(fileHandles) 3930 { 3931 if (fileHandles != null && fileHandles.length > 0 && 3932 this.spinner.spin(document.body, mxResources.get('loading'))) 3933 { 3934 this.loadFileSystemEntry(fileHandles[0]); 3935 } 3936 }), mxUtils.bind(this, function(e) 3937 { 3938 if (e.name != 'AbortError') 3939 { 3940 this.handleError(e); 3941 } 3942 })); 3943 } 3944 else if (mode == App.MODE_DEVICE && Graph.fileSupport) 3945 { 3946 if (this.openFileInputElt == null) 3947 { 3948 var input = document.createElement('input'); 3949 input.setAttribute('type', 'file'); 3950 3951 mxEvent.addListener(input, 'change', mxUtils.bind(this, function() 3952 { 3953 if (input.files != null) 3954 { 3955 this.openFiles(input.files); 3956 3957 // Resets input to force change event for 3958 // same file (type reset required for IE) 3959 input.type = ''; 3960 input.type = 'file'; 3961 input.value = ''; 3962 } 3963 })); 3964 3965 input.style.display = 'none'; 3966 document.body.appendChild(input); 3967 this.openFileInputElt = input; 3968 } 3969 3970 this.openFileInputElt.click(); 3971 } 3972 else 3973 { 3974 this.hideDialog(); 3975 window.openNew = this.getCurrentFile() != null && !this.isDiagramEmpty(); 3976 window.baseUrl = this.getUrl(); 3977 window.openKey = 'open'; 3978 3979 window.listBrowserFiles = mxUtils.bind(this, function(success, error) 3980 { 3981 StorageFile.listFiles(this, 'F', success, error); 3982 }); 3983 3984 window.openBrowserFile = mxUtils.bind(this, function(title, success, error) 3985 { 3986 StorageFile.getFileContent(this, title, success, error); 3987 }); 3988 3989 window.deleteBrowserFile = mxUtils.bind(this, function(title, success, error) 3990 { 3991 StorageFile.deleteFile(this, title, success, error); 3992 }); 3993 3994 var prevValue = Editor.useLocalStorage; 3995 Editor.useLocalStorage = (mode == App.MODE_BROWSER); 3996 this.openFile(); 3997 3998 // Installs local handler for opened files in same window 3999 window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename) 4000 { 4001 var doOpenFile = mxUtils.bind(this, function() 4002 { 4003 // Replaces PNG with XML extension 4004 var dot = !this.useCanvasForExport && filename.substring(filename.length - 4) == '.png'; 4005 4006 if (dot) 4007 { 4008 filename = filename.substring(0, filename.length - 4) + '.drawio'; 4009 } 4010 4011 this.fileLoaded((mode == App.MODE_BROWSER) ? 4012 new StorageFile(this, xml, filename) : 4013 new LocalFile(this, xml, filename)); 4014 }); 4015 4016 var currentFile = this.getCurrentFile(); 4017 4018 if (currentFile == null || !currentFile.isModified()) 4019 { 4020 doOpenFile(); 4021 } 4022 else 4023 { 4024 this.confirm(mxResources.get('allChangesLost'), null, doOpenFile, 4025 mxResources.get('cancel'), mxResources.get('discardChanges')); 4026 } 4027 })); 4028 4029 // Extends dialog close to show splash screen 4030 var dlg = this.dialog; 4031 var dlgClose = dlg.close; 4032 4033 this.dialog.close = mxUtils.bind(this, function(cancel) 4034 { 4035 Editor.useLocalStorage = prevValue; 4036 dlgClose.apply(dlg, arguments); 4037 4038 if (this.getCurrentFile() == null) 4039 { 4040 this.showSplash(); 4041 } 4042 }); 4043 } 4044 } 4045 } 4046 catch (e) 4047 { 4048 this.handleError(e); 4049 } 4050}; 4051 4052/** 4053 * Translates this point by the given vector. 4054 * 4055 * @param {number} dx X-coordinate of the translation. 4056 * @param {number} dy Y-coordinate of the translation. 4057 */ 4058App.prototype.pickLibrary = function(mode) 4059{ 4060 mode = (mode != null) ? mode : this.mode; 4061 4062 if (mode == App.MODE_GOOGLE || mode == App.MODE_DROPBOX || mode == App.MODE_ONEDRIVE || 4063 mode == App.MODE_GITHUB || mode == App.MODE_GITLAB || mode == App.MODE_TRELLO || 4064 mode == App.MODE_NOTION) 4065 { 4066 var peer = (mode == App.MODE_GOOGLE) ? this.drive : 4067 ((mode == App.MODE_ONEDRIVE) ? this.oneDrive : 4068 ((mode == App.MODE_GITHUB) ? this.gitHub : 4069 ((mode == App.MODE_GITLAB) ? this.gitLab : 4070 ((mode == App.MODE_TRELLO) ? this.trello : 4071 ((mode == App.MODE_NOTION) ? this.notion : 4072 this.dropbox))))); 4073 4074 if (peer != null) 4075 { 4076 peer.pickLibrary(mxUtils.bind(this, function(id, optionalFile) 4077 { 4078 if (optionalFile != null) 4079 { 4080 try 4081 { 4082 this.loadLibrary(optionalFile); 4083 } 4084 catch (e) 4085 { 4086 this.handleError(e, mxResources.get('errorLoadingFile')); 4087 } 4088 } 4089 else 4090 { 4091 if (this.spinner.spin(document.body, mxResources.get('loading'))) 4092 { 4093 peer.getLibrary(id, mxUtils.bind(this, function(file) 4094 { 4095 this.spinner.stop(); 4096 4097 try 4098 { 4099 this.loadLibrary(file); 4100 } 4101 catch (e) 4102 { 4103 this.handleError(e, mxResources.get('errorLoadingFile')); 4104 } 4105 }), mxUtils.bind(this, function(resp) 4106 { 4107 this.handleError(resp, (resp != null) ? mxResources.get('errorLoadingFile') : null); 4108 })); 4109 } 4110 } 4111 })); 4112 } 4113 } 4114 else if (mode == App.MODE_DEVICE && Graph.fileSupport) 4115 { 4116 if (this.libFileInputElt == null) 4117 { 4118 var input = document.createElement('input'); 4119 input.setAttribute('type', 'file'); 4120 4121 mxEvent.addListener(input, 'change', mxUtils.bind(this, function() 4122 { 4123 if (input.files != null) 4124 { 4125 for (var i = 0; i < input.files.length; i++) 4126 { 4127 (mxUtils.bind(this, function(file) 4128 { 4129 var reader = new FileReader(); 4130 4131 reader.onload = mxUtils.bind(this, function(e) 4132 { 4133 try 4134 { 4135 this.loadLibrary(new LocalLibrary(this, e.target.result, file.name)); 4136 } 4137 catch (e) 4138 { 4139 this.handleError(e, mxResources.get('errorLoadingFile')); 4140 } 4141 }); 4142 4143 reader.readAsText(file); 4144 }))(input.files[i]); 4145 } 4146 4147 // Resets input to force change event for same file (type reset required for IE) 4148 input.type = ''; 4149 input.type = 'file'; 4150 input.value = ''; 4151 } 4152 })); 4153 4154 input.style.display = 'none'; 4155 document.body.appendChild(input); 4156 this.libFileInputElt = input; 4157 } 4158 4159 this.libFileInputElt.click(); 4160 } 4161 else 4162 { 4163 window.openNew = false; 4164 window.openKey = 'open'; 4165 4166 window.listBrowserFiles = mxUtils.bind(this, function(success, error) 4167 { 4168 StorageFile.listFiles(this, 'L', success, error); 4169 }); 4170 4171 window.openBrowserFile = mxUtils.bind(this, function(title, success, error) 4172 { 4173 StorageFile.getFileContent(this, title, success, error); 4174 }); 4175 4176 window.deleteBrowserFile = mxUtils.bind(this, function(title, success, error) 4177 { 4178 StorageFile.deleteFile(this, title, success, error); 4179 }); 4180 4181 var prevValue = Editor.useLocalStorage; 4182 Editor.useLocalStorage = mode == App.MODE_BROWSER; 4183 4184 // Closes dialog after open 4185 window.openFile = new OpenFile(mxUtils.bind(this, function(cancel) 4186 { 4187 this.hideDialog(cancel); 4188 })); 4189 4190 window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename) 4191 { 4192 try 4193 { 4194 this.loadLibrary((mode == App.MODE_BROWSER) ? new StorageLibrary(this, xml, filename) : 4195 new LocalLibrary(this, xml, filename)); 4196 } 4197 catch (e) 4198 { 4199 this.handleError(e, mxResources.get('errorLoadingFile')); 4200 } 4201 })); 4202 4203 // Removes openFile if dialog is closed 4204 this.showDialog(new OpenDialog(this).container, (Editor.useLocalStorage) ? 640 : 360, 4205 (Editor.useLocalStorage) ? 480 : 220, true, true, function() 4206 { 4207 Editor.useLocalStorage = prevValue; 4208 window.openFile = null; 4209 }); 4210 } 4211}; 4212 4213/** 4214 * Translates this point by the given vector. 4215 * 4216 * @param {number} dx X-coordinate of the translation. 4217 * @param {number} dy Y-coordinate of the translation. 4218 */ 4219App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn) 4220{ 4221 try 4222 { 4223 mode = (mode != null) ? mode : this.mode; 4224 noSpin = (noSpin != null) ? noSpin : false; 4225 noReload = (noReload != null) ? noReload : false; 4226 var xml = this.createLibraryDataFromImages(images); 4227 4228 var error = mxUtils.bind(this, function(resp) 4229 { 4230 this.spinner.stop(); 4231 4232 if (fn != null) 4233 { 4234 fn(); 4235 } 4236 4237 this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null); 4238 }); 4239 4240 // Handles special case for local libraries 4241 if (file == null && mode == App.MODE_DEVICE) 4242 { 4243 file = new LocalLibrary(this, xml, name); 4244 } 4245 4246 if (file == null) 4247 { 4248 this.pickFolder(mode, mxUtils.bind(this, function(folderId) 4249 { 4250 if (mode == App.MODE_GOOGLE && this.drive != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4251 { 4252 this.drive.insertFile(name, xml, folderId, mxUtils.bind(this, function(newFile) 4253 { 4254 this.spinner.stop(); 4255 this.hideDialog(true); 4256 this.libraryLoaded(newFile, images); 4257 }), error, this.drive.libraryMimeType); 4258 } 4259 else if (mode == App.MODE_GITHUB && this.gitHub != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4260 { 4261 this.gitHub.insertLibrary(name, xml, mxUtils.bind(this, function(newFile) 4262 { 4263 this.spinner.stop(); 4264 this.hideDialog(true); 4265 this.libraryLoaded(newFile, images); 4266 }), error, folderId); 4267 } 4268 else if (mode == App.MODE_GITLAB && this.gitLab != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4269 { 4270 this.gitLab.insertLibrary(name, xml, mxUtils.bind(this, function(newFile) 4271 { 4272 this.spinner.stop(); 4273 this.hideDialog(true); 4274 this.libraryLoaded(newFile, images); 4275 }), error, folderId); 4276 } 4277 else if (mode == App.MODE_NOTION && this.notion != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4278 { 4279 this.notion.insertLibrary(name, xml, mxUtils.bind(this, function(newFile) 4280 { 4281 this.spinner.stop(); 4282 this.hideDialog(true); 4283 this.libraryLoaded(newFile, images); 4284 }), error, folderId); 4285 } 4286 else if (mode == App.MODE_TRELLO && this.trello != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4287 { 4288 this.trello.insertLibrary(name, xml, mxUtils.bind(this, function(newFile) 4289 { 4290 this.spinner.stop(); 4291 this.hideDialog(true); 4292 this.libraryLoaded(newFile, images); 4293 }), error, folderId); 4294 } 4295 else if (mode == App.MODE_DROPBOX && this.dropbox != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4296 { 4297 this.dropbox.insertLibrary(name, xml, mxUtils.bind(this, function(newFile) 4298 { 4299 this.spinner.stop(); 4300 this.hideDialog(true); 4301 this.libraryLoaded(newFile, images); 4302 }), error, folderId); 4303 } 4304 else if (mode == App.MODE_ONEDRIVE && this.oneDrive != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4305 { 4306 this.oneDrive.insertLibrary(name, xml, mxUtils.bind(this, function(newFile) 4307 { 4308 this.spinner.stop(); 4309 this.hideDialog(true); 4310 this.libraryLoaded(newFile, images); 4311 }), error, folderId); 4312 } 4313 else if (mode == App.MODE_BROWSER) 4314 { 4315 var fn = mxUtils.bind(this, function() 4316 { 4317 var file = new StorageLibrary(this, xml, name); 4318 4319 // Inserts data into local storage 4320 file.saveFile(name, false, mxUtils.bind(this, function() 4321 { 4322 this.hideDialog(true); 4323 this.libraryLoaded(file, images); 4324 }), error); 4325 }); 4326 4327 if (localStorage.getItem(name) == null) 4328 { 4329 fn(); 4330 } 4331 else 4332 { 4333 this.confirm(mxResources.get('replaceIt', [name]), fn); 4334 } 4335 } 4336 else 4337 { 4338 this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}); 4339 } 4340 })); 4341 } 4342 else if (noSpin || this.spinner.spin(document.body, mxResources.get('saving'))) 4343 { 4344 file.setData(xml); 4345 4346 var doSave = mxUtils.bind(this, function() 4347 { 4348 file.save(true, mxUtils.bind(this, function(resp) 4349 { 4350 this.spinner.stop(); 4351 this.hideDialog(true); 4352 4353 if (!noReload) 4354 { 4355 this.libraryLoaded(file, images); 4356 } 4357 4358 if (fn != null) 4359 { 4360 fn(); 4361 } 4362 }), error); 4363 }); 4364 4365 if (name != file.getTitle()) 4366 { 4367 var oldHash = file.getHash(); 4368 4369 file.rename(name, mxUtils.bind(this, function(resp) 4370 { 4371 // Change hash in stored settings 4372 if (file.constructor != LocalLibrary && oldHash != file.getHash()) 4373 { 4374 mxSettings.removeCustomLibrary(oldHash); 4375 mxSettings.addCustomLibrary(file.getHash()); 4376 } 4377 4378 // Workaround for library files changing hash so 4379 // the old library cannot be removed from the 4380 // sidebar using the updated file in libraryLoaded 4381 this.removeLibrarySidebar(oldHash); 4382 4383 doSave(); 4384 }), error) 4385 } 4386 else 4387 { 4388 doSave(); 4389 } 4390 } 4391 } 4392 catch (e) 4393 { 4394 this.handleError(e); 4395 } 4396}; 4397 4398/** 4399 * Adds the label menu items to the given menu and parent. 4400 */ 4401App.prototype.saveFile = function(forceDialog, success) 4402{ 4403 var file = this.getCurrentFile(); 4404 4405 if (file != null) 4406 { 4407 // FIXME: Invoke for local files 4408 var done = mxUtils.bind(this, function() 4409 { 4410 if (EditorUi.enableDrafts) 4411 { 4412 file.removeDraft(); 4413 } 4414 4415 if (this.getCurrentFile() != file && !file.isModified()) 4416 { 4417 // Workaround for possible status update while save as dialog is showing 4418 // is to show no saved status for device files 4419 if (file.getMode() != App.MODE_DEVICE) 4420 { 4421 this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved'))); 4422 } 4423 else 4424 { 4425 this.editor.setStatus(''); 4426 } 4427 } 4428 4429 if (success != null) 4430 { 4431 success(); 4432 } 4433 }); 4434 4435 if (!forceDialog && file.getTitle() != null && file.invalidFileHandle == null && this.mode != null) 4436 { 4437 this.save(file.getTitle(), done); 4438 } 4439 else if (file != null && file.constructor == LocalFile && file.fileHandle != null) 4440 { 4441 this.showSaveFilePicker(mxUtils.bind(this, function(fileHandle, desc) 4442 { 4443 file.invalidFileHandle = null; 4444 file.fileHandle = fileHandle; 4445 file.title = desc.name; 4446 file.desc = desc; 4447 this.save(desc.name, done); 4448 }), null, this.createFileSystemOptions(file.getTitle())); 4449 } 4450 else 4451 { 4452 var filename = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename; 4453 var allowTab = !mxClient.IS_IOS || !navigator.standalone; 4454 var prev = this.mode; 4455 var serviceCount = this.getServiceCount(true); 4456 4457 if (isLocalStorage) 4458 { 4459 serviceCount++; 4460 } 4461 4462 var rowLimit = (serviceCount <= 4) ? 2 : (serviceCount > 6 ? 4 : 3); 4463 4464 var dlg = new CreateDialog(this, filename, mxUtils.bind(this, function(name, mode, input) 4465 { 4466 if (name != null && name.length > 0) 4467 { 4468 // Handles special case where PDF export is detected 4469 if (/(\.pdf)$/i.test(name)) 4470 { 4471 this.confirm(mxResources.get('didYouMeanToExportToPdf'), mxUtils.bind(this, function() 4472 { 4473 this.hideDialog(); 4474 this.actions.get('exportPdf').funct(); 4475 }), mxUtils.bind(this, function() 4476 { 4477 input.value = name.split('.').slice(0, -1).join('.'); 4478 input.focus(); 4479 4480 if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5) 4481 { 4482 input.select(); 4483 } 4484 else 4485 { 4486 document.execCommand('selectAll', false, null); 4487 } 4488 }), mxResources.get('yes'), mxResources.get('no')); 4489 } 4490 else 4491 { 4492 this.hideDialog(); 4493 4494 if (prev == null && mode == App.MODE_DEVICE) 4495 { 4496 if (file != null && EditorUi.nativeFileSupport) 4497 { 4498 this.showSaveFilePicker(mxUtils.bind(this, function(fileHandle, desc) 4499 { 4500 file.fileHandle = fileHandle; 4501 file.mode = App.MODE_DEVICE; 4502 file.title = desc.name; 4503 file.desc = desc; 4504 4505 this.setMode(App.MODE_DEVICE); 4506 this.save(desc.name, done); 4507 }), mxUtils.bind(this, function(e) 4508 { 4509 if (e.name != 'AbortError') 4510 { 4511 this.handleError(e); 4512 } 4513 }), this.createFileSystemOptions(name)); 4514 } 4515 else 4516 { 4517 this.setMode(App.MODE_DEVICE); 4518 this.save(name, done); 4519 } 4520 } 4521 else if (mode == 'download') 4522 { 4523 var tmp = new LocalFile(this, null, name); 4524 tmp.save(); 4525 } 4526 else if (mode == '_blank') 4527 { 4528 window.openFile = new OpenFile(function() 4529 { 4530 window.openFile = null; 4531 }); 4532 4533 // Do not use a filename to use undefined mode 4534 window.openFile.setData(this.getFileData(true)); 4535 this.openLink(this.getUrl(window.location.pathname), null, true); 4536 } 4537 else if (prev != mode) 4538 { 4539 this.pickFolder(mode, mxUtils.bind(this, function(folderId) 4540 { 4541 this.createFile(name, this.getFileData(/(\.xml)$/i.test(name) || 4542 name.indexOf('.') < 0 || /(\.drawio)$/i.test(name), 4543 /(\.svg)$/i.test(name), /(\.html)$/i.test(name)), 4544 null, mode, done, this.mode == null, folderId); 4545 })); 4546 } 4547 else if (mode != null) 4548 { 4549 this.save(name, done); 4550 } 4551 } 4552 } 4553 }), mxUtils.bind(this, function() 4554 { 4555 this.hideDialog(); 4556 }), mxResources.get('saveAs'), mxResources.get('download'), null, null, allowTab, 4557 null, true, rowLimit, null, null, null, this.editor.fileExtensions, false); 4558 this.showDialog(dlg.container, 400, (serviceCount > rowLimit) ? 390 : 270, true, true); 4559 dlg.init(); 4560 } 4561 } 4562}; 4563 4564/** 4565 * Translates this point by the given vector. 4566 * 4567 * @param {number} dx X-coordinate of the translation. 4568 * @param {number} dy Y-coordinate of the translation. 4569 */ 4570App.prototype.loadTemplate = function(url, onload, onerror, templateFilename, asLibrary) 4571{ 4572 var base64 = false; 4573 var realUrl = url; 4574 4575 if (!this.editor.isCorsEnabledForUrl(realUrl)) 4576 { 4577 // Always uses base64 response to check magic numbers for file type 4578 var nocache = 't=' + new Date().getTime(); 4579 realUrl = PROXY_URL + '?url=' + encodeURIComponent(url) + '&base64=1&' + nocache; 4580 base64 = true; 4581 } 4582 4583 var filterFn = (templateFilename != null) ? templateFilename : url; 4584 4585 this.editor.loadUrl(realUrl, mxUtils.bind(this, function(responseData) 4586 { 4587 try 4588 { 4589 var data = (!base64) ? responseData : ((window.atob && !mxClient.IS_IE && !mxClient.IS_IE11) ? 4590 atob(responseData) : Base64.decode(responseData)); 4591 var isVisioFilename = /(\.v(dx|sdx?))($|\?)/i.test(filterFn) || 4592 /(\.vs(x|sx?))($|\?)/i.test(filterFn); 4593 4594 if (isVisioFilename || this.isVisioData(data)) 4595 { 4596 // Adds filename to control converter code 4597 if (!isVisioFilename) 4598 { 4599 if (asLibrary) 4600 { 4601 filterFn = this.isRemoteVisioData(data) ? 'raw.vss' : 'raw.vssx'; 4602 } 4603 else 4604 { 4605 filterFn = this.isRemoteVisioData(data) ? 'raw.vsd' : 'raw.vsdx'; 4606 } 4607 } 4608 4609 this.importVisio(this.base64ToBlob(responseData.substring(responseData.indexOf(',') + 1)), function(xml) 4610 { 4611 onload(xml); 4612 }, onerror, filterFn); 4613 } 4614 else if (!this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, filterFn)) 4615 { 4616 // Asynchronous parsing via server 4617 this.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr) 4618 { 4619 if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 299 && 4620 xhr.responseText.substring(0, 13) == '<mxGraphModel') 4621 { 4622 onload(xhr.responseText); 4623 } 4624 }), url); 4625 } 4626 else if (this.isLucidChartData(data)) 4627 { 4628 this.convertLucidChart(data, mxUtils.bind(this, function(xml) 4629 { 4630 onload(xml); 4631 }), mxUtils.bind(this, function(e) 4632 { 4633 onerror(e); 4634 })); 4635 } 4636 else 4637 { 4638 if (/(\.png)($|\?)/i.test(filterFn) || this.isPngData(data)) 4639 { 4640 data = this.extractGraphModelFromPng(responseData); 4641 } 4642 4643 onload(data); 4644 } 4645 } 4646 catch (e) 4647 { 4648 onerror(e); 4649 } 4650 }), onerror, /(\.png)($|\?)/i.test(filterFn) || /(\.v(dx|sdx?))($|\?)/i.test(filterFn) || 4651 /(\.vs(x|sx?))($|\?)/i.test(filterFn), null, null, base64); 4652}; 4653 4654/** 4655 * Translates this point by the given vector. 4656 * 4657 * @param {number} dx X-coordinate of the translation. 4658 * @param {number} dy Y-coordinate of the translation. 4659 */ 4660App.prototype.getPeerForMode = function(mode) 4661{ 4662 if (mode == App.MODE_GOOGLE) 4663 { 4664 return this.drive; 4665 } 4666 else if (mode == App.MODE_GITHUB) 4667 { 4668 return this.gitHub; 4669 } 4670 else if (mode == App.MODE_GITLAB) 4671 { 4672 return this.gitLab; 4673 } 4674 else if (mode == App.MODE_DROPBOX) 4675 { 4676 return this.dropbox; 4677 } 4678 else if (mode == App.MODE_ONEDRIVE) 4679 { 4680 return this.oneDrive; 4681 } 4682 else if (mode == App.MODE_TRELLO) 4683 { 4684 return this.trello; 4685 } 4686 else if (mode == App.MODE_NOTION) 4687 { 4688 return this.notion; 4689 } 4690 else 4691 { 4692 return null; 4693 } 4694}; 4695 4696/** 4697 * Translates this point by the given vector. 4698 * 4699 * @param {number} dx X-coordinate of the translation. 4700 * @param {number} dy Y-coordinate of the translation. 4701 */ 4702App.prototype.createFile = function(title, data, libs, mode, done, replace, folderId, tempFile, clibs) 4703{ 4704 mode = (tempFile) ? null : ((mode != null) ? mode : this.mode); 4705 4706 if (title != null && this.spinner.spin(document.body, mxResources.get('inserting'))) 4707 { 4708 data = (data != null) ? data : this.emptyDiagramXml; 4709 4710 var complete = mxUtils.bind(this, function() 4711 { 4712 this.spinner.stop(); 4713 }); 4714 4715 var error = mxUtils.bind(this, function(resp) 4716 { 4717 complete(); 4718 4719 if (resp == null && this.getCurrentFile() == null && this.dialog == null) 4720 { 4721 this.showSplash(); 4722 } 4723 else if (resp != null) 4724 { 4725 this.handleError(resp); 4726 } 4727 }); 4728 4729 try 4730 { 4731 if (mode == App.MODE_GOOGLE && this.drive != null) 4732 { 4733 if (folderId == null && this.stateArg != null && this.stateArg.folderId != null) 4734 { 4735 folderId = this.stateArg.folderId; 4736 } 4737 4738 this.drive.insertFile(title, data, folderId, mxUtils.bind(this, function(file) 4739 { 4740 complete(); 4741 this.fileCreated(file, libs, replace, done, clibs); 4742 }), error); 4743 } 4744 else if (mode == App.MODE_GITHUB && this.gitHub != null) 4745 { 4746 this.gitHub.insertFile(title, data, mxUtils.bind(this, function(file) 4747 { 4748 complete(); 4749 this.fileCreated(file, libs, replace, done, clibs); 4750 }), error, false, folderId); 4751 } 4752 else if (mode == App.MODE_GITLAB && this.gitLab != null) 4753 { 4754 this.gitLab.insertFile(title, data, mxUtils.bind(this, function(file) 4755 { 4756 complete(); 4757 this.fileCreated(file, libs, replace, done, clibs); 4758 }), error, false, folderId); 4759 } 4760 else if (mode == App.MODE_NOTION && this.notion != null) 4761 { 4762 this.notion.insertFile(title, data, mxUtils.bind(this, function(file) 4763 { 4764 complete(); 4765 this.fileCreated(file, libs, replace, done, clibs); 4766 }), error, false, folderId); 4767 } 4768 else if (mode == App.MODE_TRELLO && this.trello != null) 4769 { 4770 this.trello.insertFile(title, data, mxUtils.bind(this, function(file) 4771 { 4772 complete(); 4773 this.fileCreated(file, libs, replace, done, clibs); 4774 }), error, false, folderId); 4775 } 4776 else if (mode == App.MODE_DROPBOX && this.dropbox != null) 4777 { 4778 this.dropbox.insertFile(title, data, mxUtils.bind(this, function(file) 4779 { 4780 complete(); 4781 this.fileCreated(file, libs, replace, done, clibs); 4782 }), error); 4783 } 4784 else if (mode == App.MODE_ONEDRIVE && this.oneDrive != null) 4785 { 4786 this.oneDrive.insertFile(title, data, mxUtils.bind(this, function(file) 4787 { 4788 complete(); 4789 this.fileCreated(file, libs, replace, done, clibs); 4790 }), error, false, folderId); 4791 } 4792 else if (mode == App.MODE_BROWSER) 4793 { 4794 StorageFile.insertFile(this, title, data, mxUtils.bind(this, function(file) 4795 { 4796 complete(); 4797 this.fileCreated(file, libs, replace, done, clibs); 4798 }), error); 4799 } 4800 else if (!tempFile && mode == App.MODE_DEVICE && EditorUi.nativeFileSupport) 4801 { 4802 complete(); 4803 4804 this.showSaveFilePicker(mxUtils.bind(this, function(fileHandle, desc) 4805 { 4806 var file = new LocalFile(this, data, desc.name, null, fileHandle, desc); 4807 4808 file.saveFile(desc.name, false, mxUtils.bind(this, function() 4809 { 4810 this.fileCreated(file, libs, replace, done, clibs); 4811 }), error, true); 4812 }), mxUtils.bind(this, function(e) 4813 { 4814 if (e.name != 'AbortError') 4815 { 4816 error(e); 4817 } 4818 }), this.createFileSystemOptions(title)); 4819 } 4820 else 4821 { 4822 complete(); 4823 this.fileCreated(new LocalFile(this, data, title, mode == null), libs, replace, done, clibs); 4824 } 4825 } 4826 catch (e) 4827 { 4828 complete(); 4829 this.handleError(e); 4830 } 4831 } 4832}; 4833 4834/** 4835 * Translates this point by the given vector. 4836 * 4837 * @param {number} dx X-coordinate of the translation. 4838 * @param {number} dy Y-coordinate of the translation. 4839 */ 4840App.prototype.fileCreated = function(file, libs, replace, done, clibs) 4841{ 4842 var url = window.location.pathname; 4843 4844 if (libs != null && libs.length > 0) 4845 { 4846 url += '?libs=' + libs; 4847 } 4848 4849 if (clibs != null && clibs.length > 0) 4850 { 4851 url += '?clibs=' + clibs; 4852 } 4853 4854 url = this.getUrl(url); 4855 4856 // Always opens a new tab for local files to avoid losing changes 4857 if (file.getMode() != App.MODE_DEVICE) 4858 { 4859 url += '#' + file.getHash(); 4860 } 4861 4862 // Makes sure to produce consistent output with finalized files via createFileData this needs 4863 // to save the file again since it needs the newly created file ID for redirecting in HTML 4864 if (this.spinner.spin(document.body, mxResources.get('inserting'))) 4865 { 4866 var data = file.getData(); 4867 var dataNode = (data.length > 0) ? this.editor.extractGraphModel( 4868 mxUtils.parseXml(data).documentElement, true) : null; 4869 var redirect = window.location.protocol + '//' + window.location.hostname + url; 4870 var node = dataNode; 4871 var graph = null; 4872 4873 // Handles special case where SVG files need a rendered graph to be saved 4874 if (dataNode != null && /\.svg$/i.test(file.getTitle())) 4875 { 4876 graph = this.createTemporaryGraph(this.editor.graph.getStylesheet()); 4877 document.body.appendChild(graph.container); 4878 node = this.decodeNodeIntoGraph(node, graph); 4879 } 4880 4881 file.setData(this.createFileData(dataNode, graph, file, redirect)); 4882 4883 if (graph != null) 4884 { 4885 graph.container.parentNode.removeChild(graph.container); 4886 } 4887 4888 var complete = mxUtils.bind(this, function() 4889 { 4890 this.spinner.stop(); 4891 }); 4892 4893 var fn = mxUtils.bind(this, function() 4894 { 4895 complete(); 4896 4897 var currentFile = this.getCurrentFile(); 4898 4899 if (replace == null && currentFile != null) 4900 { 4901 replace = !currentFile.isModified() && currentFile.getMode() == null; 4902 } 4903 4904 var fn3 = mxUtils.bind(this, function() 4905 { 4906 window.openFile = null; 4907 this.fileLoaded(file); 4908 4909 if (replace) 4910 { 4911 file.addAllSavedStatus(); 4912 } 4913 4914 if (libs != null) 4915 { 4916 this.sidebar.showEntries(libs); 4917 } 4918 4919 if (clibs != null) 4920 { 4921 var temp = []; 4922 var tokens = clibs.split(';'); 4923 4924 for (var i = 0; i < tokens.length; i++) 4925 { 4926 temp.push(decodeURIComponent(tokens[i])); 4927 } 4928 4929 this.loadLibraries(temp); 4930 } 4931 }); 4932 4933 var fn2 = mxUtils.bind(this, function() 4934 { 4935 if (replace || currentFile == null || !currentFile.isModified()) 4936 { 4937 fn3(); 4938 } 4939 else 4940 { 4941 this.confirm(mxResources.get('allChangesLost'), null, fn3, 4942 mxResources.get('cancel'), mxResources.get('discardChanges')); 4943 } 4944 }); 4945 4946 if (done != null) 4947 { 4948 done(); 4949 } 4950 4951 // Opens the file in a new window 4952 if (replace != null && !replace) 4953 { 4954 // Opens local file in a new window 4955 if (file.constructor == LocalFile) 4956 { 4957 window.openFile = new OpenFile(function() 4958 { 4959 window.openFile = null; 4960 }); 4961 4962 window.openFile.setData(file.getData(), file.getTitle(), file.getMode() == null); 4963 } 4964 4965 if (done != null) 4966 { 4967 done(); 4968 } 4969 4970 window.openWindow(url, null, fn2); 4971 } 4972 else 4973 { 4974 fn2(); 4975 } 4976 }); 4977 4978 // Updates data in memory for local files 4979 if (file.constructor == LocalFile) 4980 { 4981 fn(); 4982 } 4983 else 4984 { 4985 file.saveFile(file.getTitle(), false, mxUtils.bind(this, function() 4986 { 4987 fn(); 4988 }), mxUtils.bind(this, function(resp) 4989 { 4990 complete(); 4991 this.handleError(resp); 4992 })); 4993 } 4994 } 4995}; 4996 4997/** 4998 * Translates this point by the given vector. 4999 * 5000 * @param {number} dx X-coordinate of the translation. 5001 * @param {number} dy Y-coordinate of the translation. 5002 */ 5003App.prototype.loadFile = function(id, sameWindow, file, success, force) 5004{ 5005 if (urlParams['openInSameWin'] == '1' || navigator.standalone) 5006 { 5007 sameWindow = true; 5008 } 5009 5010 this.hideDialog(); 5011 5012 var fn2 = mxUtils.bind(this, function() 5013 { 5014 if (id == null || id.length == 0) 5015 { 5016 this.editor.setStatus(''); 5017 this.fileLoaded(null); 5018 } 5019 else if (this.spinner.spin(document.body, mxResources.get('loading'))) 5020 { 5021 // Handles files from localStorage 5022 if (id.charAt(0) == 'L') 5023 { 5024 this.spinner.stop(); 5025 5026 if (!isLocalStorage) 5027 { 5028 this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function() 5029 { 5030 var tempFile = this.getCurrentFile(); 5031 window.location.hash = (tempFile != null) ? tempFile.getHash() : ''; 5032 })); 5033 } 5034 else 5035 { 5036 var error = mxUtils.bind(this, function (e) 5037 { 5038 this.handleError(e, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function() 5039 { 5040 var tempFile = this.getCurrentFile(); 5041 window.location.hash = (tempFile != null) ? tempFile.getHash() : ''; 5042 })); 5043 }); 5044 5045 id = decodeURIComponent(id.substring(1)); 5046 5047 StorageFile.getFileContent(this, id, mxUtils.bind(this, function(data) 5048 { 5049 if (data != null) 5050 { 5051 this.fileLoaded(new StorageFile(this, data, id)); 5052 5053 if (success != null) 5054 { 5055 success(); 5056 } 5057 } 5058 else 5059 { 5060 error({message: mxResources.get('fileNotFound')}); 5061 } 5062 }), error); 5063 } 5064 } 5065 else if (file != null) 5066 { 5067 // File already loaded 5068 this.spinner.stop(); 5069 this.fileLoaded(file); 5070 5071 if (success != null) 5072 { 5073 success(); 5074 } 5075 } 5076 else if (id.charAt(0) == 'S') 5077 { 5078 this.spinner.stop(); 5079 5080 this.alert('[Deprecation] #S is no longer supported, go to https://app.diagrams.net/?desc=' + id.substring(1).substring(0, 10), mxUtils.bind(this, function() 5081 { 5082 window.location.href = 'https://app.diagrams.net/?desc=' + id.substring(1); 5083 })); 5084 } 5085 else if (id.charAt(0) == 'R') 5086 { 5087 // Raw file encoded into URL 5088 this.spinner.stop(); 5089 var data = decodeURIComponent(id.substring(1)); 5090 5091 if (data.charAt(0) != '<') 5092 { 5093 data = Graph.decompress(data); 5094 } 5095 5096 var tempFile = new LocalFile(this, data, (urlParams['title'] != null) ? 5097 decodeURIComponent(urlParams['title']) : this.defaultFilename, true); 5098 tempFile.getHash = function() 5099 { 5100 return id; 5101 }; 5102 this.fileLoaded(tempFile); 5103 5104 if (success != null) 5105 { 5106 success(); 5107 } 5108 } 5109 else if (id.charAt(0) == 'E') // Embed file 5110 { 5111 //Currently we only reload current file. Id is not used! 5112 var currentFile = this.getCurrentFile(); 5113 5114 if (currentFile == null) 5115 { 5116 this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile')); 5117 } 5118 else 5119 { 5120 this.remoteInvoke('getDraftFileContent', null, null, mxUtils.bind(this, function(data, desc) 5121 { 5122 this.spinner.stop(); 5123 this.fileLoaded(new EmbedFile(this, data, desc)); 5124 5125 if (success != null) 5126 { 5127 success(); 5128 } 5129 }), mxUtils.bind(this, function() 5130 { 5131 this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile')); 5132 })); 5133 } 5134 } 5135 else if (id.charAt(0) == 'U') 5136 { 5137 var url = decodeURIComponent(id.substring(1)); 5138 5139 var doFallback = mxUtils.bind(this, function() 5140 { 5141 // Fallback for non-public Google Drive files 5142 if (url.substring(0, 31) == 'https://drive.google.com/uc?id=' && 5143 (this.drive != null || typeof window.DriveClient === 'function')) 5144 { 5145 this.hideDialog(); 5146 5147 var fallback = mxUtils.bind(this, function() 5148 { 5149 this.spinner.stop(); 5150 5151 if (this.drive != null) 5152 { 5153 var tempId = url.substring(31, url.lastIndexOf('&ex')); 5154 5155 this.loadFile('G' + tempId, sameWindow, null, mxUtils.bind(this, function() 5156 { 5157 var currentFile = this.getCurrentFile(); 5158 5159 if (currentFile != null && this.editor.chromeless && !this.editor.editable) 5160 { 5161 currentFile.getHash = function() 5162 { 5163 return 'G' + tempId; 5164 }; 5165 5166 window.location.hash = '#' + currentFile.getHash(); 5167 } 5168 5169 if (success != null) 5170 { 5171 success(); 5172 } 5173 })); 5174 5175 return true; 5176 } 5177 else 5178 { 5179 return false; 5180 } 5181 }); 5182 5183 if (!fallback() && this.spinner.spin(document.body, mxResources.get('loading'))) 5184 { 5185 this.addListener('clientLoaded', fallback); 5186 } 5187 5188 return true; 5189 } 5190 else 5191 { 5192 return false; 5193 } 5194 }); 5195 5196 this.loadTemplate(url, mxUtils.bind(this, function(text) 5197 { 5198 this.spinner.stop(); 5199 5200 if (text != null && text.length > 0) 5201 { 5202 var filename = this.defaultFilename; 5203 5204 // Tries to find name from URL with valid extensions 5205 if (urlParams['title'] == null && urlParams['notitle'] != '1') 5206 { 5207 var tmp = url; 5208 var dot = url.lastIndexOf('.'); 5209 var slash = tmp.lastIndexOf('/'); 5210 5211 if (dot > slash && slash > 0) 5212 { 5213 tmp = tmp.substring(slash + 1, dot); 5214 var ext = url.substring(dot); 5215 5216 if (!this.useCanvasForExport && ext == '.png') 5217 { 5218 ext = '.drawio'; 5219 } 5220 5221 if (ext === '.svg' || ext === '.xml' || 5222 ext === '.html' || ext === '.png' || 5223 ext === '.drawio') 5224 { 5225 filename = tmp + ext; 5226 } 5227 } 5228 } 5229 5230 var tempFile = new LocalFile(this, text, (urlParams['title'] != null) ? 5231 decodeURIComponent(urlParams['title']) : filename, true); 5232 tempFile.getHash = function() 5233 { 5234 return id; 5235 }; 5236 5237 if (this.fileLoaded(tempFile, true)) 5238 { 5239 if (success != null) 5240 { 5241 success(); 5242 } 5243 } 5244 else if (!doFallback()) 5245 { 5246 this.handleError({message: mxResources.get('fileNotFound')}, 5247 mxResources.get('errorLoadingFile')); 5248 } 5249 } 5250 else if (!doFallback()) 5251 { 5252 this.handleError({message: mxResources.get('fileNotFound')}, 5253 mxResources.get('errorLoadingFile')); 5254 } 5255 }), mxUtils.bind(this, function() 5256 { 5257 if (!doFallback()) 5258 { 5259 this.spinner.stop(); 5260 this.handleError({message: mxResources.get('fileNotFound')}, 5261 mxResources.get('errorLoadingFile')); 5262 } 5263 }), (urlParams['template-filename'] != null) ? 5264 decodeURIComponent(urlParams['template-filename']) : null); 5265 } 5266 else 5267 { 5268 // Google Drive files are handled as default file types 5269 var peer = null; 5270 5271 if (id.charAt(0) == 'G') 5272 { 5273 peer = this.drive; 5274 } 5275 else if (id.charAt(0) == 'D') 5276 { 5277 peer = this.dropbox; 5278 } 5279 else if (id.charAt(0) == 'W') 5280 { 5281 peer = this.oneDrive; 5282 } 5283 else if (id.charAt(0) == 'H') 5284 { 5285 peer = this.gitHub; 5286 } 5287 else if (id.charAt(0) == 'A') 5288 { 5289 peer = this.gitLab; 5290 } 5291 else if (id.charAt(0) == 'T') 5292 { 5293 peer = this.trello; 5294 } 5295 else if (id.charAt(0) == 'N') 5296 { 5297 peer = this.notion; 5298 } 5299 5300 if (peer == null) 5301 { 5302 this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function() 5303 { 5304 var currentFile = this.getCurrentFile(); 5305 window.location.hash = (currentFile != null) ? currentFile.getHash() : ''; 5306 })); 5307 } 5308 else 5309 { 5310 var peerChar = id.charAt(0); 5311 id = decodeURIComponent(id.substring(1)); 5312 5313 peer.getFile(id, mxUtils.bind(this, function(file) 5314 { 5315 this.spinner.stop(); 5316 this.fileLoaded(file); 5317 var currentFile = this.getCurrentFile(); 5318 5319 if (currentFile == null) 5320 { 5321 window.location.hash = ''; 5322 this.showSplash(); 5323 } 5324 else if (this.editor.chromeless && !this.editor.editable) 5325 { 5326 // Keeps ID even for converted files in chromeless mode for refresh to work 5327 currentFile.getHash = function() 5328 { 5329 return peerChar + id; 5330 }; 5331 5332 window.location.hash = '#' + currentFile.getHash(); 5333 } 5334 else if (file == currentFile && file.getMode() == null) 5335 { 5336 // Shows a warning if a copy was opened which happens 5337 // eg. for .png files in IE as they cannot be written 5338 var status = mxResources.get('copyCreated'); 5339 this.editor.setStatus('<div title="'+ status + 5340 '" class="geStatusAlert">' + status + '</div>'); 5341 } 5342 5343 if (success != null) 5344 { 5345 success(); 5346 } 5347 }), mxUtils.bind(this, function(resp) 5348 { 5349 // Makes sure the file does not save the invalid UI model and overwrites anything important 5350 if (window.console != null && resp != null) 5351 { 5352 console.log('error in loadFile:', id, resp); 5353 } 5354 5355 this.handleError(resp, (resp != null) ? mxResources.get('errorLoadingFile') : null, mxUtils.bind(this, function() 5356 { 5357 var currentFile = this.getCurrentFile(); 5358 5359 if (currentFile == null) 5360 { 5361 window.location.hash = ''; 5362 this.showSplash(); 5363 } 5364 else 5365 { 5366 window.location.hash = '#' + currentFile.getHash(); 5367 } 5368 }), null, null, '#' + peerChar + id); 5369 })); 5370 } 5371 } 5372 } 5373 }); 5374 5375 var currentFile = this.getCurrentFile(); 5376 5377 var fn = mxUtils.bind(this, function() 5378 { 5379 if (force || currentFile == null || !currentFile.isModified()) 5380 { 5381 fn2(); 5382 } 5383 else 5384 { 5385 this.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function() 5386 { 5387 if (currentFile != null) 5388 { 5389 window.location.hash = currentFile.getHash(); 5390 } 5391 }), fn2, mxResources.get('cancel'), mxResources.get('discardChanges')); 5392 } 5393 }); 5394 5395 if (id == null || id.length == 0) 5396 { 5397 fn(); 5398 } 5399 else if (currentFile != null && !sameWindow) 5400 { 5401 this.showDialog(new PopupDialog(this, this.getUrl() + '#' + id, 5402 null, fn).container, 320, 140, true, true); 5403 } 5404 else 5405 { 5406 fn(); 5407 } 5408}; 5409 5410/** 5411 * Translates this point by the given vector. 5412 * 5413 * @param {number} dx X-coordinate of the translation. 5414 * @param {number} dy Y-coordinate of the translation. 5415 */ 5416App.prototype.getLibraryStorageHint = function(file) 5417{ 5418 var tip = file.getTitle(); 5419 5420 if (file.constructor != LocalLibrary) 5421 { 5422 tip += '\n' + file.getHash(); 5423 } 5424 5425 if (file.constructor == DriveLibrary) 5426 { 5427 tip += ' (' + mxResources.get('googleDrive') + ')'; 5428 } 5429 else if (file.constructor == GitHubLibrary) 5430 { 5431 tip += ' (' + mxResources.get('github') + ')'; 5432 } 5433 else if (file.constructor == TrelloLibrary) 5434 { 5435 tip += ' (' + mxResources.get('trello') + ')'; 5436 } 5437 else if (file.constructor == DropboxLibrary) 5438 { 5439 tip += ' (' + mxResources.get('dropbox') + ')'; 5440 } 5441 else if (file.constructor == OneDriveLibrary) 5442 { 5443 tip += ' (' + mxResources.get('oneDrive') + ')'; 5444 } 5445 else if (file.constructor == StorageLibrary) 5446 { 5447 tip += ' (' + mxResources.get('browser') + ')'; 5448 } 5449 else if (file.constructor == LocalLibrary) 5450 { 5451 tip += ' (' + mxResources.get('device') + ')'; 5452 } 5453 5454 return tip; 5455}; 5456 5457/** 5458 * Updates action states depending on the selection. 5459 */ 5460App.prototype.restoreLibraries = function() 5461{ 5462 this.loadLibraries(mxSettings.getCustomLibraries(), mxUtils.bind(this, function() 5463 { 5464 this.loadLibraries((urlParams['clibs'] || '').split(';')); 5465 })); 5466}; 5467 5468/** 5469 * Updates action states depending on the selection. 5470 */ 5471App.prototype.loadLibraries = function(libs, done) 5472{ 5473 if (this.sidebar != null) 5474 { 5475 if (this.pendingLibraries == null) 5476 { 5477 this.pendingLibraries = new Object(); 5478 } 5479 5480 // Ignores this library next time 5481 var ignore = mxUtils.bind(this, function(id, keep) 5482 { 5483 if (!keep) 5484 { 5485 mxSettings.removeCustomLibrary(id); 5486 } 5487 5488 delete this.pendingLibraries[id]; 5489 }); 5490 5491 var waiting = 0; 5492 var files = []; 5493 5494 // Loads in order of libs array 5495 var checkDone = mxUtils.bind(this, function() 5496 { 5497 if (waiting == 0) 5498 { 5499 if (libs != null) 5500 { 5501 for (var i = libs.length - 1; i >= 0; i--) 5502 { 5503 if (files[i] != null) 5504 { 5505 this.loadLibrary(files[i]); 5506 } 5507 } 5508 } 5509 5510 if (done != null) 5511 { 5512 done(); 5513 } 5514 } 5515 }); 5516 5517 if (libs != null) 5518 { 5519 for (var i = 0; i < libs.length; i++) 5520 { 5521 var name = encodeURIComponent(decodeURIComponent(libs[i])); 5522 5523 (mxUtils.bind(this, function(id, index) 5524 { 5525 if (id != null && id.length > 0 && this.pendingLibraries[id] == null && 5526 this.sidebar.palettes[id] == null) 5527 { 5528 // Waits for all libraries to load 5529 waiting++; 5530 5531 var onload = mxUtils.bind(this, function(file) 5532 { 5533 delete this.pendingLibraries[id]; 5534 files[index] = file; 5535 waiting--; 5536 checkDone(); 5537 }); 5538 5539 var onerror = mxUtils.bind(this, function(keep) 5540 { 5541 ignore(id, keep); 5542 waiting--; 5543 checkDone(); 5544 }); 5545 5546 this.pendingLibraries[id] = true; 5547 var service = id.substring(0, 1); 5548 5549 if (service == 'L') 5550 { 5551 if (isLocalStorage || mxClient.IS_CHROMEAPP) 5552 { 5553 // Make asynchronous for barrier to work 5554 window.setTimeout(mxUtils.bind(this, function() 5555 { 5556 try 5557 { 5558 var name = decodeURIComponent(id.substring(1)); 5559 5560 StorageFile.getFileContent(this, name, mxUtils.bind(this, function(xml) 5561 { 5562 if (name == '.scratchpad' && xml == null) 5563 { 5564 xml = this.emptyLibraryXml; 5565 } 5566 5567 if (xml != null) 5568 { 5569 onload(new StorageLibrary(this, xml, name)); 5570 } 5571 else 5572 { 5573 onerror(); 5574 } 5575 }), onerror); 5576 } 5577 catch (e) 5578 { 5579 onerror(); 5580 } 5581 }), 0); 5582 } 5583 } 5584 else if (service == 'U') 5585 { 5586 var url = decodeURIComponent(id.substring(1)); 5587 5588 if (!this.isOffline()) 5589 { 5590 this.loadTemplate(url, mxUtils.bind(this, function(text) 5591 { 5592 if (text != null && text.length > 0) 5593 { 5594 // LATER: Convert mxfile to mxlibrary using code from libraryLoaded 5595 onload(new UrlLibrary(this, text, url)); 5596 } 5597 else 5598 { 5599 onerror(); 5600 } 5601 }), function() 5602 { 5603 onerror(); 5604 }, null, true); 5605 } 5606 } 5607 else if (service == 'R') 5608 { 5609 var libDesc = decodeURIComponent(id.substring(1)); 5610 5611 try 5612 { 5613 libDesc = JSON.parse(libDesc); 5614 var libObj = { 5615 id: libDesc[0], 5616 title: libDesc[1], 5617 downloadUrl: libDesc[2] 5618 } 5619 5620 this.remoteInvoke('getFileContent', [libObj.downloadUrl], null, mxUtils.bind(this, function(libContent) 5621 { 5622 try 5623 { 5624 onload(new RemoteLibrary(this, libContent, libObj)); 5625 } 5626 catch (e) 5627 { 5628 onerror(); 5629 } 5630 }), function() 5631 { 5632 onerror(); 5633 }); 5634 } 5635 catch (e) 5636 { 5637 onerror(); 5638 } 5639 } 5640 else if (service == 'S' && this.loadDesktopLib != null) 5641 { 5642 try 5643 { 5644 this.loadDesktopLib(decodeURIComponent(id.substring(1)), function(desktopLib) 5645 { 5646 onload(desktopLib); 5647 }, onerror); 5648 } 5649 catch (e) 5650 { 5651 onerror(); 5652 } 5653 } 5654 else 5655 { 5656 var peer = null; 5657 5658 if (service == 'G') 5659 { 5660 if (this.drive != null && this.drive.user != null) 5661 { 5662 peer = this.drive; 5663 } 5664 } 5665 else if (service == 'H') 5666 { 5667 if (this.gitHub != null && this.gitHub.getUser() != null) 5668 { 5669 peer = this.gitHub; 5670 } 5671 } 5672 else if (service == 'T') 5673 { 5674 if (this.trello != null && this.trello.isAuthorized()) 5675 { 5676 peer = this.trello; 5677 } 5678 } 5679 else if (service == 'D') 5680 { 5681 if (this.dropbox != null && this.dropbox.getUser() != null) 5682 { 5683 peer = this.dropbox; 5684 } 5685 } 5686 else if (service == 'W') 5687 { 5688 if (this.oneDrive != null && this.oneDrive.getUser() != null) 5689 { 5690 peer = this.oneDrive; 5691 } 5692 } 5693 5694 if (peer != null) 5695 { 5696 peer.getLibrary(decodeURIComponent(id.substring(1)), mxUtils.bind(this, function(file) 5697 { 5698 try 5699 { 5700 onload(file); 5701 } 5702 catch (e) 5703 { 5704 onerror(); 5705 } 5706 }), function(resp) 5707 { 5708 onerror(); 5709 }); 5710 } 5711 else 5712 { 5713 onerror(true); 5714 } 5715 } 5716 } 5717 }))(name, i); 5718 } 5719 5720 checkDone(); 5721 } 5722 else 5723 { 5724 checkDone(); 5725 } 5726 } 5727}; 5728 5729/** 5730 * Translates this point by the given vector. 5731 * 5732 * @param {number} dx X-coordinate of the translation. 5733 * @param {number} dy Y-coordinate of the translation. 5734 */ 5735App.prototype.updateButtonContainer = function() 5736{ 5737 if (this.buttonContainer != null) 5738 { 5739 var file = this.getCurrentFile(); 5740 5741 if (urlParams['embed'] == '1') 5742 { 5743 if (uiTheme == 'atlas' || urlParams['atlas'] == '1') 5744 { 5745 this.buttonContainer.style.paddingRight = '12px'; 5746 this.buttonContainer.style.paddingTop = '6px'; 5747 this.buttonContainer.style.right = urlParams['noLangIcon'] == '1'? '0' : '25px'; 5748 } 5749 else if (uiTheme != 'min') 5750 { 5751 this.buttonContainer.style.paddingRight = '38px'; 5752 this.buttonContainer.style.paddingTop = '6px'; 5753 } 5754 } 5755 5756 // Comments 5757 if (this.commentsSupported() && urlParams['sketch'] != '1') 5758 { 5759 if (this.commentButton == null) 5760 { 5761 this.commentButton = document.createElement('a'); 5762 this.commentButton.setAttribute('title', mxResources.get('comments')); 5763 this.commentButton.className = 'geToolbarButton'; 5764 this.commentButton.style.cssText = 'display:inline-block;position:relative;box-sizing:border-box;' + 5765 'margin-right:4px;float:left;cursor:pointer;width:24px;height:24px;background-size:24px 24px;' + 5766 'background-position:center center;background-repeat:no-repeat;background-image:' + 5767 'url(' + Editor.commentImage + ');'; 5768 5769 if (uiTheme == 'atlas') 5770 { 5771 this.commentButton.style.marginRight = '10px'; 5772 this.commentButton.style.marginTop = '-3px'; 5773 } 5774 else if (uiTheme == 'min') 5775 { 5776 this.commentButton.style.marginTop = '1px'; 5777 } 5778 else if (urlParams['atlas'] == '1') 5779 { 5780 this.commentButton.style.marginTop = '-2px'; 5781 } 5782 else 5783 { 5784 this.commentButton.style.marginTop = '-5px'; 5785 } 5786 5787 mxEvent.addListener(this.commentButton, 'click', mxUtils.bind(this, function() 5788 { 5789 this.actions.get('comments').funct(); 5790 })); 5791 5792 this.buttonContainer.appendChild(this.commentButton); 5793 5794 if (uiTheme == 'dark' || uiTheme == 'atlas') 5795 { 5796 this.commentButton.style.filter = 'invert(100%)'; 5797 } 5798 } 5799 } 5800 else if (this.commentButton != null) 5801 { 5802 this.commentButton.parentNode.removeChild(this.commentButton); 5803 this.commentButton = null; 5804 } 5805 5806 // Share 5807 if (urlParams['embed'] != '1' && this.getServiceName() == 'draw.io' && 5808 !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && 5809 !this.isOfflineApp()) 5810 { 5811 if (file != null) 5812 { 5813 if (this.shareButton == null) 5814 { 5815 this.shareButton = document.createElement('div'); 5816 this.shareButton.className = 'geBtn gePrimaryBtn'; 5817 this.shareButton.style.display = 'inline-block'; 5818 this.shareButton.style.backgroundColor = '#F2931E'; 5819 this.shareButton.style.borderColor = '#F08705'; 5820 this.shareButton.style.backgroundImage = 'none'; 5821 this.shareButton.style.padding = '2px 10px 0 10px'; 5822 this.shareButton.style.marginTop = '-10px'; 5823 this.shareButton.style.height = '28px'; 5824 this.shareButton.style.lineHeight = '28px'; 5825 this.shareButton.style.minWidth = '0px'; 5826 this.shareButton.style.cssFloat = 'right'; 5827 this.shareButton.setAttribute('title', mxResources.get('share')); 5828 5829 var icon = document.createElement('img'); 5830 icon.setAttribute('src', this.shareImage); 5831 icon.setAttribute('align', 'absmiddle'); 5832 icon.style.marginRight = '4px'; 5833 icon.style.marginTop = '-3px'; 5834 this.shareButton.appendChild(icon); 5835 5836 if (!Editor.isDarkMode() && uiTheme != 'atlas') 5837 { 5838 this.shareButton.style.color = 'black'; 5839 icon.style.filter = 'invert(100%)'; 5840 } 5841 5842 mxUtils.write(this.shareButton, mxResources.get('share')); 5843 5844 mxEvent.addListener(this.shareButton, 'click', mxUtils.bind(this, function() 5845 { 5846 this.actions.get('share').funct(); 5847 })); 5848 5849 this.buttonContainer.appendChild(this.shareButton); 5850 } 5851 } 5852 else if (this.shareButton != null) 5853 { 5854 this.shareButton.parentNode.removeChild(this.shareButton); 5855 this.shareButton = null; 5856 } 5857 5858 //Fetch notifications 5859 if (urlParams['extAuth'] != '1') //Disable notification with external auth (e.g, Teams app) 5860 { 5861 this.fetchAndShowNotification('online', this.mode); 5862 } 5863 } 5864 else if (urlParams['notif'] != null) //Notif for embed mode 5865 { 5866 this.fetchAndShowNotification(urlParams['notif']); 5867 } 5868 } 5869}; 5870 5871 5872App.prototype.fetchAndShowNotification = function(target, subtarget) 5873{ 5874 if (this.fetchingNotif) 5875 { 5876 return; 5877 } 5878 5879 target = target || 'online'; 5880 var cachedNotifKey = '.notifCache'; 5881 var cachedNotif = null; 5882 5883 var processNotif = mxUtils.bind(this, function(notifs) 5884 { 5885 notifs = notifs.filter(function(notif) 5886 { 5887 return !notif.targets || notif.targets.indexOf(target) > -1 || 5888 (subtarget != null && notif.targets.indexOf(subtarget) > -1); 5889 }); 5890 5891 var lsReadFlag = target + 'NotifReadTS'; 5892 var lastRead = (localStorage != null) ? parseInt(localStorage.getItem(lsReadFlag)) : true; 5893 5894 for (var i = 0; i < notifs.length; i++) 5895 { 5896 notifs[i].isNew = (!lastRead || notifs[i].timestamp > lastRead); 5897 } 5898 5899 this.showNotification(notifs, lsReadFlag); 5900 }); 5901 5902 try 5903 { 5904 if (localStorage != null) 5905 { 5906 cachedNotif = JSON.parse(localStorage.getItem(cachedNotifKey)); 5907 } 5908 } 5909 catch(e) {} //Ignore 5910 5911 if (cachedNotif == null || cachedNotif.ts + 24 * 60 * 60 * 1000 < Date.now()) //Cache for one day 5912 { 5913 this.fetchingNotif = true; 5914 //Fetch all notifications and store them, then filter client-side 5915 mxUtils.get(NOTIFICATIONS_URL, mxUtils.bind(this, function(req) 5916 { 5917 if (req.getStatus() >= 200 && req.getStatus() <= 299) 5918 { 5919 var notifs = JSON.parse(req.getText()); 5920 5921 //Process and sort 5922 notifs.sort(function(a, b) 5923 { 5924 return b.timestamp - a.timestamp; 5925 }); 5926 5927 if (isLocalStorage) 5928 { 5929 localStorage.setItem(cachedNotifKey, JSON.stringify({ts: Date.now(), notifs: notifs})); 5930 } 5931 5932 this.fetchingNotif = false; 5933 processNotif(notifs); 5934 } 5935 })); 5936 } 5937 else 5938 { 5939 processNotif(cachedNotif.notifs); 5940 } 5941}; 5942 5943App.prototype.showNotification = function(notifs, lsReadFlag) 5944{ 5945 var newCount = notifs.length; 5946 5947 if (uiTheme == 'min') 5948 { 5949 newCount = 0; 5950 5951 for (var i = 0; i < notifs.length; i++) 5952 { 5953 if (notifs[i].isNew) 5954 { 5955 newCount++; 5956 } 5957 } 5958 } 5959 5960 if (newCount == 0) 5961 { 5962 if (this.notificationBtn != null) 5963 { 5964 this.notificationBtn.style.display = 'none'; 5965 this.editor.fireEvent(new mxEventObject('statusChanged')); 5966 } 5967 5968 return; 5969 } 5970 5971 function shouldAnimate(newNotif) 5972 { 5973 var countEl = document.querySelector('.geNotification-count'); 5974 5975 if (countEl == null) 5976 { 5977 return; 5978 } 5979 5980 countEl.innerHTML = newNotif; 5981 countEl.style.display = newNotif == 0? 'none' : ''; 5982 var notifBell = document.querySelector('.geNotification-bell'); 5983 notifBell.style.animation = newNotif == 0? 'none' : ''; 5984 notifBell.className = 'geNotification-bell' + (newNotif == 0? ' geNotification-bellOff' : ''); 5985 document.querySelector('.geBell-rad').style.animation = newNotif == 0? 'none' : ''; 5986 } 5987 5988 var markAllAsRead = mxUtils.bind(this, function() 5989 { 5990 this.notificationWin.style.display = 'none'; 5991 var unread = this.notificationWin.querySelectorAll('.circle.active'); 5992 5993 for (var i = 0; i < unread.length; i++) 5994 { 5995 unread[i].className = 'circle'; 5996 } 5997 5998 if (isLocalStorage && notifs[0]) 5999 { 6000 localStorage.setItem(lsReadFlag, notifs[0].timestamp); 6001 } 6002 }); 6003 6004 if (this.notificationBtn == null) 6005 { 6006 this.notificationBtn = document.createElement('div'); 6007 this.notificationBtn.className = 'geNotification-box'; 6008 6009 if (uiTheme == 'min') 6010 { 6011 this.notificationBtn.style.width = '30px'; 6012 this.notificationBtn.style.top = '4px'; 6013 } 6014 else if (urlParams['atlas'] == '1') 6015 { 6016 this.notificationBtn.style.top = '2px'; 6017 } 6018 6019 var notifCount = document.createElement('span'); 6020 notifCount.className = 'geNotification-count'; 6021 this.notificationBtn.appendChild(notifCount); 6022 6023 var notifBell = document.createElement('div'); 6024 notifBell.className = 'geNotification-bell'; 6025 notifBell.style.opacity = uiTheme == 'min'? '0.5' : ''; 6026 var bellPart = document.createElement('span'); 6027 bellPart.className = 'geBell-top'; 6028 notifBell.appendChild(bellPart); 6029 var bellPart = document.createElement('span'); 6030 bellPart.className = 'geBell-middle'; 6031 notifBell.appendChild(bellPart); 6032 var bellPart = document.createElement('span'); 6033 bellPart.className = 'geBell-bottom'; 6034 notifBell.appendChild(bellPart); 6035 var bellPart = document.createElement('span'); 6036 bellPart.className = 'geBell-rad'; 6037 notifBell.appendChild(bellPart); 6038 this.notificationBtn.appendChild(notifBell); 6039 6040 //Add as first child such that it is the left-most one 6041 this.buttonContainer.insertBefore(this.notificationBtn, this.buttonContainer.firstChild); 6042 6043 this.notificationWin = document.createElement('div'); 6044 this.notificationWin.className = 'geNotifPanel'; 6045 this.notificationWin.style.display = 'none'; 6046 document.body.appendChild(this.notificationWin); 6047 6048 var winHeader = document.createElement('div'); 6049 winHeader.className = 'header'; 6050 var winTitle = document.createElement('span'); 6051 winTitle.className = 'title'; 6052 winTitle.textContent = mxResources.get('notifications'); 6053 winHeader.appendChild(winTitle); 6054 var winClose = document.createElement('span'); 6055 winClose.className = 'closeBtn'; 6056 winClose.textContent = 'x'; 6057 winHeader.appendChild(winClose); 6058 this.notificationWin.appendChild(winHeader); 6059 6060 var winBody = document.createElement('div'); 6061 winBody.className = 'notifications clearfix'; 6062 var notifList = document.createElement('div'); 6063 notifList.setAttribute('id', 'geNotifList'); 6064 notifList.style.position = 'relative'; 6065 winBody.appendChild(notifList); 6066 this.notificationWin.appendChild(winBody); 6067 6068 mxEvent.addListener(this.notificationBtn, 'click', mxUtils.bind(this, function() 6069 { 6070 if (this.notificationWin.style.display == 'none') 6071 { 6072 this.notificationWin.style.display = ''; 6073 document.querySelector('.notifications').scrollTop = 0; 6074 var r = this.notificationBtn.getBoundingClientRect(); 6075 this.notificationWin.style.top = (r.top + this.notificationBtn.clientHeight) + 'px'; 6076 this.notificationWin.style.left = (r.right - this.notificationWin.clientWidth) + 'px'; 6077 shouldAnimate(0); //Stop animation once notifications are open 6078 } 6079 else 6080 { 6081 markAllAsRead(); 6082 } 6083 })); 6084 6085 mxEvent.addListener(winClose, 'click', markAllAsRead); 6086 } 6087 else 6088 { 6089 this.notificationBtn.style.display = ''; //In case it was hidden 6090 } 6091 6092 var newNotif = 0; 6093 var notifListEl = document.getElementById('geNotifList'); 6094 6095 if (notifListEl == null) 6096 { 6097 return; //This shouldn't happen and no meaning of continuing 6098 } 6099 else 6100 { 6101 notifListEl.innerHTML = '<div class="line"></div>'; 6102 6103 for (var i = 0; i < notifs.length; i++) 6104 { 6105 (function(editorUi, notif) 6106 { 6107 if (notif.isNew) 6108 { 6109 newNotif++; 6110 } 6111 6112 var notifEl = document.createElement('div'); 6113 notifEl.className = 'notification'; 6114 var ts = new Date(notif.timestamp); 6115 var str = editorUi.timeSince(ts); 6116 6117 if (str == null) 6118 { 6119 str = mxResources.get('lessThanAMinute'); 6120 } 6121 6122 notifEl.innerHTML = '<div class="circle' + (notif.isNew? ' active' : '') + '"></div><span class="time">' + 6123 mxUtils.htmlEntities(mxResources.get('timeAgo', [str], '{1} ago')) + '</span>' + 6124 '<p>' + mxUtils.htmlEntities(notif.content) + '</p>'; 6125 if (notif.link) 6126 { 6127 mxEvent.addListener(notifEl, 'click', function() 6128 { 6129 window.open(notif.link, 'notifWin'); 6130 }); 6131 } 6132 6133 notifListEl.appendChild(notifEl); 6134 })(this, notifs[i]); 6135 } 6136 } 6137 6138 shouldAnimate(newNotif); 6139}; 6140 6141/** 6142 * Translates this point by the given vector. 6143 * 6144 * @param {number} dx X-coordinate of the translation. 6145 * @param {number} dy Y-coordinate of the translation. 6146 */ 6147App.prototype.save = function(name, done) 6148{ 6149 var file = this.getCurrentFile(); 6150 6151 if (file != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6152 { 6153 this.editor.setStatus(''); 6154 6155 if (this.editor.graph.isEditing()) 6156 { 6157 this.editor.graph.stopEditing(); 6158 } 6159 6160 var success = mxUtils.bind(this, function() 6161 { 6162 file.handleFileSuccess(true); 6163 6164 if (done != null) 6165 { 6166 done(); 6167 } 6168 }); 6169 6170 var error = mxUtils.bind(this, function(err) 6171 { 6172 if (file.isModified()) 6173 { 6174 Editor.addRetryToError(err, mxUtils.bind(this, function() 6175 { 6176 this.save(name, done); 6177 })); 6178 } 6179 6180 file.handleFileError(err, true); 6181 }); 6182 6183 try 6184 { 6185 if (name == file.getTitle()) 6186 { 6187 file.save(true, success, error); 6188 } 6189 else 6190 { 6191 file.saveAs(name, success, error) 6192 } 6193 } 6194 catch (err) 6195 { 6196 error(err); 6197 } 6198 } 6199}; 6200 6201/** 6202 * Invokes callback with null if mode does not support folder or not null 6203 * if a valid folder was chosen for a mode that supports it. No callback 6204 * is made if no folder was chosen for a mode that supports it. 6205 */ 6206App.prototype.pickFolder = function(mode, fn, enabled, direct, force) 6207{ 6208 enabled = (enabled != null) ? enabled : true; 6209 var resume = this.spinner.pause(); 6210 6211 if (enabled && mode == App.MODE_GOOGLE && this.drive != null) 6212 { 6213 // Shows a save dialog 6214 this.drive.pickFolder(mxUtils.bind(this, function(evt) 6215 { 6216 resume(); 6217 6218 if (evt.action == google.picker.Action.PICKED) 6219 { 6220 var folderId = null; 6221 6222 if (evt.docs != null && evt.docs.length > 0 && evt.docs[0].type == 'folder') 6223 { 6224 folderId = evt.docs[0].id; 6225 } 6226 6227 fn(folderId); 6228 } 6229 }), force); 6230 } 6231 else if (enabled && mode == App.MODE_ONEDRIVE && this.oneDrive != null) 6232 { 6233 this.oneDrive.pickFolder(mxUtils.bind(this, function(files) 6234 { 6235 var folderId = null; 6236 resume(); 6237 6238 if (files != null && files.value != null && files.value.length > 0) 6239 { 6240 folderId = OneDriveFile.prototype.getIdOf(files.value[0]); 6241 fn(folderId); 6242 } 6243 }), direct); 6244 } 6245 else if (enabled && mode == App.MODE_GITHUB && this.gitHub != null) 6246 { 6247 this.gitHub.pickFolder(mxUtils.bind(this, function(folderPath) 6248 { 6249 resume(); 6250 fn(folderPath); 6251 })); 6252 } 6253 else if (enabled && mode == App.MODE_GITLAB && this.gitLab != null) 6254 { 6255 this.gitLab.pickFolder(mxUtils.bind(this, function(folderPath) 6256 { 6257 resume(); 6258 fn(folderPath); 6259 })); 6260 } 6261 else if (enabled && mode == App.MODE_NOTION && this.notion != null) 6262 { 6263 this.notion.pickFolder(mxUtils.bind(this, function(folderPath) 6264 { 6265 resume(); 6266 fn(folderPath); 6267 })); 6268 } 6269 else if (enabled && mode == App.MODE_TRELLO && this.trello != null) 6270 { 6271 this.trello.pickFolder(mxUtils.bind(this, function(cardId) 6272 { 6273 resume(); 6274 fn(cardId); 6275 })); 6276 } 6277 else 6278 { 6279 EditorUi.prototype.pickFolder.apply(this, arguments); 6280 } 6281}; 6282 6283/** 6284 * 6285 */ 6286App.prototype.exportFile = function(data, filename, mimeType, base64Encoded, mode, folderId) 6287{ 6288 if (mode == App.MODE_DROPBOX) 6289 { 6290 if (this.dropbox != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6291 { 6292 // LATER: Add folder picker 6293 this.dropbox.insertFile(filename, (base64Encoded) ? this.base64ToBlob(data, mimeType) : 6294 data, mxUtils.bind(this, function() 6295 { 6296 this.spinner.stop(); 6297 }), mxUtils.bind(this, function(resp) 6298 { 6299 this.spinner.stop(); 6300 this.handleError(resp); 6301 })); 6302 } 6303 } 6304 else if (mode == App.MODE_GOOGLE) 6305 { 6306 if (this.drive != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6307 { 6308 this.drive.insertFile(filename, data, folderId, mxUtils.bind(this, function(resp) 6309 { 6310 // TODO: Add callback with url param for clickable status message 6311 // "File exported. Click here to open folder." 6312// this.editor.setStatus('<div class="geStatusMessage">' + 6313// mxResources.get('saved') + '</div>'); 6314// 6315// // Installs click handler for opening 6316// if (this.statusContainer != null) 6317// { 6318// var links = this.statusContainer.getElementsByTagName('div'); 6319// 6320// if (links.length > 0) 6321// { 6322// links[0].style.cursor = 'pointer'; 6323// 6324// mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function() 6325// { 6326// if (resp != null && resp.id != null) 6327// { 6328// window.open('https://drive.google.com/open?id=' + resp.id); 6329// } 6330// })); 6331// } 6332// } 6333 6334 this.spinner.stop(); 6335 }), mxUtils.bind(this, function(resp) 6336 { 6337 this.spinner.stop(); 6338 this.handleError(resp); 6339 }), mimeType, base64Encoded); 6340 } 6341 } 6342 else if (mode == App.MODE_ONEDRIVE) 6343 { 6344 if (this.oneDrive != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6345 { 6346 // KNOWN: OneDrive does not show .svg extension 6347 this.oneDrive.insertFile(filename, (base64Encoded) ? this.base64ToBlob(data, mimeType) : 6348 data, mxUtils.bind(this, function() 6349 { 6350 this.spinner.stop(); 6351 }), mxUtils.bind(this, function(resp) 6352 { 6353 this.spinner.stop(); 6354 this.handleError(resp); 6355 }), false, folderId); 6356 } 6357 } 6358 else if (mode == App.MODE_GITHUB) 6359 { 6360 if (this.gitHub != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6361 { 6362 // Must insert file as library to force the file to be written 6363 this.gitHub.insertFile(filename, data, mxUtils.bind(this, function() 6364 { 6365 this.spinner.stop(); 6366 }), mxUtils.bind(this, function(resp) 6367 { 6368 this.spinner.stop(); 6369 this.handleError(resp); 6370 }), true, folderId, base64Encoded); 6371 } 6372 } 6373 else if (mode == App.MODE_GITLAB) 6374 { 6375 if (this.gitHub != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6376 { 6377 // Must insert file as library to force the file to be written 6378 this.gitLab.insertFile(filename, data, mxUtils.bind(this, function() 6379 { 6380 this.spinner.stop(); 6381 }), mxUtils.bind(this, function(resp) 6382 { 6383 this.spinner.stop(); 6384 this.handleError(resp); 6385 }), true, folderId, base64Encoded); 6386 } 6387 } 6388 else if (mode == App.MODE_TRELLO) 6389 { 6390 if (this.trello != null && this.spinner.spin(document.body, mxResources.get('saving'))) 6391 { 6392 this.trello.insertFile(filename, (base64Encoded) ? this.base64ToBlob(data, mimeType) : 6393 data, mxUtils.bind(this, function() 6394 { 6395 this.spinner.stop(); 6396 }), mxUtils.bind(this, function(resp) 6397 { 6398 this.spinner.stop(); 6399 this.handleError(resp); 6400 }), false, folderId); 6401 } 6402 } 6403 else if (mode == App.MODE_BROWSER) 6404 { 6405 var fn = mxUtils.bind(this, function() 6406 { 6407 localStorage.setItem(filename, data); 6408 }); 6409 6410 if (localStorage.getItem(filename) == null) 6411 { 6412 fn(); 6413 } 6414 else 6415 { 6416 this.confirm(mxResources.get('replaceIt', [filename]), fn); 6417 } 6418 } 6419}; 6420 6421/** 6422 * Translates this point by the given vector. 6423 * 6424 * @param {number} dx X-coordinate of the translation. 6425 * @param {number} dy Y-coordinate of the translation. 6426 */ 6427App.prototype.descriptorChanged = function() 6428{ 6429 var file = this.getCurrentFile(); 6430 6431 if (file != null) 6432 { 6433 if (this.fname != null) 6434 { 6435 this.fnameWrapper.style.display = 'block'; 6436 this.fname.innerHTML = ''; 6437 var filename = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename; 6438 mxUtils.write(this.fname, filename); 6439 this.fname.setAttribute('title', filename + ' - ' + mxResources.get('rename')); 6440 } 6441 6442 var graph = this.editor.graph; 6443 var editable = file.isEditable() && !file.invalidChecksum; 6444 6445 if (graph.isEnabled() && !editable) 6446 { 6447 graph.reset(); 6448 } 6449 6450 graph.setEnabled(editable); 6451 6452 // Ignores title and hash for revisions 6453 if (urlParams['rev'] == null) 6454 { 6455 this.updateDocumentTitle(); 6456 var newHash = file.getHash(); 6457 6458 if (newHash.length > 0) 6459 { 6460 window.location.hash = newHash; 6461 } 6462 else if (window.location.hash.length > 0) 6463 { 6464 window.location.hash = ''; 6465 } 6466 } 6467 } 6468 6469 this.updateUi(); 6470 6471 // Refresh if editable state has changed 6472 if (this.format != null && (file == null || 6473 this.fileEditable != file.isEditable()) && 6474 this.editor.graph.isSelectionEmpty()) 6475 { 6476 this.format.refresh(); 6477 this.fileEditable = (file != null) ? file.isEditable() : null; 6478 } 6479 6480 this.fireEvent(new mxEventObject('fileDescriptorChanged', 'file', file)); 6481}; 6482 6483/** 6484 * Adds the listener for automatically saving the diagram for local changes. 6485 */ 6486App.prototype.showAuthDialog = function(peer, showRememberOption, fn, closeFn) 6487{ 6488 var resume = this.spinner.pause(); 6489 6490 this.showDialog(new AuthDialog(this, peer, showRememberOption, mxUtils.bind(this, function(remember) 6491 { 6492 try 6493 { 6494 if (fn != null) 6495 { 6496 fn(remember, mxUtils.bind(this, function() 6497 { 6498 this.hideDialog(); 6499 resume(); 6500 })); 6501 } 6502 } 6503 catch (e) 6504 { 6505 this.editor.setStatus(mxUtils.htmlEntities(e.message)); 6506 } 6507 })).container, 300, (showRememberOption) ? 180 : 140, true, true, mxUtils.bind(this, function(cancel) 6508 { 6509 if (closeFn != null) 6510 { 6511 closeFn(cancel); 6512 } 6513 6514 if (cancel && this.getCurrentFile() == null && this.dialog == null) 6515 { 6516 this.showSplash(); 6517 } 6518 })); 6519}; 6520 6521/** 6522 * Checks if the client is authorized and calls the next step. The optional 6523 * readXml argument is used for import. Default is false. The optional 6524 * readLibrary argument is used for reading libraries. Default is false. 6525 */ 6526App.prototype.convertFile = function(url, filename, mimeType, extension, success, error, executeRequest, headers) 6527{ 6528 var name = filename; 6529 6530 // SVG file extensions are valid and needed for image import 6531 if (!/\.svg$/i.test(name)) 6532 { 6533 name = name.substring(0, filename.lastIndexOf('.')) + extension; 6534 } 6535 6536 var gitHubUrl = false; 6537 6538 if (this.gitHub != null && url.substring(0, this.gitHub.baseUrl.length) == this.gitHub.baseUrl) 6539 { 6540 gitHubUrl = true; 6541 } 6542 6543 // Workaround for wrong binary response with VSD(X) & VDX files 6544 if (/\.v(dx|sdx?)$/i.test(filename) && Graph.fileSupport && new XMLHttpRequest().upload && 6545 typeof new XMLHttpRequest().responseType === 'string') 6546 { 6547 var req = new XMLHttpRequest(); 6548 req.open('GET', url, true); 6549 6550 if (!gitHubUrl) 6551 { 6552 req.responseType = 'blob'; 6553 } 6554 6555 if (headers) 6556 { 6557 for (var key in headers) 6558 { 6559 req.setRequestHeader(key, headers[key]); 6560 } 6561 } 6562 6563 req.onload = mxUtils.bind(this, function() 6564 { 6565 if (req.status >= 200 && req.status <= 299) 6566 { 6567 var blob = null; 6568 6569 if (gitHubUrl) 6570 { 6571 var file = JSON.parse(req.responseText); 6572 blob = this.base64ToBlob(file.content, 'application/octet-stream'); 6573 } 6574 else 6575 { 6576 blob = new Blob([req.response], {type: 'application/octet-stream'}); 6577 } 6578 6579 this.importVisio(blob, mxUtils.bind(this, function(xml) 6580 { 6581 success(new LocalFile(this, xml, name, true)); 6582 }), error, filename) 6583 } 6584 else if (error != null) 6585 { 6586 error({message: mxResources.get('errorLoadingFile')}); 6587 } 6588 }); 6589 6590 req.onerror = error; 6591 req.send(); 6592 } 6593 else 6594 { 6595 var handleData = mxUtils.bind(this, function(data) 6596 { 6597 try 6598 { 6599 if (/\.pdf$/i.test(filename)) 6600 { 6601 var temp = Editor.extractGraphModelFromPdf(data); 6602 6603 if (temp != null && temp.length > 0) 6604 { 6605 success(new LocalFile(this, temp, name, true)); 6606 } 6607 } 6608 else if (/\.png$/i.test(filename)) 6609 { 6610 var temp = this.extractGraphModelFromPng(data); 6611 6612 if (temp != null) 6613 { 6614 success(new LocalFile(this, temp, name, true)); 6615 } 6616 else 6617 { 6618 success(new LocalFile(this, data, filename, true)); 6619 } 6620 } 6621 else if (Graph.fileSupport && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, url)) 6622 { 6623 this.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr) 6624 { 6625 if (xhr.readyState == 4) 6626 { 6627 if (xhr.status >= 200 && xhr.status <= 299) 6628 { 6629 success(new LocalFile(this, xhr.responseText, name, true)); 6630 } 6631 else if (error != null) 6632 { 6633 error({message: mxResources.get('errorLoadingFile')}); 6634 } 6635 } 6636 }), filename); 6637 } 6638 else 6639 { 6640 success(new LocalFile(this, data, name, true)); 6641 } 6642 } 6643 catch (e) 6644 { 6645 if (error != null) 6646 { 6647 error(e); 6648 } 6649 } 6650 }); 6651 6652 var binary = /\.png$/i.test(filename) || /\.jpe?g$/i.test(filename) || 6653 /\.pdf$/i.test(filename) || (mimeType != null && 6654 mimeType.substring(0, 6) == 'image/'); 6655 6656 // NOTE: Cannot force non-binary request via loadUrl so needs separate 6657 // code as decoding twice on content with binary data did not work 6658 if (gitHubUrl) 6659 { 6660 mxUtils.get(url, mxUtils.bind(this, function(req) 6661 { 6662 if (req.getStatus() >= 200 && req.getStatus() <= 299) 6663 { 6664 if (success != null) 6665 { 6666 var file = JSON.parse(req.getText()); 6667 var data = file.content; 6668 6669 if (file.encoding === 'base64') 6670 { 6671 if (/\.png$/i.test(filename)) 6672 { 6673 data = 'data:image/png;base64,' + data; 6674 } 6675 else if (/\.pdf$/i.test(filename)) 6676 { 6677 data = 'data:application/pdf;base64,' + data; 6678 } 6679 else 6680 { 6681 // Workaround for character encoding issues in IE10/11 6682 data = (window.atob && !mxClient.IS_IE && !mxClient.IS_IE11) ? atob(data) : Base64.decode(data); 6683 } 6684 } 6685 6686 handleData(data); 6687 } 6688 } 6689 else if (error != null) 6690 { 6691 error({code: App.ERROR_UNKNOWN}); 6692 } 6693 }), function() 6694 { 6695 if (error != null) 6696 { 6697 error({code: App.ERROR_UNKNOWN}); 6698 } 6699 }, false, this.timeout, function() 6700 { 6701 if (error != null) 6702 { 6703 error({code: App.ERROR_TIMEOUT, retry: fn}); 6704 } 6705 }, headers); 6706 } 6707 else if (executeRequest != null) 6708 { 6709 executeRequest(url, handleData, error, binary); 6710 } 6711 else 6712 { 6713 this.editor.loadUrl(url, handleData, error, binary, null, null, null, headers); 6714 } 6715 } 6716}; 6717 6718/** 6719 * Adds the listener for automatically saving the diagram for local changes. 6720 */ 6721App.prototype.updateHeader = function() 6722{ 6723 if (this.menubar != null) 6724 { 6725 this.appIcon = document.createElement('a'); 6726 this.appIcon.style.display = 'block'; 6727 this.appIcon.style.position = 'absolute'; 6728 this.appIcon.style.width = '32px'; 6729 this.appIcon.style.height = (this.menubarHeight - 28) + 'px'; 6730 this.appIcon.style.margin = '14px 0px 8px 16px'; 6731 this.appIcon.style.opacity = '0.85'; 6732 this.appIcon.style.borderRadius = '3px'; 6733 6734 if (uiTheme != 'dark') 6735 { 6736 this.appIcon.style.backgroundColor = '#f08705'; 6737 } 6738 6739 mxEvent.disableContextMenu(this.appIcon); 6740 6741 mxEvent.addListener(this.appIcon, 'click', mxUtils.bind(this, function(evt) 6742 { 6743 this.appIconClicked(evt); 6744 })); 6745 6746 // LATER: Use Alpha image loader in IE6 6747 // NOTE: This uses the diagram bit of the old logo as it looks better in this case 6748 //this.appIcon.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=' + IMAGE_PATH + '/logo-white.png,sizingMethod=\'scale\')'; 6749 var logo = (!mxClient.IS_SVG) ? 'url(\'' + IMAGE_PATH + '/logo-white.png\')' : 6750 ((uiTheme == 'dark') ? 'url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzA2LjE4NSAxMjAuMjk2IgogICB2aWV3Qm94PSIyNCAyNiA2OCA2OCIKICAgeT0iMHB4IgogICB4PSIwcHgiCiAgIHZlcnNpb249IjEuMSI+CiAgIAkgPGc+PGxpbmUKICAgICAgIHkyPSI3Mi4zOTQiCiAgICAgICB4Mj0iNDEuMDYxIgogICAgICAgeTE9IjQzLjM4NCIKICAgICAgIHgxPSI1OC4wNjkiCiAgICAgICBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNTUyOCIKICAgICAgIHN0cm9rZT0iI0ZGRkZGRiIKICAgICAgIGZpbGw9Im5vbmUiIC8+PGxpbmUKICAgICAgIHkyPSI3Mi4zOTQiCiAgICAgICB4Mj0iNzUuMDc2IgogICAgICAgeTE9IjQzLjM4NCIKICAgICAgIHgxPSI1OC4wNjgiCiAgICAgICBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNTAwOCIKICAgICAgIHN0cm9rZT0iI0ZGRkZGRiIKICAgICAgIGZpbGw9Im5vbmUiIC8+PGc+PHBhdGgKICAgICAgICAgZD0iTTUyLjc3Myw3Ny4wODRjMCwxLjk1NC0xLjU5OSwzLjU1My0zLjU1MywzLjU1M0gzNi45OTljLTEuOTU0LDAtMy41NTMtMS41OTktMy41NTMtMy41NTN2LTkuMzc5ICAgIGMwLTEuOTU0LDEuNTk5LTMuNTUzLDMuNTUzLTMuNTUzaDEyLjIyMmMxLjk1NCwwLDMuNTUzLDEuNTk5LDMuNTUzLDMuNTUzVjc3LjA4NHoiCiAgICAgICAgIGZpbGw9IiNGRkZGRkYiIC8+PC9nPjxnCiAgICAgICBpZD0iZzM0MTkiPjxwYXRoCiAgICAgICAgIGQ9Ik02Ny43NjIsNDguMDc0YzAsMS45NTQtMS41OTksMy41NTMtMy41NTMsMy41NTNINTEuOTg4Yy0xLjk1NCwwLTMuNTUzLTEuNTk5LTMuNTUzLTMuNTUzdi05LjM3OSAgICBjMC0xLjk1NCwxLjU5OS0zLjU1MywzLjU1My0zLjU1M0g2NC4yMWMxLjk1NCwwLDMuNTUzLDEuNTk5LDMuNTUzLDMuNTUzVjQ4LjA3NHoiCiAgICAgICAgIGZpbGw9IiNGRkZGRkYiIC8+PC9nPjxnPjxwYXRoCiAgICAgICAgIGQ9Ik04Mi43NTIsNzcuMDg0YzAsMS45NTQtMS41OTksMy41NTMtMy41NTMsMy41NTNINjYuOTc3Yy0xLjk1NCwwLTMuNTUzLTEuNTk5LTMuNTUzLTMuNTUzdi05LjM3OSAgICBjMC0xLjk1NCwxLjU5OS0zLjU1MywzLjU1My0zLjU1M2gxMi4yMjJjMS45NTQsMCwzLjU1MywxLjU5OSwzLjU1MywzLjU1M1Y3Ny4wODR6IgogICAgICAgICBmaWxsPSIjRkZGRkZGIiAvPjwvZz48L2c+PC9zdmc+)' : 6751 'url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJFYmVuZV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgMjI1IDIyNSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjI1IDIyNTsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MXtmaWxsOiNERjZDMEM7fQoJLnN0MntmaWxsOiNGRkZGRkY7fQo8L3N0eWxlPgo8cGF0aCBjbGFzcz0ic3QxIiBkPSJNMjI1LDIxNS40YzAsNS4zLTQuMyw5LjYtOS41LDkuNmwwLDBINzcuMWwtNDQuOC00NS41TDYwLjIsMTM0bDgyLjctMTAyLjdsODIuMSw4NC41VjIxNS40eiIvPgo8cGF0aCBjbGFzcz0ic3QyIiBkPSJNMTg0LjYsMTI1LjhoLTIzLjdsLTI1LTQyLjdjNS43LTEuMiw5LjgtNi4yLDkuNy0xMlYzOWMwLTYuOC01LjQtMTIuMy0xMi4yLTEyLjNoLTAuMUg5MS42CgljLTYuOCwwLTEyLjMsNS40LTEyLjMsMTIuMlYzOXYzMi4xYzAsNS44LDQsMTAuOCw5LjcsMTJsLTI1LDQyLjdINDAuNGMtNi44LDAtMTIuMyw1LjQtMTIuMywxMi4ydjAuMXYzMi4xCgljMCw2LjgsNS40LDEyLjMsMTIuMiwxMi4zaDAuMWg0MS43YzYuOCwwLDEyLjMtNS40LDEyLjMtMTIuMnYtMC4xdi0zMi4xYzAtNi44LTUuNC0xMi4zLTEyLjItMTIuM2gtMC4xaC00bDI0LjgtNDIuNGgxOS4zCglsMjQuOSw0Mi40SDE0M2MtNi44LDAtMTIuMyw1LjQtMTIuMywxMi4ydjAuMXYzMi4xYzAsNi44LDUuNCwxMi4zLDEyLjIsMTIuM2gwLjFoNDEuN2M2LjgsMCwxMi4zLTUuNCwxMi4zLTEyLjJ2LTAuMXYtMzIuMQoJYzAtNi44LTUuNC0xMi4zLTEyLjItMTIuM0MxODQuNywxMjUuOCwxODQuNywxMjUuOCwxODQuNiwxMjUuOHoiLz4KPC9zdmc+Cg==)'); 6752 this.appIcon.style.backgroundImage = logo; 6753 this.appIcon.style.backgroundPosition = 'center center'; 6754 this.appIcon.style.backgroundSize = '100% 100%'; 6755 this.appIcon.style.backgroundRepeat = 'no-repeat'; 6756 6757 mxUtils.setPrefixedStyle(this.appIcon.style, 'transition', 'all 125ms linear'); 6758 6759 mxEvent.addListener(this.appIcon, 'mouseover', mxUtils.bind(this, function() 6760 { 6761 var file = this.getCurrentFile(); 6762 6763 if (file != null) 6764 { 6765 var mode = file.getMode(); 6766 6767 if (mode == App.MODE_GOOGLE) 6768 { 6769 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/google-drive-logo-white.svg)'; 6770 this.appIcon.style.backgroundSize = '70% 70%'; 6771 } 6772 else if (mode == App.MODE_DROPBOX) 6773 { 6774 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/dropbox-logo-white.svg)'; 6775 this.appIcon.style.backgroundSize = '70% 70%'; 6776 } 6777 else if (mode == App.MODE_ONEDRIVE) 6778 { 6779 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/onedrive-logo-white.svg)'; 6780 this.appIcon.style.backgroundSize = '70% 70%'; 6781 } 6782 else if (mode == App.MODE_GITHUB) 6783 { 6784 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/github-logo-white.svg)'; 6785 this.appIcon.style.backgroundSize = '70% 70%'; 6786 } 6787 else if (mode == App.MODE_GITLAB) 6788 { 6789 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/gitlab-logo-white.svg)'; 6790 this.appIcon.style.backgroundSize = '100% 100%'; 6791 } 6792 else if (mode == App.MODE_NOTION) 6793 { 6794 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/notion-logo-white.svg)'; 6795 this.appIcon.style.backgroundSize = '70% 70%'; 6796 } 6797 else if (mode == App.MODE_TRELLO) 6798 { 6799 this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/trello-logo-white-orange.svg)'; 6800 this.appIcon.style.backgroundSize = '70% 70%'; 6801 } 6802 } 6803 })); 6804 6805 mxEvent.addListener(this.appIcon, 'mouseout', mxUtils.bind(this, function() 6806 { 6807 this.appIcon.style.backgroundImage = logo; 6808 this.appIcon.style.backgroundSize = '90% 90%'; 6809 })); 6810 6811 if (urlParams['embed'] != '1') 6812 { 6813 this.menubarContainer.appendChild(this.appIcon); 6814 } 6815 6816 this.fnameWrapper = document.createElement('div'); 6817 this.fnameWrapper.style.position = 'absolute'; 6818 this.fnameWrapper.style.right = '120px'; 6819 this.fnameWrapper.style.left = '60px'; 6820 this.fnameWrapper.style.top = '9px'; 6821 this.fnameWrapper.style.height = '26px'; 6822 this.fnameWrapper.style.display = 'none'; 6823 this.fnameWrapper.style.overflow = 'hidden'; 6824 this.fnameWrapper.style.textOverflow = 'ellipsis'; 6825 6826 this.fname = document.createElement('a'); 6827 this.fname.setAttribute('title', mxResources.get('rename')); 6828 this.fname.className = 'geItem'; 6829 this.fname.style.padding = '2px 8px 2px 8px'; 6830 this.fname.style.display = 'inline'; 6831 this.fname.style.fontSize = '18px'; 6832 this.fname.style.whiteSpace = 'nowrap'; 6833 6834 // Prevents focus 6835 mxEvent.addListener(this.fname, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', 6836 mxUtils.bind(this, function(evt) 6837 { 6838 evt.preventDefault(); 6839 })); 6840 6841 mxEvent.addListener(this.fname, 'click', mxUtils.bind(this, function(evt) 6842 { 6843 var file = this.getCurrentFile(); 6844 6845 if (file != null && file.isRenamable()) 6846 { 6847 if (this.editor.graph.isEditing()) 6848 { 6849 this.editor.graph.stopEditing(); 6850 } 6851 6852 this.actions.get('rename').funct(); 6853 } 6854 6855 mxEvent.consume(evt); 6856 })); 6857 6858 this.fnameWrapper.appendChild(this.fname); 6859 6860 if (urlParams['embed'] != '1') 6861 { 6862 this.menubarContainer.appendChild(this.fnameWrapper); 6863 6864 this.menubar.container.style.position = 'absolute'; 6865 this.menubar.container.style.paddingLeft = '59px'; 6866 this.toolbar.container.style.paddingLeft = '16px'; 6867 this.menubar.container.style.boxSizing = 'border-box'; 6868 this.menubar.container.style.top = '34px'; 6869 } 6870 6871 /** 6872 * Adds format panel toggle. 6873 */ 6874 this.toggleFormatElement = document.createElement('a'); 6875 this.toggleFormatElement.setAttribute('title', mxResources.get('formatPanel') + ' (' + Editor.ctrlKey + '+Shift+P)'); 6876 this.toggleFormatElement.style.position = 'absolute'; 6877 this.toggleFormatElement.style.display = 'inline-block'; 6878 this.toggleFormatElement.style.top = (uiTheme == 'atlas') ? '8px' : '6px'; 6879 this.toggleFormatElement.style.right = (uiTheme != 'atlas' && urlParams['embed'] != '1') ? '30px' : '10px'; 6880 this.toggleFormatElement.style.padding = '2px'; 6881 this.toggleFormatElement.style.fontSize = '14px'; 6882 this.toggleFormatElement.className = (uiTheme != 'atlas') ? 'geButton' : ''; 6883 this.toggleFormatElement.style.width = '16px'; 6884 this.toggleFormatElement.style.height = '16px'; 6885 this.toggleFormatElement.style.backgroundPosition = '50% 50%'; 6886 this.toggleFormatElement.style.backgroundRepeat = 'no-repeat'; 6887 this.toolbarContainer.appendChild(this.toggleFormatElement); 6888 6889 if (uiTheme == 'dark') 6890 { 6891 this.toggleFormatElement.style.filter = 'invert(100%)'; 6892 } 6893 6894 // Prevents focus 6895 mxEvent.addListener(this.toggleFormatElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', 6896 mxUtils.bind(this, function(evt) 6897 { 6898 evt.preventDefault(); 6899 })); 6900 6901 mxEvent.addListener(this.toggleFormatElement, 'click', mxUtils.bind(this, function(evt) 6902 { 6903 this.actions.get('formatPanel').funct(); 6904 mxEvent.consume(evt); 6905 })); 6906 6907 var toggleFormatPanel = mxUtils.bind(this, function() 6908 { 6909 if (this.formatWidth > 0) 6910 { 6911 this.toggleFormatElement.style.backgroundImage = 'url(\'' + this.formatShowImage + '\')'; 6912 } 6913 else 6914 { 6915 this.toggleFormatElement.style.backgroundImage = 'url(\'' + this.formatHideImage + '\')'; 6916 } 6917 }); 6918 6919 this.addListener('formatWidthChanged', toggleFormatPanel); 6920 toggleFormatPanel(); 6921 6922 this.fullscreenElement = document.createElement('a'); 6923 this.fullscreenElement.setAttribute('title', mxResources.get('fullscreen')); 6924 this.fullscreenElement.style.position = 'absolute'; 6925 this.fullscreenElement.style.display = 'inline-block'; 6926 this.fullscreenElement.style.top = (uiTheme == 'atlas') ? '8px' : '6px'; 6927 this.fullscreenElement.style.right = (uiTheme != 'atlas' && urlParams['embed'] != '1') ? '50px' : '30px'; 6928 this.fullscreenElement.style.padding = '2px'; 6929 this.fullscreenElement.style.fontSize = '14px'; 6930 this.fullscreenElement.className = (uiTheme != 'atlas') ? 'geButton' : ''; 6931 this.fullscreenElement.style.width = '16px'; 6932 this.fullscreenElement.style.height = '16px'; 6933 this.fullscreenElement.style.backgroundPosition = '50% 50%'; 6934 this.fullscreenElement.style.backgroundRepeat = 'no-repeat'; 6935 this.fullscreenElement.style.backgroundImage = 'url(\'' + this.fullscreenImage + '\')'; 6936 this.toolbarContainer.appendChild(this.fullscreenElement); 6937 6938 // Prevents focus 6939 mxEvent.addListener(this.fullscreenElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', 6940 mxUtils.bind(this, function(evt) 6941 { 6942 evt.preventDefault(); 6943 })); 6944 6945 // Some style changes in Atlas theme 6946 if (uiTheme == 'atlas') 6947 { 6948 mxUtils.setOpacity(this.toggleFormatElement, 70); 6949 mxUtils.setOpacity(this.fullscreenElement, 70); 6950 } 6951 6952 var initialPosition = this.hsplitPosition; 6953 6954 if (uiTheme == 'dark') 6955 { 6956 this.fullscreenElement.style.filter = 'invert(100%)'; 6957 } 6958 6959 mxEvent.addListener(this.fullscreenElement, 'click', mxUtils.bind(this, function(evt) 6960 { 6961 var visible = this.fullscreenMode; 6962 6963 if (uiTheme != 'atlas' && urlParams['embed'] != '1') 6964 { 6965 this.toggleCompactMode(visible); 6966 } 6967 6968 if (!visible) 6969 { 6970 initialPosition = this.hsplitPosition; 6971 } 6972 6973 this.hsplitPosition = (visible) ? initialPosition : 0; 6974 this.toggleFormatPanel(visible); 6975 this.fullscreenMode = !visible; 6976 mxEvent.consume(evt); 6977 })); 6978 6979 /** 6980 * Adds compact UI toggle. 6981 */ 6982 if (urlParams['embed'] != '1') 6983 { 6984 this.toggleElement = document.createElement('a'); 6985 this.toggleElement.setAttribute('title', mxResources.get('collapseExpand')); 6986 this.toggleElement.className = 'geButton'; 6987 this.toggleElement.style.position = 'absolute'; 6988 this.toggleElement.style.display = 'inline-block'; 6989 this.toggleElement.style.width = '16px'; 6990 this.toggleElement.style.height = '16px'; 6991 this.toggleElement.style.color = '#666'; 6992 this.toggleElement.style.top = (uiTheme == 'atlas') ? '8px' : '6px'; 6993 this.toggleElement.style.right = '10px'; 6994 this.toggleElement.style.padding = '2px'; 6995 this.toggleElement.style.fontSize = '14px'; 6996 this.toggleElement.style.textDecoration = 'none'; 6997 this.toggleElement.style.backgroundImage = 'url(\'' + this.chevronUpImage + '\')'; 6998 6999 this.toggleElement.style.backgroundPosition = '50% 50%'; 7000 this.toggleElement.style.backgroundRepeat = 'no-repeat'; 7001 7002 if (uiTheme == 'dark') 7003 { 7004 this.toggleElement.style.filter = 'invert(100%)'; 7005 } 7006 7007 // Prevents focus 7008 mxEvent.addListener(this.toggleElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', 7009 mxUtils.bind(this, function(evt) 7010 { 7011 evt.preventDefault(); 7012 })); 7013 7014 // Toggles compact mode 7015 mxEvent.addListener(this.toggleElement, 'click', mxUtils.bind(this, function(evt) 7016 { 7017 this.toggleCompactMode(); 7018 mxEvent.consume(evt); 7019 })); 7020 7021 if (uiTheme != 'atlas') 7022 { 7023 this.toolbarContainer.appendChild(this.toggleElement); 7024 } 7025 7026 // Enable compact mode for small screens except for Firefox where the height is wrong 7027 if (!mxClient.IS_FF && screen.height <= 740 && typeof this.toggleElement.click !== 'undefined') 7028 { 7029 window.setTimeout(mxUtils.bind(this, function() 7030 { 7031 this.toggleElement.click(); 7032 }), 0); 7033 } 7034 } 7035 } 7036}; 7037 7038/** 7039 * Adds the listener for automatically saving the diagram for local changes. 7040 */ 7041App.prototype.toggleCompactMode = function(visible) 7042{ 7043 visible = (visible != null) ? visible : this.compactMode; 7044 7045 if (visible) 7046 { 7047 this.menubar.container.style.position = 'absolute'; 7048 this.menubar.container.style.paddingLeft = '59px'; 7049 this.menubar.container.style.paddingTop = ''; 7050 this.menubar.container.style.paddingBottom = ''; 7051 this.menubar.container.style.top = '34px'; 7052 this.toolbar.container.style.paddingLeft = '16px'; 7053 this.buttonContainer.style.visibility = 'visible'; 7054 this.appIcon.style.display = 'block'; 7055 this.fnameWrapper.style.display = 'block'; 7056 this.fnameWrapper.style.visibility = 'visible'; 7057 this.menubarHeight = App.prototype.menubarHeight; 7058 this.refresh(); 7059 this.toggleElement.style.backgroundImage = 'url(\'' + this.chevronUpImage + '\')'; 7060 } 7061 else 7062 { 7063 this.menubar.container.style.position = 'relative'; 7064 this.menubar.container.style.paddingLeft = '4px'; 7065 this.menubar.container.style.paddingTop = '0px'; 7066 this.menubar.container.style.paddingBottom = '0px'; 7067 this.menubar.container.style.top = '0px'; 7068 this.toolbar.container.style.paddingLeft = '8px'; 7069 this.buttonContainer.style.visibility = 'hidden'; 7070 this.appIcon.style.display = 'none'; 7071 this.fnameWrapper.style.display = 'none'; 7072 this.fnameWrapper.style.visibility = 'hidden'; 7073 this.menubarHeight = EditorUi.prototype.menubarHeight; 7074 this.refresh(); 7075 this.toggleElement.style.backgroundImage = 'url(\'' + this.chevronDownImage + '\')'; 7076 } 7077 7078 this.compactMode = !visible; 7079}; 7080 7081/** 7082 * Adds the listener for automatically saving the diagram for local changes. 7083 */ 7084App.prototype.updateUserElement = function() 7085{ 7086 if ((this.drive == null || this.drive.getUser() == null) && 7087 (this.oneDrive == null || this.oneDrive.getUser() == null) && 7088 (this.dropbox == null || this.dropbox.getUser() == null) && 7089 (this.gitHub == null || this.gitHub.getUser() == null) && 7090 (this.gitLab == null || this.gitLab.getUser() == null) && 7091 (this.notion == null || this.notion.getUser() == null) && 7092 (this.trello == null || !this.trello.isAuthorized())) //TODO Trello no user issue 7093 { 7094 if (this.userElement != null) 7095 { 7096 this.userElement.parentNode.removeChild(this.userElement); 7097 this.userElement = null; 7098 } 7099 } 7100 else 7101 { 7102 if (this.userElement == null) 7103 { 7104 this.userElement = document.createElement('a'); 7105 this.userElement.className = 'geItem'; 7106 this.userElement.style.position = 'absolute'; 7107 this.userElement.style.fontSize = '8pt'; 7108 this.userElement.style.top = (uiTheme == 'atlas') ? '8px' : '2px'; 7109 this.userElement.style.right = '30px'; 7110 this.userElement.style.margin = '4px'; 7111 this.userElement.style.padding = '2px'; 7112 this.userElement.style.paddingRight = '16px'; 7113 this.userElement.style.verticalAlign = 'middle'; 7114 this.userElement.style.backgroundImage = 'url(' + IMAGE_PATH + '/expanded.gif)'; 7115 this.userElement.style.backgroundPosition = '100% 60%'; 7116 this.userElement.style.backgroundRepeat = 'no-repeat'; 7117 7118 this.menubarContainer.appendChild(this.userElement); 7119 7120 // Prevents focus 7121 mxEvent.addListener(this.userElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', 7122 mxUtils.bind(this, function(evt) 7123 { 7124 evt.preventDefault(); 7125 })); 7126 7127 mxEvent.addListener(this.userElement, 'click', mxUtils.bind(this, function(evt) 7128 { 7129 if (this.userPanel == null) 7130 { 7131 var div = document.createElement('div'); 7132 div.className = 'geDialog'; 7133 div.style.position = 'absolute'; 7134 div.style.top = (this.userElement.clientTop + 7135 this.userElement.clientHeight + 6) + 'px'; 7136 div.style.zIndex = 5; 7137 div.style.right = '36px'; 7138 div.style.padding = '0px'; 7139 div.style.cursor = 'default'; 7140 div.style.minWidth = '300px'; 7141 7142 this.userPanel = div; 7143 } 7144 7145 if (this.userPanel.parentNode != null) 7146 { 7147 this.userPanel.parentNode.removeChild(this.userPanel); 7148 } 7149 else 7150 { 7151 var connected = false; 7152 this.userPanel.innerHTML = ''; 7153 7154 var img = document.createElement('img'); 7155 7156 img.setAttribute('src', Dialog.prototype.closeImage); 7157 img.setAttribute('title', mxResources.get('close')); 7158 img.className = 'geDialogClose'; 7159 img.style.top = '8px'; 7160 img.style.right = '8px'; 7161 7162 mxEvent.addListener(img, 'click', mxUtils.bind(this, function() 7163 { 7164 if (this.userPanel.parentNode != null) 7165 { 7166 this.userPanel.parentNode.removeChild(this.userPanel); 7167 } 7168 })); 7169 7170 this.userPanel.appendChild(img); 7171 7172 if (this.drive != null) 7173 { 7174 var driveUsers = this.drive.getUsersList(); 7175 7176 if (driveUsers.length > 0) 7177 { 7178 // LATER: Cannot change user while file is open since close will not work with new 7179 // credentials and closing the file using fileLoaded(null) will show splash dialog. 7180 var closeFile = mxUtils.bind(this, function(callback, spinnerMsg) 7181 { 7182 var file = this.getCurrentFile(); 7183 7184 if (file != null && file.constructor == DriveFile) 7185 { 7186 this.spinner.spin(document.body, spinnerMsg); 7187 7188// file.close(); 7189 this.fileLoaded(null); 7190 7191 // LATER: Use callback to wait for thumbnail update 7192 window.setTimeout(mxUtils.bind(this, function() 7193 { 7194 this.spinner.stop(); 7195 callback(); 7196 }), 2000); 7197 } 7198 else 7199 { 7200 callback(); 7201 } 7202 }); 7203 7204 var createUserRow = mxUtils.bind(this, function (user) 7205 { 7206 var tr = document.createElement('tr'); 7207 tr.setAttribute('title', 'User ID: ' + user.id); 7208 7209 var td = document.createElement('td'); 7210 td.setAttribute('valig', 'middle'); 7211 td.style.height = '59px'; 7212 td.style.width = '66px'; 7213 7214 var img = document.createElement('img'); 7215 img.setAttribute('width', '50'); 7216 img.setAttribute('height', '50'); 7217 img.setAttribute('border', '0'); 7218 img.setAttribute('src', (user.pictureUrl != null) ? user.pictureUrl : this.defaultUserPicture); 7219 img.style.borderRadius = '50%'; 7220 img.style.margin = '4px 8px 0 8px'; 7221 td.appendChild(img); 7222 tr.appendChild(td); 7223 7224 var td = document.createElement('td'); 7225 td.setAttribute('valign', 'middle'); 7226 td.style.whiteSpace = 'nowrap'; 7227 td.style.paddingTop = '4px'; 7228 td.style.maxWidth = '0'; 7229 td.style.overflow = 'hidden'; 7230 td.style.textOverflow = 'ellipsis'; 7231 mxUtils.write(td, user.displayName + 7232 ((user.isCurrent && driveUsers.length > 1) ? 7233 ' (' + mxResources.get('default') + ')' : '')); 7234 7235 if (user.email != null) 7236 { 7237 mxUtils.br(td); 7238 7239 var small = document.createElement('small'); 7240 small.style.color = 'gray'; 7241 mxUtils.write(small, user.email); 7242 td.appendChild(small); 7243 } 7244 7245 var div = document.createElement('div'); 7246 div.style.marginTop = '4px'; 7247 7248 var i = document.createElement('i'); 7249 mxUtils.write(i, mxResources.get('googleDrive')); 7250 div.appendChild(i); 7251 td.appendChild(div); 7252 tr.appendChild(td); 7253 7254 if (!user.isCurrent) 7255 { 7256 tr.style.cursor = 'pointer'; 7257 tr.style.opacity = '0.3'; 7258 7259 mxEvent.addListener(tr, 'click', mxUtils.bind(this, function(evt) 7260 { 7261 closeFile(mxUtils.bind(this, function() 7262 { 7263 this.stateArg = null; 7264 this.drive.setUser(user); 7265 7266 this.drive.authorize(true, mxUtils.bind(this, function() 7267 { 7268 this.setMode(App.MODE_GOOGLE); 7269 this.hideDialog(); 7270 this.showSplash(); 7271 }), mxUtils.bind(this, function(resp) 7272 { 7273 this.handleError(resp); 7274 }), true); //Remember is true since add account imply keeping that account 7275 }), mxResources.get('closingFile') + '...'); 7276 7277 mxEvent.consume(evt); 7278 })); 7279 } 7280 7281 return tr; 7282 }); 7283 7284 connected = true; 7285 7286 var driveUserTable = document.createElement('table'); 7287 driveUserTable.style.borderSpacing = '0'; 7288 driveUserTable.style.fontSize = '10pt'; 7289 driveUserTable.style.width = '100%'; 7290 driveUserTable.style.padding = '10px'; 7291 7292 for (var i = 0; i < driveUsers.length; i++) 7293 { 7294 driveUserTable.appendChild(createUserRow(driveUsers[i])); 7295 } 7296 7297 this.userPanel.appendChild(driveUserTable); 7298 7299 var div = document.createElement('div'); 7300 div.style.textAlign = 'left'; 7301 div.style.padding = '10px'; 7302 div.style.whiteSpace = 'nowrap'; 7303 div.style.borderTop = '1px solid rgb(224, 224, 224)'; 7304 7305 var btn = mxUtils.button(mxResources.get('signOut'), mxUtils.bind(this, function() 7306 { 7307 this.confirm(mxResources.get('areYouSure'), mxUtils.bind(this, function() 7308 { 7309 closeFile(mxUtils.bind(this, function() 7310 { 7311 this.stateArg = null; 7312 this.drive.logout(); 7313 this.setMode(App.MODE_GOOGLE); 7314 this.hideDialog(); 7315 this.showSplash(); 7316 }), mxResources.get('signOut')); 7317 })); 7318 })); 7319 btn.className = 'geBtn'; 7320 btn.style.float = 'right'; 7321 div.appendChild(btn); 7322 7323 var btn = mxUtils.button(mxResources.get('addAccount'), mxUtils.bind(this, function() 7324 { 7325 var authWin = this.drive.createAuthWin(); 7326 //FIXME This doean't work to set focus back to main window until closing the file is done 7327 authWin.blur(); 7328 window.focus(); 7329 7330 closeFile(mxUtils.bind(this, function() 7331 { 7332 this.stateArg = null; 7333 7334 this.drive.authorize(false, mxUtils.bind(this, function() 7335 { 7336 this.setMode(App.MODE_GOOGLE); 7337 this.hideDialog(); 7338 this.showSplash(); 7339 }), mxUtils.bind(this, function(resp) 7340 { 7341 this.handleError(resp); 7342 }), true, authWin); //Remember is true since add account imply keeping that account 7343 }), mxResources.get('closingFile') + '...'); 7344 })); 7345 btn.className = 'geBtn'; 7346 btn.style.margin = '0px'; 7347 div.appendChild(btn); 7348 this.userPanel.appendChild(div); 7349 } 7350 } 7351 7352 var addUser = mxUtils.bind(this, function(user, logo, logout, label) 7353 { 7354 if (user != null) 7355 { 7356 if (connected) 7357 { 7358 this.userPanel.appendChild(document.createElement('hr')); 7359 } 7360 7361 connected = true; 7362 var userTable = document.createElement('table'); 7363 userTable.style.borderSpacing = '0'; 7364 userTable.style.fontSize = '10pt'; 7365 userTable.style.width = '100%'; 7366 userTable.style.padding = '10px'; 7367 7368 var tbody = document.createElement('tbody'); 7369 var row = document.createElement('tr'); 7370 var td = document.createElement('td'); 7371 td.setAttribute('valig', 'top'); 7372 td.style.width = '40px'; 7373 7374 if (logo != null) 7375 { 7376 var img = document.createElement('img'); 7377 img.setAttribute('width', '40'); 7378 img.setAttribute('height', '40'); 7379 img.setAttribute('border', '0'); 7380 img.setAttribute('src', logo); 7381 img.style.marginRight = '6px'; 7382 7383 td.appendChild(img); 7384 } 7385 7386 row.appendChild(td); 7387 7388 var td = document.createElement('td'); 7389 td.setAttribute('valign', 'middle'); 7390 td.style.whiteSpace = 'nowrap'; 7391 td.style.maxWidth = '0'; 7392 td.style.overflow = 'hidden'; 7393 td.style.textOverflow = 'ellipsis'; 7394 7395 mxUtils.write(td, user.displayName); 7396 7397 if (user.email != null) 7398 { 7399 mxUtils.br(td); 7400 7401 var small = document.createElement('small'); 7402 small.style.color = 'gray'; 7403 mxUtils.write(small, user.email); 7404 td.appendChild(small); 7405 } 7406 7407 if (label != null) 7408 { 7409 var div = document.createElement('div'); 7410 div.style.marginTop = '4px'; 7411 7412 var i = document.createElement('i'); 7413 mxUtils.write(i, label); 7414 div.appendChild(i); 7415 td.appendChild(div); 7416 } 7417 7418 row.appendChild(td); 7419 tbody.appendChild(row); 7420 userTable.appendChild(tbody); 7421 7422 this.userPanel.appendChild(userTable); 7423 var div = document.createElement('div'); 7424 div.style.textAlign = 'center'; 7425 div.style.padding = '10px'; 7426 div.style.whiteSpace = 'nowrap'; 7427 7428 if (logout != null) 7429 { 7430 var btn = mxUtils.button(mxResources.get('signOut'), logout); 7431 btn.className = 'geBtn'; 7432 div.appendChild(btn); 7433 } 7434 7435 this.userPanel.appendChild(div); 7436 } 7437 }); 7438 7439 if (this.dropbox != null) 7440 { 7441 addUser(this.dropbox.getUser(), IMAGE_PATH + '/dropbox-logo.svg', mxUtils.bind(this, function() 7442 { 7443 var file = this.getCurrentFile(); 7444 7445 if (file != null && file.constructor == DropboxFile) 7446 { 7447 var doLogout = mxUtils.bind(this, function() 7448 { 7449 this.dropbox.logout(); 7450 window.location.hash = ''; 7451 }); 7452 7453 if (!file.isModified()) 7454 { 7455 doLogout(); 7456 } 7457 else 7458 { 7459 this.confirm(mxResources.get('allChangesLost'), null, doLogout, 7460 mxResources.get('cancel'), mxResources.get('discardChanges')); 7461 } 7462 } 7463 else 7464 { 7465 this.dropbox.logout(); 7466 } 7467 }), mxResources.get('dropbox')); 7468 } 7469 7470 if (this.oneDrive != null) 7471 { 7472 addUser(this.oneDrive.getUser(), IMAGE_PATH + '/onedrive-logo.svg', this.oneDrive.noLogout? null : mxUtils.bind(this, function() 7473 { 7474 var file = this.getCurrentFile(); 7475 7476 if (file != null && file.constructor == OneDriveFile) 7477 { 7478 var doLogout = mxUtils.bind(this, function() 7479 { 7480 this.oneDrive.logout(); 7481 window.location.hash = ''; 7482 }); 7483 7484 if (!file.isModified()) 7485 { 7486 doLogout(); 7487 } 7488 else 7489 { 7490 this.confirm(mxResources.get('allChangesLost'), null, doLogout, 7491 mxResources.get('cancel'), mxResources.get('discardChanges')); 7492 } 7493 } 7494 else 7495 { 7496 this.oneDrive.logout(); 7497 } 7498 }), mxResources.get('oneDrive')); 7499 } 7500 7501 if (this.gitHub != null) 7502 { 7503 addUser(this.gitHub.getUser(), IMAGE_PATH + '/github-logo.svg', mxUtils.bind(this, function() 7504 { 7505 var file = this.getCurrentFile(); 7506 7507 if (file != null && file.constructor == GitHubFile) 7508 { 7509 var doLogout = mxUtils.bind(this, function() 7510 { 7511 this.gitHub.logout(); 7512 window.location.hash = ''; 7513 }); 7514 7515 if (!file.isModified()) 7516 { 7517 doLogout(); 7518 } 7519 else 7520 { 7521 this.confirm(mxResources.get('allChangesLost'), null, doLogout, 7522 mxResources.get('cancel'), mxResources.get('discardChanges')); 7523 } 7524 } 7525 else 7526 { 7527 this.gitHub.logout(); 7528 } 7529 }), mxResources.get('github')); 7530 } 7531 7532 if (this.gitLab != null) 7533 { 7534 addUser(this.gitLab.getUser(), IMAGE_PATH + '/gitlab-logo.svg', mxUtils.bind(this, function() 7535 { 7536 var file = this.getCurrentFile(); 7537 7538 if (file != null && file.constructor == GitLabFile) 7539 { 7540 var doLogout = mxUtils.bind(this, function() 7541 { 7542 this.gitLab.logout(); 7543 window.location.hash = ''; 7544 }); 7545 7546 if (!file.isModified()) 7547 { 7548 doLogout(); 7549 } 7550 else 7551 { 7552 this.confirm(mxResources.get('allChangesLost'), null, doLogout, 7553 mxResources.get('cancel'), mxResources.get('discardChanges')); 7554 } 7555 } 7556 else 7557 { 7558 this.gitLab.logout(); 7559 } 7560 }), mxResources.get('gitlab')); 7561 } 7562 7563 if (this.notion != null) 7564 { 7565 addUser(this.notion.getUser(), IMAGE_PATH + '/notion-logo.svg', mxUtils.bind(this, function() 7566 { 7567 var file = this.getCurrentFile(); 7568 7569 if (file != null && file.constructor == NotionFile) 7570 { 7571 var doLogout = mxUtils.bind(this, function() 7572 { 7573 this.notion.logout(); 7574 window.location.hash = ''; 7575 }); 7576 7577 if (!file.isModified()) 7578 { 7579 doLogout(); 7580 } 7581 else 7582 { 7583 this.confirm(mxResources.get('allChangesLost'), null, doLogout, 7584 mxResources.get('cancel'), mxResources.get('discardChanges')); 7585 } 7586 } 7587 else 7588 { 7589 this.notion.logout(); 7590 } 7591 }), mxResources.get('notion')); 7592 } 7593 7594 //TODO We have no user info from Trello, how we can create a user? 7595 if (this.trello != null) 7596 { 7597 addUser(this.trello.getUser(), IMAGE_PATH + '/trello-logo.svg', mxUtils.bind(this, function() 7598 { 7599 var file = this.getCurrentFile(); 7600 7601 if (file != null && file.constructor == TrelloFile) 7602 { 7603 var doLogout = mxUtils.bind(this, function() 7604 { 7605 this.trello.logout(); 7606 window.location.hash = ''; 7607 }); 7608 7609 if (!file.isModified()) 7610 { 7611 doLogout(); 7612 } 7613 else 7614 { 7615 this.confirm(mxResources.get('allChangesLost'), null, doLogout, 7616 mxResources.get('cancel'), mxResources.get('discardChanges')); 7617 } 7618 } 7619 else 7620 { 7621 this.trello.logout(); 7622 } 7623 }), mxResources.get('trello')); 7624 } 7625 7626 if (!connected) 7627 { 7628 var div = document.createElement('div'); 7629 div.style.textAlign = 'center'; 7630 div.style.padding = '10px'; 7631 div.innerHTML = mxResources.get('notConnected'); 7632 7633 this.userPanel.appendChild(div); 7634 } 7635 7636 var div = document.createElement('div'); 7637 div.style.textAlign = 'center'; 7638 div.style.padding = '10px'; 7639 div.style.background = Editor.isDarkMode() ? '' : 'whiteSmoke'; 7640 div.style.borderTop = '1px solid #e0e0e0'; 7641 div.style.whiteSpace = 'nowrap'; 7642 7643 if (urlParams['sketch'] == '1') 7644 { 7645 var btn = mxUtils.button(mxResources.get('share'), mxUtils.bind(this, function() 7646 { 7647 this.actions.get('share').funct(); 7648 })); 7649 btn.className = 'geBtn'; 7650 div.appendChild(btn); 7651 this.userPanel.appendChild(div); 7652 7653 if (this.commentsSupported()) 7654 { 7655 btn = mxUtils.button(mxResources.get('comments'), mxUtils.bind(this, function() 7656 { 7657 this.actions.get('comments').funct(); 7658 })); 7659 btn.className = 'geBtn'; 7660 div.appendChild(btn); 7661 this.userPanel.appendChild(div); 7662 } 7663 } 7664 else 7665 { 7666 var btn = mxUtils.button(mxResources.get('close'), mxUtils.bind(this, function() 7667 { 7668 if (!mxEvent.isConsumed(evt) && this.userPanel != null && this.userPanel.parentNode != null) 7669 { 7670 this.userPanel.parentNode.removeChild(this.userPanel); 7671 } 7672 })); 7673 btn.className = 'geBtn'; 7674 div.appendChild(btn); 7675 this.userPanel.appendChild(div); 7676 } 7677 7678 document.body.appendChild(this.userPanel); 7679 } 7680 7681 mxEvent.consume(evt); 7682 })); 7683 7684 mxEvent.addListener(document.body, 'click', mxUtils.bind(this, function(evt) 7685 { 7686 if (!mxEvent.isConsumed(evt) && this.userPanel != null && this.userPanel.parentNode != null) 7687 { 7688 this.userPanel.parentNode.removeChild(this.userPanel); 7689 } 7690 })); 7691 } 7692 7693 var user = null; 7694 7695 if (this.drive != null && this.drive.getUser() != null) 7696 { 7697 user = this.drive.getUser(); 7698 } 7699 else if (this.oneDrive != null && this.oneDrive.getUser() != null) 7700 { 7701 user = this.oneDrive.getUser(); 7702 } 7703 else if (this.dropbox != null && this.dropbox.getUser() != null) 7704 { 7705 user = this.dropbox.getUser(); 7706 } 7707 else if (this.gitHub != null && this.gitHub.getUser() != null) 7708 { 7709 user = this.gitHub.getUser(); 7710 } 7711 else if (this.gitLab != null && this.gitLab.getUser() != null) 7712 { 7713 user = this.gitLab.getUser(); 7714 } 7715 else if (this.notion != null && this.notion.getUser() != null) 7716 { 7717 user = this.notion.getUser(); 7718 } 7719 //TODO Trello no user issue 7720 7721 if (user != null) 7722 { 7723 this.userElement.innerHTML = ''; 7724 7725 if (screen.width > 560) 7726 { 7727 mxUtils.write(this.userElement, user.displayName); 7728 this.userElement.style.display = 'block'; 7729 } 7730 } 7731 else 7732 { 7733 this.userElement.style.display = 'none'; 7734 } 7735 } 7736}; 7737 7738//TODO Use this function to get the currently logged in user 7739App.prototype.getCurrentUser = function() 7740{ 7741 var user = null; 7742 7743 if (this.drive != null && this.drive.getUser() != null) 7744 { 7745 user = this.drive.getUser(); 7746 } 7747 else if (this.oneDrive != null && this.oneDrive.getUser() != null) 7748 { 7749 user = this.oneDrive.getUser(); 7750 } 7751 else if (this.dropbox != null && this.dropbox.getUser() != null) 7752 { 7753 user = this.dropbox.getUser(); 7754 } 7755 else if (this.gitHub != null && this.gitHub.getUser() != null) 7756 { 7757 user = this.gitHub.getUser(); 7758 } 7759 //TODO Trello no user issue 7760 7761 return user; 7762} 7763/** 7764 * Override depends on mxSettings which is not defined in the minified viewer. 7765 */ 7766var editorResetGraph = Editor.prototype.resetGraph; 7767Editor.prototype.resetGraph = function() 7768{ 7769 editorResetGraph.apply(this, arguments); 7770 7771 // Overrides default with persisted value 7772 if (this.graph.defaultPageFormat == null) 7773 { 7774 this.graph.pageFormat = mxSettings.getPageFormat(); 7775 } 7776}; 7777