1/** 2 * Copyright (c) 2006-2017, JGraph Ltd 3 * Copyright (c) 2006-2017, Gaudenz Alder 4 */ 5DriveFile = function(ui, data, desc) 6{ 7 DrawioFile.call(this, ui, data); 8 9 this.desc = desc; 10}; 11 12//Extends mxEventSource 13mxUtils.extend(DriveFile, DrawioFile); 14 15/** 16 * Delay for last save in ms. 17 */ 18DriveFile.prototype.saveDelay = 0; 19 20/** 21 * Delay for last save in ms. 22 */ 23DriveFile.prototype.allChangesSavedKey = 'allChangesSavedInDrive'; 24 25/** 26 * Specifies if notify events should be ignored. 27 */ 28DriveFile.prototype.getSize = function() 29{ 30 return this.desc.fileSize; 31}; 32 33/** 34 * Returns true if copy, export and print are not allowed for this file. 35 */ 36DriveFile.prototype.isRestricted = function() 37{ 38 return this.desc.userPermission != null && this.desc.labels != null && 39 this.desc.userPermission.role == 'reader' && this.desc.labels.restricted; 40}; 41 42/** 43 * Adds the listener for automatically saving the diagram for local changes. 44 */ 45DriveFile.prototype.isConflict = function(err) 46{ 47 return err != null && err.error != null && err.error.code == 412; 48}; 49 50/** 51 * Returns the current etag. 52 */ 53DriveFile.prototype.getCurrentUser = function() 54{ 55 return (this.ui.drive != null) ? this.ui.drive.user : null; 56}; 57 58/** 59 * Translates this point by the given vector. 60 * 61 * @param {number} dx X-coordinate of the translation. 62 * @param {number} dy Y-coordinate of the translation. 63 */ 64DriveFile.prototype.getMode = function() 65{ 66 return App.MODE_GOOGLE; 67}; 68 69/** 70 * Returns true if copy, export and print are not allowed for this file. 71 */ 72DriveFile.prototype.getPublicUrl = function(fn) 73{ 74 this.ui.drive.executeRequest({ 75 url: '/files/' + this.desc.id + '/permissions?supportsAllDrives=true' 76 }, 77 mxUtils.bind(this, function(resp) 78 { 79 if (resp != null && resp.items != null) 80 { 81 for (var i = 0; i < resp.items.length; i++) 82 { 83 if (resp.items[i].id === 'anyoneWithLink' || 84 resp.items[i].id === 'anyone') 85 { 86 fn(this.desc.webContentLink); 87 88 return; 89 } 90 } 91 } 92 93 fn(null); 94 }), mxUtils.bind(this, function() 95 { 96 fn(null) 97 })); 98}; 99 100/** 101 * Overridden to enable the autosave option in the document properties dialog 102 * if realtime is not used. 103 */ 104DriveFile.prototype.isAutosaveOptional = function() 105{ 106 return true; 107}; 108 109/** 110 * Translates this point by the given vector. 111 * 112 * @param {number} dx X-coordinate of the translation. 113 * @param {number} dy Y-coordinate of the translation. 114 */ 115DriveFile.prototype.isRenamable = function() 116{ 117 return this.isEditable() && DrawioFile.prototype.isEditable.apply(this, arguments); 118}; 119 120/** 121 * Translates this point by the given vector. 122 * 123 * @param {number} dx X-coordinate of the translation. 124 * @param {number} dy Y-coordinate of the translation. 125 */ 126DriveFile.prototype.isMovable = function() 127{ 128 return this.isEditable(); 129}; 130 131/** 132 * Translates this point by the given vector. 133 * 134 * @param {number} dx X-coordinate of the translation. 135 * @param {number} dy Y-coordinate of the translation. 136 */ 137DriveFile.prototype.isTrashed = function() 138{ 139 return this.desc.labels.trashed; 140}; 141 142/** 143 * Translates this point by the given vector. 144 * 145 * @param {number} dx X-coordinate of the translation. 146 * @param {number} dy Y-coordinate of the translation. 147 */ 148DriveFile.prototype.save = function(revision, success, error, unloading, overwrite) 149{ 150 DrawioFile.prototype.save.apply(this, [revision, mxUtils.bind(this, function() 151 { 152 this.saveFile(null, revision, success, error, unloading, overwrite); 153 }), error, unloading, overwrite]); 154}; 155 156/** 157 * Translates this point by the given vector. 158 * 159 * @param {number} dx X-coordinate of the translation. 160 * @param {number} dy Y-coordinate of the translation. 161 */ 162DriveFile.prototype.saveFile = function(title, revision, success, error, unloading, overwrite) 163{ 164 try 165 { 166 if (!this.isEditable()) 167 { 168 if (success != null) 169 { 170 success(); 171 } 172 } 173 else if (!this.savingFile) 174 { 175 // Sets shadow modified state during save 176 this.savingFileTime = new Date(); 177 this.setShadowModified(false); 178 this.savingFile = true; 179 180 this.createSecret(mxUtils.bind(this, function(secret, token) 181 { 182 var doSave = mxUtils.bind(this, function(realOverwrite, realRevision) 183 { 184 try 185 { 186 var lastDesc = this.desc; 187 188 this.ui.drive.saveFile(this, realRevision, mxUtils.bind(this, function(resp, savedData) 189 { 190 try 191 { 192 this.savingFile = false; 193 194 // Handles special case where resp is false eg 195 // if the old file was converted to realtime 196 if (resp != false) 197 { 198 // Checks for changes during save 199 this.setModified(this.getShadowModified()); 200 201 if (revision) 202 { 203 this.lastAutosaveRevision = new Date().getTime(); 204 } 205 206 // Adaptive autosave delay 207 this.autosaveDelay = Math.round(Math.min(10000, 208 Math.max(DriveFile.prototype.autosaveDelay, 209 this.saveDelay))); 210 this.desc = resp; 211 212 // Shows possible errors but keeps the modified flag as the 213 // file was saved but the cache entry could not be written 214 if (token != null) 215 { 216 this.fileSaved(savedData, lastDesc, mxUtils.bind(this, function() 217 { 218 this.contentChanged(); 219 220 if (success != null) 221 { 222 success(resp); 223 } 224 }), error, token); 225 } 226 else if (success != null) 227 { 228 success(resp); 229 } 230 } 231 else if (error != null) 232 { 233 error(resp); 234 } 235 } 236 catch (e) 237 { 238 this.savingFile = false; 239 240 if (error != null) 241 { 242 error(e); 243 } 244 else 245 { 246 throw e; 247 } 248 } 249 }), mxUtils.bind(this, function(err, desc) 250 { 251 try 252 { 253 this.savingFile = false; 254 255 if (this.isConflict(err)) 256 { 257 this.inConflictState = true; 258 259 if (this.sync != null) 260 { 261 this.savingFile = true; 262 263 this.sync.fileConflict(desc, mxUtils.bind(this, function() 264 { 265 // Adds random cool-off 266 window.setTimeout(mxUtils.bind(this, function() 267 { 268 this.updateFileData(); 269 this.setShadowModified(false); 270 doSave(realOverwrite, true); 271 }), 100 + Math.random() * 500); 272 }), mxUtils.bind(this, function() 273 { 274 this.savingFile = false; 275 276 if (error != null) 277 { 278 error(); 279 } 280 })); 281 } 282 else if (error != null) 283 { 284 error(); 285 } 286 } 287 else if (error != null) 288 { 289 error(err); 290 } 291 } 292 catch (e) 293 { 294 this.savingFile = false; 295 296 if (error != null) 297 { 298 error(e); 299 } 300 else 301 { 302 throw e; 303 } 304 } 305 }), unloading, unloading, realOverwrite, null, secret); 306 } 307 catch (e) 308 { 309 this.savingFile = false; 310 311 if (error != null) 312 { 313 error(e); 314 } 315 else 316 { 317 throw e; 318 } 319 } 320 }); 321 322 doSave(overwrite, revision); 323 })); 324 } 325 } 326 catch (e) 327 { 328 if (error != null) 329 { 330 error(e); 331 } 332 else 333 { 334 throw e; 335 } 336 } 337}; 338 339/** 340 * Shows a conflict dialog to the user. 341 */ 342DriveFile.prototype.copyFile = function(success, error) 343{ 344 if (!this.isRestricted()) 345 { 346 this.makeCopy(mxUtils.bind(this, function() 347 { 348 if (this.ui.spinner.spin(document.body, mxResources.get('saving'))) 349 { 350 try 351 { 352 this.save(true, success, error) 353 } 354 catch (e) 355 { 356 error(e); 357 } 358 } 359 }), error, true); 360 } 361 else 362 { 363 DrawioFile.prototype.copyFile.apply(this, arguments); 364 } 365}; 366 367/** 368 * Shows a conflict dialog to the user. 369 */ 370DriveFile.prototype.makeCopy = function(success, error, timestamp) 371{ 372 if (this.ui.spinner.spin(document.body, mxResources.get('saving'))) 373 { 374 // Uses copyFile internally which is a remote REST call with the advantage of keeping 375 // the parents of the file in-place, but copies the remote file contents so needs to 376 // be updated as soon as we have the ID. 377 this.saveAs(this.ui.getCopyFilename(this, timestamp), mxUtils.bind(this, function(resp) 378 { 379 this.desc = resp; 380 this.ui.spinner.stop(); 381 this.setModified(false); 382 383 this.backupPatch = null; 384 this.invalidChecksum = false; 385 this.inConflictState = false; 386 387 this.descriptorChanged(); 388 success(); 389 }), mxUtils.bind(this, function() 390 { 391 this.ui.spinner.stop(); 392 393 if (error != null) 394 { 395 error(); 396 } 397 })); 398 } 399}; 400 401/** 402 * Translates this point by the given vector. 403 * 404 * @param {number} dx X-coordinate of the translation. 405 * @param {number} dy Y-coordinate of the translation. 406 */ 407DriveFile.prototype.saveAs = function(filename, success, error) 408{ 409 this.ui.drive.copyFile(this.getId(), filename, success, error); 410}; 411 412/** 413 * Translates this point by the given vector. 414 * 415 * @param {number} dx X-coordinate of the translation. 416 * @param {number} dy Y-coordinate of the translation. 417 */ 418DriveFile.prototype.rename = function(title, success, error) 419{ 420 var etag = this.getCurrentEtag(); 421 422 this.ui.drive.renameFile(this.getId(), title, mxUtils.bind(this, function(desc) 423 { 424 if (!this.hasSameExtension(title, this.getTitle())) 425 { 426 this.desc = desc; 427 428 if (this.sync != null) 429 { 430 this.sync.descriptorChanged(etag); 431 } 432 433 this.save(true, success, error); 434 } 435 else 436 { 437 this.desc = desc; 438 this.descriptorChanged(); 439 440 if (this.sync != null) 441 { 442 this.sync.descriptorChanged(etag); 443 } 444 445 if (success != null) 446 { 447 success(desc); 448 } 449 } 450 }), error); 451}; 452 453/** 454 * Translates this point by the given vector. 455 * 456 * @param {number} dx X-coordinate of the translation. 457 * @param {number} dy Y-coordinate of the translation. 458 */ 459DriveFile.prototype.move = function(folderId, success, error) 460{ 461 this.ui.drive.moveFile(this.getId(), folderId, mxUtils.bind(this, function(resp) 462 { 463 this.desc = resp; 464 this.descriptorChanged(); 465 466 if (success != null) 467 { 468 success(resp); 469 } 470 }), error); 471}; 472 473/** 474 * Translates this point by the given vector. 475 * 476 * @param {number} dx X-coordinate of the translation. 477 * @param {number} dy Y-coordinate of the translation. 478 */ 479DriveFile.prototype.share = function() 480{ 481 this.ui.drive.showPermissions(this.getId()); 482}; 483 484/** 485 * Translates this point by the given vector. 486 * 487 * @param {number} dx X-coordinate of the translation. 488 * @param {number} dy Y-coordinate of the translation. 489 */ 490DriveFile.prototype.getTitle = function() 491{ 492 return this.desc.title; 493}; 494 495/** 496 * Translates this point by the given vector. 497 * 498 * @param {number} dx X-coordinate of the translation. 499 * @param {number} dy Y-coordinate of the translation. 500 */ 501DriveFile.prototype.getHash = function() 502{ 503 return 'G' + this.getId(); 504}; 505 506/** 507 * Translates this point by the given vector. 508 * 509 * @param {number} dx X-coordinate of the translation. 510 * @param {number} dy Y-coordinate of the translation. 511 */ 512DriveFile.prototype.getId = function() 513{ 514 return this.desc.id; 515}; 516 517/** 518 * Translates this point by the given vector. 519 * 520 * @param {number} dx X-coordinate of the translation. 521 * @param {number} dy Y-coordinate of the translation. 522 */ 523DriveFile.prototype.isEditable = function() 524{ 525 return DrawioFile.prototype.isEditable.apply(this, arguments) && 526 this.desc.editable; 527}; 528 529/** 530 * Hook for subclassers. 531 */ 532DriveFile.prototype.isSyncSupported = function() 533{ 534 return true; 535}; 536 537/** 538 * Hook for subclassers. 539 */ 540DriveFile.prototype.isRevisionHistorySupported = function() 541{ 542 return true; 543}; 544 545/** 546 * Hook for subclassers. 547 */ 548DriveFile.prototype.getRevisions = function(success, error) 549{ 550 this.ui.drive.executeRequest( 551 { 552 url: '/files/' + this.getId() + '/revisions' 553 }, 554 mxUtils.bind(this, function(resp) 555 { 556 for (var i = 0; i < resp.items.length; i++) 557 { 558 (mxUtils.bind(this, function(item) 559 { 560 // Redirects title to originalFilename to 561 // match expected descriptor interface 562 item.title = item.originalFilename; 563 564 item.getXml = mxUtils.bind(this, function(itemSuccess, itemError) 565 { 566 this.ui.drive.getXmlFile(item, mxUtils.bind(this, function(file) 567 { 568 itemSuccess(file.getData()); 569 }), itemError); 570 }); 571 572 item.getUrl = mxUtils.bind(this, function(page) 573 { 574 return this.ui.getUrl(window.location.pathname + '?rev=' + item.id + 575 '&chrome=0&nav=1&layers=1&edit=_blank' + ((page != null) ? 576 '&page=' + page : '')) + window.location.hash; 577 }); 578 }))(resp.items[i]); 579 } 580 581 success(resp.items); 582 }), error); 583}; 584 585/** 586 * Adds the listener for automatically saving the diagram for local changes. 587 */ 588DriveFile.prototype.getLatestVersion = function(success, error) 589{ 590 this.ui.drive.getFile(this.getId(), success, error, true); 591}; 592 593/** 594 * Adds all listeners. 595 */ 596DriveFile.prototype.getChannelId = function() 597{ 598 var chan = this.ui.drive.getCustomProperty(this.desc, 'channel'); 599 600 if (chan != null) 601 { 602 chan = 'G-' + this.getId() + '.' + chan; 603 } 604 605 return chan; 606}; 607 608/** 609 * Gets the channel ID from the given descriptor. 610 */ 611DriveFile.prototype.getChannelKey = function() 612{ 613 return this.ui.drive.getCustomProperty(this.desc, 'key'); 614}; 615 616/** 617 * Adds all listeners. 618 */ 619DriveFile.prototype.getLastModifiedDate = function() 620{ 621 return new Date(this.desc.modifiedDate); 622}; 623 624/** 625 * Adds all listeners. 626 */ 627DriveFile.prototype.getDescriptor = function() 628{ 629 return this.desc; 630}; 631 632/** 633* Updates the descriptor of this file with the one from the given file. 634*/ 635DriveFile.prototype.setDescriptor = function(desc) 636{ 637 this.desc = desc; 638}; 639 640/** 641 * Returns the etag from the given descriptor. 642 */ 643DriveFile.prototype.getDescriptorSecret = function(desc) 644{ 645 return this.ui.drive.getCustomProperty(desc, 'secret'); 646}; 647 648/** 649 * Updates the revision ID on the given descriptor. 650 */ 651DriveFile.prototype.setDescriptorRevisionId = function(desc, id) 652{ 653 desc.headRevisionId = id; 654}; 655 656/** 657 * Returns the revision ID from the given descriptor. 658 */ 659DriveFile.prototype.getDescriptorRevisionId = function(desc) 660{ 661 return desc.headRevisionId; 662}; 663 664/** 665 * Adds all listeners. 666 */ 667DriveFile.prototype.getDescriptorEtag = function(desc) 668{ 669 return desc.etag; 670}; 671 672/** 673 * Adds the listener for automatically saving the diagram for local changes. 674 */ 675DriveFile.prototype.setDescriptorEtag = function(desc, etag) 676{ 677 desc.etag = etag; 678}; 679 680/** 681 * Adds the listener for automatically saving the diagram for local changes. 682 */ 683DriveFile.prototype.loadPatchDescriptor = function(success, error) 684{ 685 this.ui.drive.executeRequest( 686 { 687 url: '/files/' + this.getId() + '?supportsAllDrives=true&fields=' + this.ui.drive.catchupFields 688 }, 689 mxUtils.bind(this, function(desc) 690 { 691 success(desc); 692 }), error); 693}; 694 695/** 696 * Adds the listener for automatically saving the diagram for local changes. 697 */ 698DriveFile.prototype.patchDescriptor = function(desc, patch) 699{ 700 desc.headRevisionId = patch.headRevisionId; 701 desc.modifiedDate = patch.modifiedDate; 702 703 DrawioFile.prototype.patchDescriptor.apply(this, arguments); 704}; 705 706/** 707 * Adds the listener for automatically saving the diagram for local changes. 708 */ 709DriveFile.prototype.loadDescriptor = function(success, error) 710{ 711 this.ui.drive.loadDescriptor(this.getId(), success, error); 712}; 713 714/** 715 * Are comments supported 716 */ 717DriveFile.prototype.commentsSupported = function() 718{ 719 return true; 720}; 721 722/** 723 * Get comments of the file 724 */ 725DriveFile.prototype.getComments = function(success, error) 726{ 727 var currentUser = this.ui.getCurrentUser(); 728 729 function driveCommentToDrawio(file, gComment, pCommentId) 730 { 731 if (gComment.deleted) return null; //skip deleted comments 732 733 var comment = new DriveComment(file, gComment.commentId || gComment.replyId, gComment.content, 734 gComment.modifiedDate, gComment.createdDate, gComment.status == 'resolved', 735 gComment.author.isAuthenticatedUser? currentUser : 736 new DrawioUser(gComment.author.permissionId, gComment.author.emailAddress, 737 gComment.author.displayName, gComment.author.picture.url), pCommentId); 738 739 for (var i = 0; gComment.replies != null && i < gComment.replies.length; i++) 740 { 741 comment.addReplyDirect(driveCommentToDrawio(file, gComment.replies[i], gComment.commentId)); 742 } 743 744 return comment; 745 }; 746 747 this.ui.drive.executeRequest( 748 { 749 url: '/files/' + this.getId() + '/comments' 750 }, 751 mxUtils.bind(this, function(resp) 752 { 753 var comments = []; 754 755 for (var i = 0; i < resp.items.length; i++) 756 { 757 var comment = driveCommentToDrawio(this, resp.items[i]); 758 759 if (comment != null) comments.push(comment); 760 } 761 762 success(comments); 763 }), error); 764}; 765 766/** 767 * Add a comment to the file 768 */ 769DriveFile.prototype.addComment = function(comment, success, error) 770{ 771 var body = {'content': comment.content}; 772 773 this.ui.drive.executeRequest( 774 { 775 url: '/files/' + this.getId() + '/comments', 776 method: 'POST', 777 params: body 778 }, 779 mxUtils.bind(this, function(resp) 780 { 781 success(resp.commentId); //pass comment id 782 }), error); 783}; 784 785/** 786 * Can add a reply to a reply 787 */ 788DriveFile.prototype.canReplyToReplies = function() 789{ 790 return false; 791}; 792 793/** 794 * Can add comments (The permission to comment to this file) 795 */ 796DriveFile.prototype.canComment = function() 797{ 798 return this.desc.canComment; 799}; 800 801/** 802 * Get a new comment object 803 */ 804DriveFile.prototype.newComment = function(content, user) 805{ 806 return new DriveComment(this, null, content, Date.now(), Date.now(), false, user); 807}; 808