gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
668 lines (663 loc) • 35.7 kB
JavaScript
/*
* Copyright (C) 1998-2023 by Northwoods Software Corporation
* All Rights Reserved.
*
* Go DropBox
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./GoCloudStorage.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoDropBox = void 0;
var gcs = require("./GoCloudStorage.js");
/**
* Class for saving / loading GoJS {@link Model}s to / from Dropbox.
* As with all {@link GoCloudStorage} subclasses (with the exception of {@link GoLocalStorage}, any page using GoDropBox must be served on a web server.
*
* **Note**: Any page using GoDropBox must include a script tag with a reference to the <a href="https://cdnjs.com/libraries/dropbox.js/">Dropbox JS SDK</a>.
* @category Storage
*/
var GoDropBox = /** @class */ (function (_super) {
__extends(GoDropBox, _super);
/**
* @constructor
* @param {go.Diagram|go.Diagram[]} managedDiagrams An array of GoJS {@link Diagram}s whose model(s) will be saved to / loaded from Dropbox.
* Can also be a single Diagram.
* @param {string} clientId The client ID of the application in use (given in Dropbox Developer's Console)
* @param {string} defaultModel String representation of the default model data for new diagrams. If this is null,
* default new diagrams will be empty. Usually a value given by calling {@link Model#toJson} on a GoJS Diagram's Model.
* @param {string} iconsRelativeDirectory The directory path relative to the page in which this instance of GoDropBox exists, in which
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
*/
function GoDropBox(managedDiagrams, clientId, defaultModel, iconsRelativeDirectory) {
var _this = _super.call(this, managedDiagrams, defaultModel, clientId, iconsRelativeDirectory) || this;
if (window['Dropbox']) {
var Dropbox = window['Dropbox'];
_this._dropbox = new Dropbox({ clientId: clientId });
}
_this.menuPath = '';
_this.ui.id = 'goDropBoxCustomFilepicker';
_this._serviceName = 'Dropbox';
_this._className = 'GoDropBox';
_this._options = {
// Required. Called when a user selects an item in the Chooser.
success: function (files) {
alert("Here's the file link: " + files[0].link);
},
// Optional. Called when the user closes the dialog without selecting a file
// and does not include any parameters.
cancel: function () {
},
// Optional. "preview" (default) is a preview link to the document for sharing,
// "direct" is an expiring link to download the contents of the file. For more
// information about link types, see Link types below.
linkType: 'direct',
// Optional. A value of false (default) limits selection to a single file, while
// true enables multiple file selection.
multiselect: false,
// Optional. This is a list of file extensions. If specified, the user will
// only be able to select files with these extensions. You may also specify
// file types, such as "video" or "images" in the list. For more information,
// see File types below. By default, all extensions are allowed.
extensions: ['.pdf', '.doc', '.docx', '.diagram'],
// Optional. A value of false (default) limits selection to files,
// while true allows the user to select both folders and files.
// You cannot specify `linkType: "direct"` when using `folderselect: true`.
folderselect: false // or true
};
return _this;
}
Object.defineProperty(GoDropBox.prototype, "dropbox", {
/**
* Get the <a href="https://github.com/dropbox/dropbox-sdk-js">Dropbox client</a> instance associated with this instance of GoDropBox
* (via {@link #clientId}). Set during {@link #authorize}.
* @function.
* @return {any}
*/
get: function () { return this._dropbox; },
enumerable: false,
configurable: true
});
Object.defineProperty(GoDropBox.prototype, "menuPath", {
/**
* Get / set currently open Dropnpx path in custom filepicker {@link #ui}. Default value is the empty string, which corresponds to the
* currently signed in user's Drobox account's root path. Set when a user clicks on a folder in the custom ui menu by invoking anchor onclick values.
* These onclick values are set when the Dropbox directory at the current menuPath is displayed with {@link #showUI}.
* @function.
* @return {string}
*/
get: function () { return this._menuPath; },
set: function (value) { this._menuPath = value; },
enumerable: false,
configurable: true
});
/**
* Check if there is a signed in Dropbox user who has authorized the application linked to this instance of GoDropBox (via {@link #clientId}).
* If not, prompt user to sign in / authenticate their Dropbox account.
* @param {boolean} refreshToken Whether to get a new acess token (triggers a page redirect) (true) or try to find / use the
* one in the browser window URI (no redirect) (false)
* @return {Promise<boolean>} Returns a Promise that resolves with a boolean stating whether authorization was succesful (true) or failed (false)
*/
GoDropBox.prototype.authorize = function (refreshToken) {
if (refreshToken === void 0) { refreshToken = false; }
var storage = this;
return new Promise(function (resolve, reject) {
// First, check if we're explicitly being told to refresh token (redirect to login screen)
if (refreshToken) {
storage.maybeSaveAppState();
// redirect to the Dropbox sign in page for authentication
var authUrl = storage.dropbox.getAuthenticationUrl(window.location.href);
window.location.href = authUrl;
resolve(false);
}
else if (!storage.dropbox.getAccessToken()) { // Then, check if there is no access token set on our Dropbox instance...
// if no redirect, check if there's an db_id and access_token in the current uri
if (storage.getAccessTokenFromUrl()) {
storage.dropbox.setAccessToken(storage.getAccessTokenFromUrl());
resolve(true);
}
else {
storage.maybeSaveAppState();
// if not, redirect to get an access_token from Dropbox login screen
var authUrl = storage.dropbox.getAuthenticationUrl(window.location.href);
window.location.href = authUrl;
resolve(false);
}
}
// load in diagrams' models from before the auth flow started (preserve prior app state)
storage.maybeLoadAppState();
// If not explicitly redirecting and we have an access token already, we're already authenticated
resolve(true);
});
};
GoDropBox.prototype.getAccessTokenFromUrl = function () {
var accessToken = window.location.hash.substring(window.location.hash.indexOf('=') + 1, window.location.hash.indexOf('&'));
return !!accessToken ? accessToken : null;
};
/**
* Attempt to preserve the app state in local storage
* This is usually called before a redirect (usually auth flow), so when we return to the app page, we can have the same model data
*/
GoDropBox.prototype.maybeSaveAppState = function () {
var storage = this;
try {
// temp save the current diagram model data to local storage, if possible (preserver prior app state)
// This will be loaded back in after the auth process (which happens in another window)
var item = storage.makeSaveFile();
window.localStorage.setItem('gdb-' + storage.clientId, item);
}
catch (e) {
throw new Error('Local storage not supported; diagrams model data will not be preserved during Dropboc authentication.');
}
};
/**
* Attempt to load the previous app state from local storage
* This is usually called after a redirect from another page (usually auth flow),
* so when we return to the app page, we can have the same model data as before
*/
GoDropBox.prototype.maybeLoadAppState = function () {
var storage = this;
try {
var fileContents = window.localStorage.getItem('gdb-' + storage.clientId);
storage.loadFromFileContents(fileContents);
localStorage.removeItem('gdb-' + storage.clientId);
}
catch (e) { }
};
/**
* Sign out the currently signed in Dropbox user
* Note: Since this redirects the app page, unsaved diagram model data will be lost after calling this
*/
GoDropBox.prototype.signOut = function () {
var storage = this;
var dbx = storage.dropbox;
// if (!dbx.getAccessToken()) return;
storage.maybeSaveAppState();
dbx.setAccessToken(null);
dbx.authTokenRevoke();
/*.then(function(response) {
// the access token for `dbx` has been revoked
console.log("got authTokenRevoke response:");
console.log(response);
// this should fail now:
dbx.usersGetCurrentAccount()
.then(function(response) {
console.log("got usersGetCurrentAccount response:");
console.log(response);
})
.catch(function(error) {
console.log("got usersGetCurrentAccount error:");
console.log(error);
});
})
.catch(function(error) {
console.log("got authTokenRevoke error:");
console.log(error);
});*/
// window.location.href = window.location.href.substring(0, window.location.href.indexOf('#'));
};
/*
No longer used???
public testAuth() {
const xhr: XMLHttpRequest = new XMLHttpRequest();
const link: string = 'https://www.dropbox.com/oauth2/authorize';
xhr.open('GET', link, true);
xhr.setRequestHeader('response_type', 'code');
xhr.setRequestHeader('client_id', this.clientId);
xhr.onload = function () {
if (xhr.readyState === 4 && (xhr.status === 200)) {
// tslint:disable-next-line:no-console
console.log(xhr.response);
} else {
throw new Error(xhr.response); // failed to load
}
}; // end xhr onload
xhr.send();
}*/
/**
* Get information about the currently logged in Dropbox user. Some properties of particular note include:
* - country
* - email
* - account_id
* - name
* - abbreviated_name
* - display_name
* - given_name
* - surname
* @return {Promise} Returns a Promise that resolves with information about the currently logged in Dropbox user
*/
GoDropBox.prototype.getUserInfo = function () {
var storage = this;
return new Promise(function (resolve, reject) {
// Case: No access token in URI
if (!storage.dropbox.getAccessToken() && window.location.hash.indexOf('access_token') === -1) {
storage.authorize(true);
}
else if (!storage.dropbox.getAccessToken() && window.location.hash.indexOf('access_token') === 1) {
storage.authorize(false);
}
storage.dropbox.usersGetCurrentAccount(null).then(function (userData) {
resolve(userData);
}).catch(function (e) {
// Case: storage.dropbox.access_token has expired or become malformed; get another access token
if (e.status === 400) {
storage.authorize(true);
}
});
});
};
GoDropBox.prototype.showUI = function () {
var storage = this;
var ui = storage.ui;
ui.innerHTML = ''; // clear div
ui.style.visibility = 'visible';
ui.innerHTML = "<img class='icons' src='" + storage.iconsRelativeDirectory + "dropBox.png'></img><strong>Save Diagram As</strong><hr></hr>";
// user input div
var userInputDiv = document.createElement('div');
userInputDiv.id = 'userInputDiv';
userInputDiv.innerHTML += '<input id="gdb-userInput" placeholder="Enter filename"></input>';
ui.appendChild(userInputDiv);
var submitDiv = document.createElement('div');
submitDiv.id = 'submitDiv';
var actionButton = document.createElement('button');
actionButton.id = 'actionButton';
actionButton.textContent = 'Save';
actionButton.onclick = function () {
var input = (document.getElementById('gdb-userInput'));
var val = input.value;
if (val !== '' && val !== undefined && val != null) {
ui.style.visibility = 'hidden';
storage.saveWithUI(val);
}
};
submitDiv.appendChild(actionButton);
ui.appendChild(submitDiv);
var cancelDiv = document.createElement('div');
cancelDiv.id = 'cancelDiv';
var cancelButton = document.createElement('button');
cancelButton.id = 'cancelButton';
cancelButton.textContent = 'Cancel';
cancelButton.onclick = function () {
storage.hideUI(true);
};
cancelDiv.appendChild(cancelButton);
ui.appendChild(cancelDiv);
return storage._deferredPromise.promise;
};
/**
* Hide the custom GoDropBox filepicker {@link #ui}; nullify {@link #menuPath}.
* @param {boolean} isActionCanceled If action (Save, Delete, Load) is cancelled, resolve the Promise returned in {@link #showUI} with a 'Canceled' notification.
*/
GoDropBox.prototype.hideUI = function (isActionCanceled) {
var storage = this;
storage.menuPath = '';
_super.prototype.hideUI.call(this, isActionCanceled);
};
/**
* @private
* @hidden
* Process the result of pressing the action (Save, Delete, Load) button on the custom GoDropBox filepicker {@link #ui}.
* @param {string} action The action that must be done. Acceptable values:
* - Save
* - Delete
* - Load
*/
GoDropBox.prototype.processUIResult = function (action) {
var storage = this;
/**
* Get the selected file (in menu's) Dropbox filepath
* @return {string} The selected file's Dropbox filepath
*/
function getSelectedFilepath() {
var radios = document.getElementsByName('dropBoxFile');
var selectedFile = null;
for (var i = 0; i < radios.length; i++) {
if (radios[i].checked) {
selectedFile = radios[i].getAttribute('data');
}
}
return selectedFile;
}
var filePath = getSelectedFilepath();
switch (action) {
case 'Save': {
if (storage.menuPath || storage.menuPath === '') {
var name_1 = document.getElementById('userInput').value;
if (name_1) {
if (name_1.indexOf('.diagram') === -1)
name_1 += '.diagram';
storage.save(storage.menuPath + '/' + name_1);
}
else {
// handle bad save name
// tslint:disable-next-line:no-console
console.log('Proposed file name is not valid'); // placeholder handler
}
}
break;
}
case 'Load': {
storage.load(filePath);
break;
}
case 'Delete': {
storage.remove(filePath);
break;
}
}
storage.hideUI();
};
/**
* Check whether a file exists in user's Dropbox at a given path.
* @param {string} path A valid Dropbox filepath. Path syntax is `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`.
* Alternatively, this may be a valid Dropbox file ID.
* @return {Promise} Returns a Promise that resolves with a boolean stating whether a file exists in user's Dropbox at a given path
*/
GoDropBox.prototype.checkFileExists = function (path) {
var storage = this;
if (path.indexOf('.diagram') === -1)
path += '.diagram';
return new Promise(function (resolve, reject) {
storage.dropbox.filesGetMetadata({ path: path }).then(function (resp) {
if (resp)
resolve(true);
}).catch(function (err) {
resolve(false);
});
});
};
/**
* Get the Dropbox file reference object at a given path. Properties of particular note include:
* - name: The name of the file in DropBox
* - id: The DropBox-given file ID
* - path_diplay: A lower-case version of the path this file is stored at in DropBox
* - .tag: A tag denoting the type of this file. Common values are "file" and "folder".
*
* **Note:** The first three elements in the above list are requisite for creating valid {@link DiagramFile}s.
* @param {string} path A valid Dropbox filepath. Path syntax is `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`.
* Alternatively, this may be a valid Dropbox file ID.
* @return {Promise} Returns a Promise that resolves with a Dropbox file reference object at a given path
*/
GoDropBox.prototype.getFile = function (path) {
var storage = this;
if (path.indexOf('.diagram') === -1)
path += '.diagram';
return storage.dropbox.filesGetMetadata({ path: path }).then(function (resp) {
if (resp)
return resp;
}).catch(function (err) {
return null;
});
};
/**
* Save the current {@link #managedDiagrams} model data to Dropbox with the filepicker {@link #ui}. Returns a Promise that resolves with a
* {@link DiagramFile} representing the saved file.
* @param {string} filename Optional: The name to save data to Dropbox under. If this is not provided, you will be prompted for a filename
* @return {Promise}
*/
GoDropBox.prototype.saveWithUI = function (filename) {
var storage = this;
if (filename === undefined || filename == null) {
// let filename: string = prompt("GIMME A NAME");
// storage.saveWithUI(filename);
return new Promise(function (resolve, reject) {
resolve(storage.showUI());
});
}
else {
if (filename.length < 8) {
filename += '.diagram';
}
else {
var lastEight = filename.substring(filename.length - 8, filename.length);
if (lastEight !== '.diagram') {
filename += '.diagram';
}
}
return new Promise(function (resolve, reject) {
storage._options.success = function (resp) {
var a = 3;
// find the file that was just saved
// look at all files with "filename" in title
// find most recent of those
var savedFile = null;
storage.dropbox.filesListFolder({
path: '',
recursive: true
}).then(function (r) {
var files = r.entries;
var possibleFiles = [];
/*for (let i in files) {
var file = files[i];
//var fname = filename.replace(/.diagram([^_]*)$/,'$1');
//console.log(fname);
if (file.filename.indexOf(fname) != -1 && file.filename.indexOf(".diagram") != -1) {
possibleFiles.push(file);
}
}*/
// find most recently modified (saved)
var latestestDate = new Date(-8400000);
var latestFile = null;
// for (let i in files) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
var dateModified = new Date(file.server_modified);
if (dateModified != null && dateModified !== undefined && dateModified instanceof Date) {
if (dateModified > latestestDate) {
dateModified = latestestDate;
latestFile = file;
}
}
}
// resolve Promises
// tslint:disable-next-line:no-shadowed-variable
var savedFile = { name: latestFile.name, path: latestFile.path_lower, id: latestFile.id };
storage.currentDiagramFile = savedFile;
resolve(savedFile);
storage._deferredPromise.promise.resolve(savedFile);
storage._deferredPromise.promise = storage.makeDeferredPromise();
});
};
function makeTextFile(text) {
var data = new Blob([text], { type: 'text/plain' });
var uri = '';
uri = window.URL.createObjectURL(data);
return uri;
}
var dataURI = 'data:text/html,' + encodeURIComponent(storage.makeSaveFile());
var Dropbox = window['Dropbox'];
Dropbox.save(dataURI, filename, storage._options);
});
} // end if filename exists case
};
/**
* Save {@link #managedDiagrams}' model data to Dropbox. If path is supplied save to that path. If no path is supplied but {@link #currentDiagramFile} has non-null,
* valid properties, update saved diagram file content at the path in Dropbox corresponding to currentDiagramFile.path with current managedDiagrams' model data.
* If no path is supplied and currentDiagramFile is null or has null properties, this calls {@link #saveWithUI}.
* @param {string} path A valid Dropbox filepath to save current diagram model to. Path syntax is `/{path-to-file}/{filename}`;
* i.e. `/Public/example.diagram`.
* Alternatively, this may be a valid Dropbox file ID.
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file.
*/
GoDropBox.prototype.save = function (path) {
var storage = this;
return new Promise(function (resolve, reject) {
if (path) { // save as
storage.dropbox.filesUpload({
contents: storage.makeSaveFile(),
path: path,
autorename: true,
mode: { '.tag': 'add' },
mute: false
}).then(function (resp) {
var savedFile = { name: resp.name, id: resp.id, path: resp.path_lower };
storage.currentDiagramFile = savedFile;
resolve(savedFile); // used if saveDiagramAs was called without UI
// if saveAs has been called in processUIResult, need to resolve / reset the Deferred Promise instance variable
storage._deferredPromise.promise.resolve(savedFile);
storage._deferredPromise.promise = storage.makeDeferredPromise();
}).catch(function (e) {
// Bad request: Access token is either expired or malformed. Get another one.
if (e.status === 400) {
storage.authorize(true);
}
});
}
else if (storage.currentDiagramFile.path) { // save
path = storage.currentDiagramFile.path;
storage.dropbox.filesUpload({
contents: storage.makeSaveFile(),
path: path,
autorename: false,
mode: { '.tag': 'overwrite' },
mute: true
}).then(function (resp) {
var savedFile = { name: resp.name, id: resp.id, path: resp.path_lower };
resolve(savedFile);
}).catch(function (e) {
// Bad request: Access token is either expired or malformed. Get another one.
if (e.status === 400) {
storage.authorize(true);
}
});
}
else {
resolve(storage.saveWithUI());
// throw Error("Cannot save file to Dropbox with path " + path);
}
});
};
/**
* Load the contents of a saved diagram from Dropbox using the custom filepicker {@link #ui}.
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file.
*/
GoDropBox.prototype.loadWithUI = function () {
var storage = this;
storage._options.success = function (r) {
var file = r[0];
// get the file path
storage.dropbox.filesGetMetadata({ path: file.id }).then(function (resp) {
var path = resp.path_display;
storage.load(path);
});
};
var Dropbox = window['Dropbox'];
Dropbox.choose(storage._options);
return storage._deferredPromise.promise; // will not resolve until action (save, load, delete) completes
};
/**
* Load the contents of a saved diagram from Dropbox.
* @param {string} path A valid Dropbox filepath to load diagram model data from. Path syntax is `/{path-to-file}/{filename}`;
* i.e. `/Public/example.diagram`.
* Alternatively, this may be a valid Dropbox file ID.
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
*/
GoDropBox.prototype.load = function (path) {
var storage = this;
return new Promise(function (resolve, reject) {
if (path) {
storage.dropbox.filesGetTemporaryLink({ path: path }).then(function (resp) {
var link = resp.link;
storage.currentDiagramFile.name = resp.metadata.name;
storage.currentDiagramFile.id = resp.metadata.id;
storage.currentDiagramFile.path = path;
var xhr = new XMLHttpRequest();
xhr.open('GET', link, true);
xhr.setRequestHeader('Authorization', 'Bearer ' + storage.dropbox.getAccessToken());
xhr.onload = function () {
if (xhr.readyState === 4 && (xhr.status === 200)) {
storage.loadFromFileContents(xhr.response);
var loadedFile = { name: resp.metadata.name, id: resp.metadata.id, path: resp.metadata.path_lower };
resolve(loadedFile); // used if loadDiagram was called without UI
// if loadDiagram has been called in processUIResult, need to resolve / reset the Deferred Promise instance variable
storage._deferredPromise.promise.resolve(loadedFile);
storage._deferredPromise.promise = storage.makeDeferredPromise();
}
else {
throw Error('Cannot load file from Dropbox with path ' + path); // failed to load
}
}; // end xhr onload
xhr.send();
}).catch(function (e) {
// Bad request: Access token is either expired or malformed. Get another one.
if (e.status === 400) {
storage.authorize(true);
}
});
}
else
throw Error('Cannot load file from Dropbox with path ' + path);
});
};
/**
* Delete a chosen diagram file from Dropbox using the custom filepicker {@link #ui}.
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file.
*/
GoDropBox.prototype.removeWithUI = function () {
var storage = this;
storage._options.success = function (r) {
var file = r[0];
// get the file path
storage.dropbox.filesGetMetadata({ path: file.id }).then(function (resp) {
var path = resp.path_display;
storage.remove(path);
});
};
var Dropbox = window['Dropbox'];
Dropbox.choose(storage._options);
return storage._deferredPromise.promise; // will not resolve until action (save, load, delete) completes
};
/**
* Delete a given diagram file from Dropbox.
* @param {string} path A valid Dropbox filepath to delete diagram model data from. Path syntax is
* `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`.
* Alternatively, this may be a valid Dropbox file ID.
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file.
*/
GoDropBox.prototype.remove = function (path) {
var storage = this;
return new Promise(function (resolve, reject) {
if (path) {
storage.dropbox.filesDelete({ path: path }).then(function (resp) {
if (storage.currentDiagramFile && storage.currentDiagramFile['id'] === resp['id'])
storage.currentDiagramFile = { name: null, path: null, id: null };
var deletedFile = { name: resp.name, id: resp['id'], path: resp.path_lower };
resolve(deletedFile); // used if deleteDiagram was called without UI
// if deleteDiagram has been called in processUIResult, need to resolve / reset the Deferred Promise instance variable
storage._deferredPromise.promise.resolve(deletedFile);
storage._deferredPromise.promise = storage.makeDeferredPromise();
}).catch(function (e) {
// Bad request: Access token is either expired or malformed. Get another one.
if (e.status === 400) {
storage.authorize(true);
}
});
}
else
throw Error('Cannot delete file from Dropbox with path ' + path);
});
};
return GoDropBox;
}(gcs.GoCloudStorage));
exports.GoDropBox = GoDropBox;
});