feat: 后端全量更新 - 含所有本次需求
- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
This commit is contained in:
+651
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* GoCloudStorage.js
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
|
||||
|
||||
/**
|
||||
* A simple interface containing basic information about a diagram saved to a storage service.
|
||||
* Guarantees the existence of file id, name, and path.
|
||||
*
|
||||
* DiagramFiles are used as a minimal representation of files stored in data. {@link GoCloudStorage#currentDiagramFile}.
|
||||
* {@link GoCloudStorage#save}, {@link GoCloudStorage#load}, {@link GoCloudStorage#remove},
|
||||
* and {@link GoCloudStorage#create} all return Promises which resolve with
|
||||
* DiagramFiles containing minimal data about the saved / loaded / removed / created file in storage.
|
||||
* @category Storage
|
||||
*/
|
||||
export interface DiagramFile {
|
||||
/**
|
||||
* The storage-given ID of the diagram file. This is usually a lengthy alphanumeric string.
|
||||
*
|
||||
* **Note**: In the case of files saved to / loaded from Local Storage with {@link GoLocalStorage},
|
||||
* ID is the same as the name of the file.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The name of the diagram file in storage. This is assigned by the user during {@link GoCloudStorage#save}.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The path of the diagram file in storage. Rules for valid path syntax by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs.
|
||||
* Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* token is sometimes necesary for {@link GoOneDrive}. This is an access token given by the
|
||||
* <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">
|
||||
* Microsoft OneDrive Filepicker</a> allowing read / write on a specific drive file.
|
||||
* No other {@link GoCloudStorage} subclasses use it. It is unlikely one will use this field without editing source code.
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* parentReference is sometimes necesary for {@link GoOneDrive}. It is the
|
||||
* <a href="https://developer.microsoft.com/en-us/graph/docs/api-reference/beta/resources/itemreference"> parent information</a>
|
||||
* of a Microsoft OneDrive item, if the item has a parent.
|
||||
* No other {@link GoCloudStorage} subclasses use it. It is unlikely one will use this field without editing source code.
|
||||
*/
|
||||
parentReference?: Object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* A simple interface acting as a wrapper for a Promise that gurantees the existence of the promise field.
|
||||
* DeferredPromise is important in that it allows for a Promise to be returned by one function and resolved within another.
|
||||
* You may call `.resolve` and `.reject` on this field as you may a standard ES6 Promise. After resolving / rejecting
|
||||
* deferredPromise.promise, it is recommended you reset it by calling {@link GoCloudStorage.makeDeferredPromise}. Example:
|
||||
*
|
||||
* ```js
|
||||
*
|
||||
* // function a returns the "promise" field of deferredPromise
|
||||
* function a () {
|
||||
* return gcs.deferredPromise.promise
|
||||
* }
|
||||
*
|
||||
* // function b resolves the "promise" field of deferredPromise and resets it
|
||||
* function b () {
|
||||
* gcs.deferredPromise.promise.resolve("Promise resolved"); // resolve
|
||||
* gcs.deferredPromise.promise = gcs.makeDeferredPromise(); // reset
|
||||
* }
|
||||
*
|
||||
* a(); // return deferredPromise.promise
|
||||
* b(); // b is called after a (and before anything else can resolve deferredPromise.promise), so b resolves deferredPromise.promise
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export interface DeferredPromise {
|
||||
promise: any; // this really isn't "any", it's an ES6 Promise, but declaring it as Promise<any> removes .resolve() functionality???
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract class for storing GoJS {@link Model}s in various cloud storage services.
|
||||
*
|
||||
* GoCloudStorage is never used on its own. Its subclasses can be used to manage diagram model storage programatically,
|
||||
* or any subset of GoCloudStorage subclasses can be bundled and used graphically with the {@link GoCloudStorageManager}.
|
||||
*
|
||||
* **Note**: With the exception of {@link GoLocalStorage}, all GoCloudStorage subclasses must be used in pages served
|
||||
* on a web server.
|
||||
* @category Storage
|
||||
*/
|
||||
export class GoCloudStorage {
|
||||
|
||||
private _managedDiagrams: Array<go.Diagram>;
|
||||
private _clientId: string;
|
||||
private _isAutoSaving: boolean;
|
||||
private _currentDiagramFile: DiagramFile;
|
||||
private _ui: HTMLElement;
|
||||
private _defaultModel: string;
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
*/
|
||||
protected _serviceName: string;
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
*/
|
||||
protected _className: string;
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
*/
|
||||
protected _deferredPromise: DeferredPromise;
|
||||
private _iconsRelativeDirectory: string;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {go.Diagram|go.Diagram[]} managedDiagrams An array of GoJS {@link Diagram}s whose model(s) will be saved to
|
||||
* / loaded from a cloud storage service. Can also be a single Diagram.
|
||||
* @param {string} clientId The client ID of the cloud storage application to use (given by the cloud storage service to developer).
|
||||
* Not needed for all subclasses.
|
||||
* @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 GoCloudStorage exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
*/
|
||||
constructor(managedDiagrams: go.Diagram | Array<go.Diagram>, defaultModel?: string, clientId?: string, iconsRelativeDirectory?: string) {
|
||||
if (managedDiagrams instanceof go.Diagram) managedDiagrams = [managedDiagrams];
|
||||
this._managedDiagrams = managedDiagrams;
|
||||
this._currentDiagramFile = { name: null, id: null, path: null };
|
||||
this._isAutoSaving = true;
|
||||
if (clientId) this._clientId = clientId;
|
||||
else clientId = null;
|
||||
|
||||
// if defaultModel does not begin with "{", try using that as the iconsRelativeDirectory
|
||||
if (defaultModel) {
|
||||
const firstChar: string = defaultModel.trim().charAt(0);
|
||||
if (defaultModel && firstChar === '{') this._defaultModel = defaultModel;
|
||||
if (firstChar !== '{' && !iconsRelativeDirectory) this._iconsRelativeDirectory = defaultModel;
|
||||
else this._iconsRelativeDirectory = (!!iconsRelativeDirectory) ? iconsRelativeDirectory : '../goCloudStorageIcons/';
|
||||
} else {
|
||||
this._defaultModel = null;
|
||||
this._iconsRelativeDirectory = (!!iconsRelativeDirectory) ? iconsRelativeDirectory : '../goCloudStorageIcons/';
|
||||
}
|
||||
|
||||
// make sure iconsRelativeDirectory has a trailing '/'
|
||||
const lastChar = this._iconsRelativeDirectory.charAt(this._iconsRelativeDirectory.length - 1);
|
||||
if (lastChar !== '/') {
|
||||
this._iconsRelativeDirectory += '/';
|
||||
}
|
||||
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'goCustomFilepicker';
|
||||
menu.style.visibility = 'hidden';
|
||||
|
||||
// TODO -- this assumes the document has a body element, is this OK??
|
||||
document.getElementsByTagName('body')[0].appendChild(menu);
|
||||
this._ui = menu;
|
||||
this._deferredPromise = { promise: this.makeDeferredPromise() };
|
||||
|
||||
// enable autosaving capability
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
function addAutoSave(d: go.Diagram) {
|
||||
d.addModelChangedListener(function(e: go.ChangedEvent) {
|
||||
if (e.isTransactionFinished && storage.isAutoSaving && e.oldValue !== '') {
|
||||
if (storage.currentDiagramFile.name) {
|
||||
storage.save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const d = this.managedDiagrams;
|
||||
const storage = this;
|
||||
if (d instanceof go.Diagram) {
|
||||
addAutoSave(d);
|
||||
} else {
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
addAutoSave(d[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get / set the GoJS {@link Diagram}s associated with this instance of GoCloudStorage.
|
||||
* Set with a parameter during construction.
|
||||
* @function.
|
||||
* @return {go.Diagram[]}
|
||||
*/
|
||||
get managedDiagrams(): Array<go.Diagram> { return this._managedDiagrams; }
|
||||
set managedDiagrams(value: Array<go.Diagram>) { this._managedDiagrams = value; }
|
||||
|
||||
/**
|
||||
* Get / set the defaultModel data for the app used by an instance of GoCloudStorage.
|
||||
* defaultModel is used when creating new diagrams. See {@link #create}.
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get defaultModel(): string { return this._defaultModel; }
|
||||
set defaultModel(value: string) { this._defaultModel = value; }
|
||||
|
||||
/**
|
||||
* Get / set iconsRelativeDirectory, the directory path relative to the page in which this instance of GoCloudStorage exists,
|
||||
* in which the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get iconsRelativeDirectory(): string { return this._iconsRelativeDirectory; }
|
||||
set iconsRelativeDirectory(value: string) { this._iconsRelativeDirectory = value; }
|
||||
|
||||
/**
|
||||
* Get the clientId for the app using the cloud storage service. This is usually given by the cloud storage provider's dev console or similar.
|
||||
* Set with a parameter during construction.
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get clientId(): string { return this._clientId; }
|
||||
|
||||
/**
|
||||
* Get or set the currently open {@link DiagramFile}. By default, currentDiagramFile is set when a file is
|
||||
* loaded from storage, saved to storage (if saved to a different path from the currentDiagramFile.path), or
|
||||
* deleted from storage (if the deleted file is the currently open one).
|
||||
* The default value is a {@link DiagramFile} with null id, name, and path values.
|
||||
* @function.
|
||||
* @return {Object}
|
||||
*/
|
||||
get currentDiagramFile(): DiagramFile { return this._currentDiagramFile; }
|
||||
set currentDiagramFile(value: DiagramFile) { this._currentDiagramFile = value; }
|
||||
|
||||
/**
|
||||
* Get or set isAutoSaving property. If true, the {@link #managedDiagrams} will be saved to storage after every
|
||||
* {@link Transaction} (only if {@link #currentDiagramFile} holds a non-null path value).
|
||||
* Additionally, if isAutoSaving is true, users will be prompted to save newly created
|
||||
* diagrams when created with {@link #create}.
|
||||
* The default value for isAutoSaving is `true`.
|
||||
* @function.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isAutoSaving(): boolean { return this._isAutoSaving; }
|
||||
set isAutoSaving(value: boolean) { this._isAutoSaving = value; }
|
||||
|
||||
/**
|
||||
* Get the name of the cloud storage service being used; i.e. "Dropbox"
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get serviceName(): string { return this._serviceName; }
|
||||
|
||||
/**
|
||||
* Get the name of the class; i.e. "GoDropbox"
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get className(): string { return this._className; }
|
||||
|
||||
/**
|
||||
* Get the UI element associated with this instance of GoCloudStorage. This is a custom filepicker window for {@link GoDropBox} and
|
||||
* {@link GoLocalStorage}. It is a save prompt for {@link GoOneDrive} and {@link GoGoogleDrive} (both these classes use third party
|
||||
* UI for storage navigation, provided by Microsoft and Google, respectively). The UI element is created during construction.
|
||||
* @function.
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
get ui(): HTMLElement { return this._ui; }
|
||||
|
||||
/**
|
||||
* Explicitly authorize a currently-signed in user of the storage service to use the application associated with this
|
||||
* instance of GoCloudStorage (via {@link #clientId}. If no currently signed-in user exists, prompt user to sign into their account, then authorize that account.
|
||||
*
|
||||
* **Note:** Authorization does not occur (and is not possible or necessary) with {@link GoLocalStorage}. Instead,
|
||||
* {@link GoLocalStorage#authorize} ensures localStorage exists in the browser.
|
||||
* @param {boolean} refreshToken Whether to get a new access token (true) or try to find / use an existing one. Exact behavior varies
|
||||
* from subclass to subclass. See:
|
||||
* - {@link GoLocalStorage#authorize}
|
||||
* - {@link GoDropBox#authorize}
|
||||
* - {@link GoGoogleDrive#authorize}
|
||||
* - {@link GoOneDrive#authorize}
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a boolean stating whether authorization was succesful (true) or failed (false).
|
||||
*/
|
||||
public authorize(refreshToken: boolean = false): Promise<any> {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('authorize not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* Returns a new {@link DeferredPromise}. Use this method to reset {@link #deferredPromise}.promise property
|
||||
* after deferredPromise.promise has been resolved. For example:
|
||||
*
|
||||
* ```js
|
||||
*
|
||||
* // function a returns the "promise" field of deferredPromise
|
||||
* function a () {
|
||||
* return gcs.deferredPromise.promise
|
||||
* }
|
||||
*
|
||||
* // function b resolves the "promise" field of deferredPromise and resets it
|
||||
* function b () {
|
||||
* gcs.deferredPromise.promise.resolve("Promise resolved"); // resolve
|
||||
* gcs.deferredPromise.promise = gcs.makeDeferredPromise(); // reset
|
||||
* }
|
||||
*
|
||||
* a(); // return deferredPromise.promise
|
||||
* b(); // b is called after a (and before anything else can resolve deferredPromise.promise), so b resolves deferredPromise.promise
|
||||
*
|
||||
* ```
|
||||
* @return {Promise<Object>}
|
||||
*/
|
||||
public makeDeferredPromise(): Promise<Object> {
|
||||
let res: Function; let rej: Function;
|
||||
const promise: any = new Promise((resolve: Function, reject: Function) => {
|
||||
res = resolve;
|
||||
rej = reject;
|
||||
});
|
||||
promise.resolve = res;
|
||||
promise.reject = rej;
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the currently logged in user. This information varies from subclass to subclass. For more info, see:
|
||||
* - {@link GoDropBox#getUserInfo}
|
||||
* - {@link GoGoogleDrive#getUserInfo}
|
||||
* - {@link GoOneDrive#getUserInfo}
|
||||
* @return {Promise<any>} Returns a Promise that resolves with information about the currently logged in user
|
||||
*/
|
||||
public getUserInfo() {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('getUserInfo not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the {@link #ui} element associated with this instance of GoCloudStorage. Used in some UI element onclicks.
|
||||
* @param {boolean} isActionCanceled If action (save, delete, load) is canceled, resolve Promise (returned previously in
|
||||
* {@link #showUI}) with a 'Canceled' notification. Default value is false.
|
||||
*/
|
||||
public hideUI(isActionCanceled: boolean = false) {
|
||||
const storage = this;
|
||||
storage.ui.style.visibility = 'hidden';
|
||||
if (isActionCanceled) {
|
||||
const action: string = document.getElementById('actionButton').innerHTML;
|
||||
storage._deferredPromise.promise.resolve(action + ' canceled by user');
|
||||
storage._deferredPromise.promise = storage.makeDeferredPromise();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file exists at a given path.
|
||||
* @param {string} path A valid filepath. What is meant by this varies from subclass to subclass. Rules for valid filepaths by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs. Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @return {Promise<boolean>} Returns a Promise that resolves with a boolean stating whether a file exists at a given path.
|
||||
*/
|
||||
public checkFileExists(path: string) {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('checkFileExists not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file at a given path in storage. The exact file data given varies from storage service
|
||||
* to storage service, though all include name, path, id data. The exact syntax of these data fields may vary.
|
||||
* See subclass-specific documentation for more details.
|
||||
* - {@link GoLocalStorage#getFile}
|
||||
* - {@link GoDropBox#getFile}
|
||||
* - {@link GoGoogleDrive#getFile}
|
||||
* - {@link GoOneDrive#getFile}
|
||||
* @param {string} path A valid filepath. What is meant by this varies from subclass to subclass. Rules for valid filepaths by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs. Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @return {Promise<any>} Return a Promise that resolves with the file at a given path in storage(or null).
|
||||
*/
|
||||
public getFile(path: string) {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
throw Error('getFile not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the {@link #ui} element associated with this instance of GoCloudStorage.
|
||||
* @param {string} action Clarify what action is being done after file selection. Acceptable values: Save, Delete, Load
|
||||
* @return {Promise} Returns a Promise that resolves (in {@link #save}, {@link #load}, or {@link #remove} with an {@link DiagramFile}
|
||||
* representing the saved/loaded/deleted file
|
||||
*/
|
||||
public showUI(action: string) {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
throw Error('showUI not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set each of {@link #managedDiagrams}' .model to {@link #defaultModel} (if defaultModel is null,
|
||||
* each model will be set to a new {@link GraphLinksModel} by default).
|
||||
* If {@link #isAutoSaving} is true and no path parameter is supplied, users will be immediately prompted to save their new diagrams to cloud storage.
|
||||
* @param {string} path Optional. If a valid filepath is supplied, save each of {@link #managedDiagrams}' model data to this path. No UI of any sort appears.
|
||||
* What is meant by "valid filepath" varies from subclass to subclass. Rules for valid filepaths by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs. Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @param {boolean} saveBefore Optional. If true, the user will be prompted to save their current diagram(s) before GoCloudStorage makes a new one.
|
||||
* The default value is false.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the newly created file.
|
||||
*/
|
||||
public create(path?: string, saveBefore: boolean = false) {
|
||||
const storage = this;
|
||||
function makeNewDiagram(d: go.Diagram) {
|
||||
if (storage.defaultModel) d.model = go.Model.fromJson(JSON.parse(storage.defaultModel));
|
||||
else d.model = new go.GraphLinksModel();
|
||||
}
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
// TODO -- offer the chance for user to save their current diagram
|
||||
if (saveBefore) {
|
||||
storage.promptUserToSaveBeforeNew().then(function(resp) {
|
||||
if (resp) {
|
||||
storage.saveWithUI().then(function(diagramFile) {
|
||||
storage.currentDiagramFile = { name: null, id: null, path: null };
|
||||
if (storage.managedDiagrams instanceof go.Diagram) {
|
||||
makeNewDiagram(storage.managedDiagrams);
|
||||
} else {
|
||||
for (let i = 0; i < storage.managedDiagrams.length; i++) {
|
||||
makeNewDiagram(storage.managedDiagrams[i]);
|
||||
}
|
||||
}
|
||||
if (storage.isAutoSaving) {
|
||||
if (path) {
|
||||
resolve(storage.save(path));
|
||||
} else resolve(storage.saveWithUI());
|
||||
} else resolve('New diagram created.'); // no prompt to save
|
||||
});
|
||||
} else {
|
||||
storage.currentDiagramFile = { name: null, id: null, path: null };
|
||||
if (storage.managedDiagrams instanceof go.Diagram) {
|
||||
makeNewDiagram(storage.managedDiagrams);
|
||||
} else {
|
||||
for (let i = 0; i < storage.managedDiagrams.length; i++) {
|
||||
makeNewDiagram(storage.managedDiagrams[i]);
|
||||
}
|
||||
}
|
||||
if (storage.isAutoSaving) {
|
||||
if (path) {
|
||||
resolve(storage.save(path));
|
||||
} else resolve(storage.saveWithUI());
|
||||
} else resolve('New diagram created.'); // no prompt to save
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!saveBefore) {
|
||||
storage.currentDiagramFile = { name: null, id: null, path: null };
|
||||
if (storage.managedDiagrams instanceof go.Diagram) {
|
||||
makeNewDiagram(storage.managedDiagrams);
|
||||
} else {
|
||||
for (let i = 0; i < storage.managedDiagrams.length; i++) {
|
||||
makeNewDiagram(storage.managedDiagrams[i]);
|
||||
}
|
||||
}
|
||||
if (storage.isAutoSaving) {
|
||||
if (path) {
|
||||
resolve(storage.save(path));
|
||||
} else resolve(storage.saveWithUI());
|
||||
} else resolve('New diagram created.'); // no prompt to save
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private promptUserToSaveBeforeNew() {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
// Remove any prior window like this
|
||||
const d = document.getElementById('gcs-save-before-new');
|
||||
if (d) {
|
||||
document.body.removeChild(d);
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.id = 'gcs-save-before-new';
|
||||
const p = document.createElement('p');
|
||||
p.innerText = 'Save current diagram(s) before creating a new file?';
|
||||
|
||||
const yb = document.createElement('button');
|
||||
yb.innerText = 'Yes';
|
||||
const nb = document.createElement('button');
|
||||
nb.innerText = 'No';
|
||||
|
||||
yb.onclick = function() {
|
||||
document.body.removeChild(div);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
nb.onclick = function() {
|
||||
document.body.removeChild(div);
|
||||
resolve(false);
|
||||
};
|
||||
nb.style['float'] = 'right';
|
||||
|
||||
div.style['font-family'] = 'Arial, Helvetica, sans-serif';
|
||||
div.style['width'] = '400px';
|
||||
div.style['top'] = '25%';
|
||||
div.style['left'] = '40%';
|
||||
div.style['position'] = 'absolute';
|
||||
div.style['border'] = '1px solid black';
|
||||
div.style['padding'] = '10px';
|
||||
div.style['box-shadow'] = '10px 10px 5px #888888';
|
||||
div.style['background'] = 'white';
|
||||
div.style['z-index'] = '100';
|
||||
|
||||
div.appendChild(p);
|
||||
div.appendChild(yb);
|
||||
div.appendChild(nb);
|
||||
document.body.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* Returns the data to save to storage. This is a string representation of a JSON-like object.
|
||||
* Keys are the div IDs of the diagrams being saved. Values are the model.toJson() values for those diagrams.
|
||||
* @return {string}
|
||||
*/
|
||||
protected makeSaveFile() {
|
||||
let item: string = '{\n';
|
||||
const storage = this;
|
||||
if (storage.managedDiagrams.length === 0) return;
|
||||
for (let i = 0; i < storage.managedDiagrams.length; i++) {
|
||||
const diagram: go.Diagram = storage.managedDiagrams[i];
|
||||
const div: string = diagram.div.id;
|
||||
const _model: string = diagram.model.toJson();
|
||||
item += '"' + div + '"' + ': ' + diagram.model.toJson();
|
||||
if (i + 1 !== storage.managedDiagrams.length) item += ',\n';
|
||||
}
|
||||
|
||||
item += '\n}';
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* Loads all models in a saved file to their respective diagrams
|
||||
*/
|
||||
protected loadFromFileContents(fileContents: string) {
|
||||
const models = JSON.parse(fileContents);
|
||||
for (const divId in models) {
|
||||
const model = models[divId];
|
||||
const div: HTMLDivElement = document.getElementById(divId) as HTMLDivElement;
|
||||
const diagram: any = go.Diagram.fromDiv(div);
|
||||
if (diagram) {
|
||||
diagram.model = go.Model.fromJson(JSON.stringify(model));
|
||||
} else {
|
||||
throw Error('No Diagram on page is associated with a div with id ' + divId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current diagram's model data to cloud storage with the {@link #ui} for this class.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file.
|
||||
*/
|
||||
public saveWithUI() {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('saveWithUI not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save {@link #managedDiagrams}' model data to storage. 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 storage corresponding to currentDiagramFile.path with current managedDiagrams' model data.
|
||||
*
|
||||
* Rules for valid filepaths by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs. Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public save(path?: string) {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('save not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load diagram model data from a given cloud storage-specific file path into {@link #managedDiagrams}.
|
||||
* @param {string} path A valid filepath. What is meant by this varies from subclass to subclass. Rules for valid filepaths by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs. Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile}
|
||||
*/
|
||||
public load(path: string) {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('load not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load diagram model data from cloud storage into {@link #managedDiagrams} using the {@link #ui} for this class.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public loadWithUI() {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('loadWithUI not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file containing diagram model data at a given cloud storage-specific file path.
|
||||
* @param {string} path A valid filepath. What is meant by this varies from subclass to subclass. Rules for valid filepaths by subclass:
|
||||
* - {@link GoLocalStorage}: Just the filename (the key in local storage); i.e. `example.diagram`
|
||||
* - {@link GoDropBox}: `/{path-to-file}/{filename}`; i.e. `/Public/example.diagram`
|
||||
* - {@link GoGoogleDrive}: Use Google Drive-given file IDs. Parameter is still called 'path' in GoGoogleDrive methods to preserve system nomenclature.
|
||||
* - {@link GoOneDrive}: `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file.
|
||||
*/
|
||||
public remove(path: string) {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('remove not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a given diagram from cloud storage using the {@link #ui} for this class.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public removeWithUI() {
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
reject('removeWithUI not implemented');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Go Cloud Storage Manager
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
import * as gcs from './GoCloudStorage.js';
|
||||
|
||||
/**
|
||||
* Class for easily saving / loading GoJS {@link Model}s to / from a user-defined set of Cloud Storage Services with a pre-defined UI.
|
||||
*
|
||||
* GoCloudStorageManager holds a set of {@link GoCloudStorage} subclass instances ({@link #storages}) to manage. When one is selected from the
|
||||
* storage selection {@link #menu}, it becomes the {@link #currentStorage} property, which is used to save / load / delete / create files.
|
||||
* @category Storage
|
||||
*/
|
||||
export class GoCloudStorageManager {
|
||||
|
||||
private _storages: go.Set<gcs.GoCloudStorage>;
|
||||
private _currentStorage: gcs.GoCloudStorage;
|
||||
private _menu: HTMLElement;
|
||||
private _deferredPromise: gcs.DeferredPromise;
|
||||
private _iconsRelativeDirectory: string;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {go.Set<gcs.GoCloudStorage>} storages Contains valid instances of {@link GoCloudStorage} subclasses. Use at most one instance of each subclass.
|
||||
* @param {string} iconsRelativeDirectory The directory path relative to the page in which this instance of GoCloudStorageManager exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
* **Note:** If this parameter is supplied, it is used as for the "iconsRelativeDirectory" constructor parameter for each instance
|
||||
* this instance of GoCloudStorageManager manages in {@link #storages}.
|
||||
*/
|
||||
constructor(storages: go.Set<gcs.GoCloudStorage> | Array<gcs.GoCloudStorage>, iconsRelativeDirectory?: string) {
|
||||
if (storages instanceof Array) {
|
||||
const storagesSet = new go.Set<gcs.GoCloudStorage>();
|
||||
for (let i = 0; i < storages.length; i++) {
|
||||
if (!(storages[i] instanceof gcs.GoCloudStorage)) {
|
||||
throw new Error("Cannot create GoCloudStorageManager; provided 'storages' parameter elements are not all of type GoCloudStorage");
|
||||
} else {
|
||||
storagesSet.add(storages[i]);
|
||||
}
|
||||
}
|
||||
storages = storagesSet;
|
||||
}
|
||||
if (!(storages instanceof go.Set) || !storages) throw Error("Cannot create GoCloudStorageManager with provided 'storages' parameter");
|
||||
const storageManager = this;
|
||||
storageManager._storages = storages;
|
||||
storageManager._currentStorage = storages.first();
|
||||
const menu = document.createElement('div');
|
||||
menu.id = 'goCloudStorageManagerMenu';
|
||||
storageManager._menu = menu;
|
||||
storageManager._deferredPromise = { promise: gcs.GoCloudStorage.prototype.makeDeferredPromise() };
|
||||
storageManager._iconsRelativeDirectory = (!!iconsRelativeDirectory) ? iconsRelativeDirectory : '../goCloudStorageIcons/';
|
||||
if (iconsRelativeDirectory) {
|
||||
storageManager._storages.iterator.each(function(storage) {
|
||||
storage.iconsRelativeDirectory = iconsRelativeDirectory;
|
||||
});
|
||||
}
|
||||
// if href includes a certain string, we just authenticated DropBox, so use GoDropBox as "currentStorage"
|
||||
if (window.location.href.indexOf('account_id=dbid') !== -1) {
|
||||
storages.iterator.each(function(storage) {
|
||||
if (storage.className === 'GoDropBox') {
|
||||
storageManager._currentStorage = storage;
|
||||
// this will force a load of the diagram data that existed before the Dropbox auth flow
|
||||
storageManager.currentStorage.authorize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// append the menu ui to the document
|
||||
document.getElementsByTagName('body')[0].appendChild(storageManager.menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storages ({@link GoCloudStorage} subclass instances) managed by an instance of GoCloudStorageManager. At most, there should be only one instance of each subclass.
|
||||
* This is set with a parameter during construction.
|
||||
* @function.
|
||||
* @return {go.Set<gcs.GoCloudStorage>}
|
||||
*/
|
||||
get storages(): go.Set<gcs.GoCloudStorage> { return this._storages; }
|
||||
|
||||
/**
|
||||
* Get / set iconsRelativeDirectory, the directory path relative to the page in which this instance of GoCloudStorageManager exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get iconsRelativeDirectory(): string { return this._iconsRelativeDirectory; }
|
||||
set iconsRelativeDirectory(value: string) { this._iconsRelativeDirectory = value; }
|
||||
|
||||
/**
|
||||
* Get GoCloudStorageManager menu, from which a user chooses which storage service for this instance of GoCloudStorageManager to actively manage (see {@link #currentStorage}).
|
||||
* This is created (as a blank div) during construction. Its contents are populated during {@link #selectStorageService}.
|
||||
* @function.
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
get menu(): HTMLElement { return this._menu; }
|
||||
|
||||
/**
|
||||
* Get / set the {@link GoCloudStorage} subclass this instance of GoCloudStorageManager is actively managing.
|
||||
* @function.
|
||||
* @return {gcs.GoCloudStorage}
|
||||
*/
|
||||
get currentStorage() { return this._currentStorage; }
|
||||
set currentStorage(value: gcs.GoCloudStorage) { this._currentStorage = value; }
|
||||
|
||||
/**
|
||||
* Creates a new diagram with {@link #currentStorage}'s default model data (see {@link GoCloudStorage#defaultModel}.
|
||||
* If currentStorage.isAutoSaving is true, prompt to save it to to currentStorage's storage service.
|
||||
* if {@link #currentStorage}'s {@link GoCloudStorage#isAutoSaving} is true).
|
||||
* @param {boolean} saveBeforeCreate Whether or not to prompt the user to save their current work before creating a new diagram.
|
||||
* See more at {@link GoCloudStorage#create}. Default value is false.
|
||||
* @return {Promise} Returns a Promise that resolves a {@link DiagramFile} representing the newly created file (if file was saved).
|
||||
*/
|
||||
public create(saveBeforeCreate: boolean = false) {
|
||||
const storageManager = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storageManager.handleAction('Create', saveBeforeCreate));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the load interface for {@link #currentStorage}.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file.
|
||||
*/
|
||||
public load() {
|
||||
const storageManager = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storageManager.handleAction('Load'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the remove interface for {@link #currentStorage}.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file.
|
||||
*/
|
||||
public remove() {
|
||||
const storageManager = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storageManager.handleAction('Remove'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Either launches the save interface for {@link #currentStorage} or just saves the {@link GoCloudStorage#managedDiagrams}' model data to
|
||||
* storage at the path supplied in currentStorage's {@link GoCloudStorage#currentDiagramFile}.path value, depending on a parameter.
|
||||
* @param {boolean} isSaveAs If true, show the save interface for currentStorage. If false, save currentStorage's managedDiagrams' model data to storage.
|
||||
* Default value is true.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file.
|
||||
*/
|
||||
public save(isSaveAs: boolean = true) {
|
||||
const storageManager = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (isSaveAs) resolve(storageManager.handleAction('SaveAs'));
|
||||
else resolve(storageManager.handleAction('Save'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a message on the screen for a given number of seconds. Can be used for a variety of purposes, but a common one is to
|
||||
* notify users when a file has been loaded / saved / deleted / created by handling the {@link DiagramFile} argument in the
|
||||
* "then" function of returned Promises (from functions {@link #load}, {@link #create}, {@link #save},
|
||||
* {@link #remove}) by displaying it as a message.
|
||||
* @param msg Message to display
|
||||
* @param seconds Number of seconds to display the message for. If no value is provided, the message will display for two seconds.
|
||||
*/
|
||||
public showMessage(msg: string, seconds?: number) {
|
||||
if (!seconds) seconds = 2;
|
||||
const messageBox = document.createElement('div');
|
||||
messageBox.id = 'goCloudStorageManagerMessageBox';
|
||||
messageBox.innerHTML = '<p>' + msg + '</p>';
|
||||
document.body.appendChild(messageBox);
|
||||
setTimeout(function() {
|
||||
messageBox.style.opacity = '0';
|
||||
setTimeout(function() { messageBox.parentNode.removeChild(messageBox); }, 1000);
|
||||
}, 1000 * seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the icon for a given {@link GoCloudStorage#className}
|
||||
* @param className
|
||||
*/
|
||||
public getStorageIconPath(className: string) {
|
||||
const storageManager = this;
|
||||
if (storageManager.iconsRelativeDirectory == null || storageManager.iconsRelativeDirectory === undefined) return null;
|
||||
let src: string = storageManager.iconsRelativeDirectory;
|
||||
switch (className) {
|
||||
case 'GoGoogleDrive': {
|
||||
src += 'googleDrive.jpg';
|
||||
break;
|
||||
}
|
||||
case 'GoOneDrive': {
|
||||
src += 'oneDrive.png';
|
||||
break;
|
||||
}
|
||||
case 'GoLocalStorage': {
|
||||
src += 'localStorage.png';
|
||||
break;
|
||||
}
|
||||
case 'GoDropBox': {
|
||||
src += 'dropBox.png';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display options ({@link #storages}) supported by this instance of GoCloudStorageManager.
|
||||
* Sets {@link #currentStorage} to user's choice.
|
||||
* @return {Promise} Returns a Promise that resolves with the new {@link #currentStorage} instance
|
||||
*/
|
||||
public selectStorageService() {
|
||||
const storageManager = this;
|
||||
const storages = this.storages;
|
||||
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const menu = storageManager.menu;
|
||||
const title: string = 'Select Storage Service';
|
||||
menu.innerHTML = '<strong>' + title + '</strong><hr></hr>';
|
||||
|
||||
const selectedStorage: HTMLElement = document.createElement('p');
|
||||
selectedStorage.id = 'gcsmSelectedStorage'; // Go Cloud Storage Manager selected storage
|
||||
selectedStorage.innerHTML = storageManager.currentStorage.serviceName;
|
||||
menu.appendChild(selectedStorage);
|
||||
|
||||
// display the name of the currently selected radio button's storage service
|
||||
menu.onchange = function() {
|
||||
const radios: NodeListOf<HTMLInputElement> = document.getElementsByName('storageSelection') as NodeListOf<HTMLInputElement>;
|
||||
let selectedStorageClassName: string = null;
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) {
|
||||
selectedStorageClassName = radios[i].id;
|
||||
}
|
||||
}
|
||||
let serviceNameStr: string = '';
|
||||
storages.iterator.each(function(s) {
|
||||
if (s.className === selectedStorageClassName) {
|
||||
serviceNameStr = s.serviceName;
|
||||
}
|
||||
});
|
||||
document.getElementById('gcsmSelectedStorage').innerHTML = serviceNameStr;
|
||||
};
|
||||
|
||||
// document.getElementsByTagName('body')[0].appendChild(storageManager.menu);
|
||||
storageManager.menu.style.visibility = 'visible';
|
||||
const optionsDiv: HTMLElement = document.createElement('div');
|
||||
optionsDiv.id = 'storageOptions';
|
||||
|
||||
const it = storages.iterator;
|
||||
it.each(function(storage) {
|
||||
// create a radio input box for each service managed by this instace of GoCloudStorageManager
|
||||
const type: string = storage.className;
|
||||
const src: string = storageManager.getStorageIconPath(type);
|
||||
const isChecked: boolean = storage.className === storageManager.currentStorage.className;
|
||||
let checkedStr: string = '';
|
||||
if (isChecked) checkedStr = 'checked';
|
||||
|
||||
optionsDiv.innerHTML +=
|
||||
'<label>' +
|
||||
'<input id=' + type + " type='radio' name='storageSelection' " + checkedStr + ' />' +
|
||||
"<img class='storageLogo' src=" + src + ' >';
|
||||
});
|
||||
|
||||
menu.appendChild(optionsDiv);
|
||||
|
||||
// tslint:disable-next-line:max-line-length
|
||||
const description: string = 'This will be where you save / load diagram model data to / from. You will need to grant GoCloudStorage permission to access your files on the selected storage service.';
|
||||
menu.innerHTML += "<p class='description'>" + description + '</p>';
|
||||
|
||||
const submitDiv: HTMLElement = document.createElement('div');
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.id = 'actionButton';
|
||||
actionButton.textContent = 'Select';
|
||||
actionButton.onclick = function() {
|
||||
// set currentStorage
|
||||
const radios: NodeListOf<HTMLInputElement> = document.getElementsByName('storageSelection') as NodeListOf<HTMLInputElement>;
|
||||
// tslint:disable-next-line:no-shadowed-variable
|
||||
let selectedStorage: string = null;
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) {
|
||||
selectedStorage = radios[i].id;
|
||||
}
|
||||
}
|
||||
storageManager.storages.each(function(storage) {
|
||||
if (storage.className === selectedStorage) storageManager.currentStorage = storage;
|
||||
});
|
||||
if (storageManager.currentStorageNeedsAuth()) {
|
||||
storageManager.currentStorage.authorize().then(function(resp) {
|
||||
});
|
||||
}
|
||||
resolve(storageManager.currentStorage);
|
||||
storageManager.hideMenu();
|
||||
};
|
||||
submitDiv.appendChild(actionButton);
|
||||
menu.appendChild(submitDiv);
|
||||
|
||||
const cancelDiv: HTMLElement = document.createElement('div');
|
||||
const cancelButton = document.createElement('button');
|
||||
cancelButton.id = 'cancelButton';
|
||||
cancelButton.textContent = 'Cancel';
|
||||
cancelButton.onclick = function() {
|
||||
storageManager.hideMenu();
|
||||
};
|
||||
cancelDiv.appendChild(cancelButton);
|
||||
menu.appendChild(cancelDiv);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the storage selection {@link #menu}
|
||||
*/
|
||||
public hideMenu() {
|
||||
const storageManager = this;
|
||||
storageManager.menu.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Some classes need to be explicitly authorized (get a user-specific auth token) for use with GoCloudStorageManager. Some do not.
|
||||
* This function simply examines the currently active storage and determines whether or not this explicit authorization is needed.
|
||||
* @return {boolean}
|
||||
*/
|
||||
private currentStorageNeedsAuth() {
|
||||
const storageManager = this;
|
||||
const currentStorageClass: string = storageManager.currentStorage.className;
|
||||
if (currentStorageClass === 'GoGoogleDrive' || currentStorageClass === 'GoDropBox') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an action with the current {@link #currentStorage}. Possible values for `action` can be:
|
||||
* - Load
|
||||
* - Save
|
||||
* - Save As
|
||||
* - Create
|
||||
* - Remove
|
||||
* @param action
|
||||
* @param saveBeforeCreate Whether or not to prompt the user to save their current work before creating a new file.
|
||||
* Default value is false. See more at {@link GoCloudStorage#create}
|
||||
*/
|
||||
public handleAction(action: string, saveBeforeCreate: boolean = false) {
|
||||
const storageManager = this;
|
||||
const storage: gcs.GoCloudStorage = storageManager.currentStorage;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
function doAction() {
|
||||
switch (action) {
|
||||
case 'Load': {
|
||||
resolve(storage.loadWithUI());
|
||||
break;
|
||||
}
|
||||
case 'SaveAs': {
|
||||
resolve(storage.saveWithUI());
|
||||
break;
|
||||
}
|
||||
case 'Save': {
|
||||
resolve(storage.save());
|
||||
break;
|
||||
}
|
||||
case 'Remove': {
|
||||
resolve(storage.removeWithUI());
|
||||
break;
|
||||
}
|
||||
case 'Create': {
|
||||
resolve(storage.create(null, saveBeforeCreate));
|
||||
break;
|
||||
}
|
||||
}
|
||||
storageManager.hideMenu();
|
||||
}
|
||||
if (storageManager.currentStorageNeedsAuth()) {
|
||||
storage.authorize().then(function() {
|
||||
doAction();
|
||||
});
|
||||
} else doAction();
|
||||
});
|
||||
}
|
||||
}
|
||||
+672
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Go DropBox
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
import * as gcs from './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
|
||||
*/
|
||||
export class GoDropBox extends gcs.GoCloudStorage {
|
||||
|
||||
private _dropbox: any;
|
||||
private _menuPath: string;
|
||||
private _options: any;
|
||||
|
||||
/**
|
||||
* @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/".
|
||||
*/
|
||||
constructor(managedDiagrams: go.Diagram | Array<go.Diagram>, clientId: string, defaultModel?: string, iconsRelativeDirectory?: string) {
|
||||
super(managedDiagrams, defaultModel, clientId, iconsRelativeDirectory);
|
||||
if (window['Dropbox']) {
|
||||
const 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', // or "direct"
|
||||
|
||||
// Optional. A value of false (default) limits selection to a single file, while
|
||||
// true enables multiple file selection.
|
||||
multiselect: false, // or true
|
||||
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 dropbox(): any { return this._dropbox; }
|
||||
|
||||
/**
|
||||
* 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 menuPath(): string { return this._menuPath; }
|
||||
set menuPath(value: string) { this._menuPath = value; }
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
public authorize(refreshToken: boolean = false) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
// 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
|
||||
const authUrl: string = 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
|
||||
const authUrl: string = 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);
|
||||
});
|
||||
}
|
||||
|
||||
private getAccessTokenFromUrl() {
|
||||
const 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
|
||||
*/
|
||||
private maybeSaveAppState() {
|
||||
const 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)
|
||||
const item: string = 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
|
||||
*/
|
||||
private maybeLoadAppState() {
|
||||
const storage = this;
|
||||
try {
|
||||
const fileContents: string = 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
|
||||
*/
|
||||
public signOut() {
|
||||
const storage = this;
|
||||
const 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.substr(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
|
||||
*/
|
||||
public getUserInfo() {
|
||||
const 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public showUI() {
|
||||
const storage = this;
|
||||
const 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
|
||||
const userInputDiv: HTMLElement = document.createElement('div');
|
||||
userInputDiv.id = 'userInputDiv';
|
||||
userInputDiv.innerHTML += '<input id="gdb-userInput" placeholder="Enter filename"></input>';
|
||||
ui.appendChild(userInputDiv);
|
||||
|
||||
const submitDiv: HTMLElement = document.createElement('div');
|
||||
submitDiv.id = 'submitDiv';
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.id = 'actionButton';
|
||||
actionButton.textContent = 'Save';
|
||||
actionButton.onclick = function() {
|
||||
const input: HTMLInputElement = (document.getElementById('gdb-userInput')) as HTMLInputElement;
|
||||
const val: string = input.value;
|
||||
if (val !== '' && val !== undefined && val != null) {
|
||||
ui.style.visibility = 'hidden';
|
||||
storage.saveWithUI(val);
|
||||
}
|
||||
};
|
||||
submitDiv.appendChild(actionButton);
|
||||
ui.appendChild(submitDiv);
|
||||
|
||||
const cancelDiv: HTMLElement = document.createElement('div');
|
||||
cancelDiv.id = 'cancelDiv';
|
||||
const 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.
|
||||
*/
|
||||
public hideUI(isActionCanceled?: boolean) {
|
||||
const storage = this;
|
||||
storage.menuPath = '';
|
||||
super.hideUI(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
|
||||
*/
|
||||
public processUIResult(action: string) {
|
||||
const storage = this;
|
||||
/**
|
||||
* Get the selected file (in menu's) Dropbox filepath
|
||||
* @return {string} The selected file's Dropbox filepath
|
||||
*/
|
||||
function getSelectedFilepath() {
|
||||
const radios = document.getElementsByName('dropBoxFile');
|
||||
let selectedFile = null;
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
if ((radios[i] as HTMLInputElement).checked) {
|
||||
selectedFile = radios[i].getAttribute('data');
|
||||
}
|
||||
}
|
||||
return selectedFile;
|
||||
}
|
||||
|
||||
const filePath: string = getSelectedFilepath();
|
||||
switch (action) {
|
||||
case 'Save': {
|
||||
if (storage.menuPath || storage.menuPath === '') {
|
||||
let name: string = (document.getElementById('userInput') as HTMLInputElement).value;
|
||||
if (name) {
|
||||
if (name.indexOf('.diagram') === -1) name += '.diagram';
|
||||
storage.save(storage.menuPath + '/' + name);
|
||||
} 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
|
||||
*/
|
||||
public checkFileExists(path: string) {
|
||||
const storage = this;
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
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
|
||||
*/
|
||||
public getFile(path: string) {
|
||||
const 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}
|
||||
*/
|
||||
public saveWithUI(filename?: string) {
|
||||
const 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 {
|
||||
const lastEight: string = filename.substring(filename.length - 8, filename.length);
|
||||
if (lastEight !== '.diagram') {
|
||||
filename += '.diagram';
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
storage._options.success = function(resp) {
|
||||
|
||||
const a = 3;
|
||||
|
||||
// find the file that was just saved
|
||||
// look at all files with "filename" in title
|
||||
// find most recent of those
|
||||
const savedFile: any = null;
|
||||
storage.dropbox.filesListFolder({
|
||||
path: '',
|
||||
recursive: true
|
||||
}).then(function(r) {
|
||||
|
||||
const files = r.entries;
|
||||
const 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)
|
||||
const latestestDate: Date = new Date(-8400000);
|
||||
let latestFile = null;
|
||||
// for (let i in files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
let 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
|
||||
const savedFile: gcs.DiagramFile = { 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) {
|
||||
const data = new Blob([text], { type: 'text/plain' });
|
||||
let uri = '';
|
||||
uri = window.URL.createObjectURL(data);
|
||||
|
||||
return uri;
|
||||
}
|
||||
const dataURI = 'data:text/html,' + encodeURIComponent(storage.makeSaveFile());
|
||||
|
||||
const 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.
|
||||
*/
|
||||
public save(path?: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (path) { // save as
|
||||
storage.dropbox.filesUpload({
|
||||
contents: storage.makeSaveFile(),
|
||||
path: path,
|
||||
autorename: true, // instead of overwriting, save to a different name (i.e. test.diagram -> test(1).diagram)
|
||||
mode: { '.tag': 'add' },
|
||||
mute: false
|
||||
}).then(function(resp) {
|
||||
const savedFile: gcs.DiagramFile = { 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) {
|
||||
const savedFile: Object = { 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.
|
||||
*/
|
||||
public loadWithUI() {
|
||||
const storage = this;
|
||||
storage._options.success = function(r) {
|
||||
const file = r[0];
|
||||
// get the file path
|
||||
storage.dropbox.filesGetMetadata({ path: file.id }).then(function(resp) {
|
||||
const path: string = resp.path_display;
|
||||
storage.load(path);
|
||||
});
|
||||
};
|
||||
|
||||
const 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
|
||||
*/
|
||||
public load(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (path) {
|
||||
storage.dropbox.filesGetTemporaryLink({ path: path }).then(function(resp) {
|
||||
const link: string = resp.link;
|
||||
storage.currentDiagramFile.name = resp.metadata.name;
|
||||
storage.currentDiagramFile.id = resp.metadata.id;
|
||||
storage.currentDiagramFile.path = path;
|
||||
const xhr: XMLHttpRequest = 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);
|
||||
|
||||
const loadedFile: gcs.DiagramFile = { 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.
|
||||
*/
|
||||
public removeWithUI() {
|
||||
const storage = this;
|
||||
storage._options.success = function(r) {
|
||||
const file = r[0];
|
||||
// get the file path
|
||||
storage.dropbox.filesGetMetadata({ path: file.id }).then(function(resp) {
|
||||
const path: string = resp.path_display;
|
||||
storage.remove(path);
|
||||
});
|
||||
};
|
||||
|
||||
const 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.
|
||||
*/
|
||||
public remove(path: string) {
|
||||
const 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 };
|
||||
const deletedFile: gcs.DiagramFile = { 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Go Google Drive
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
import * as gcs from './GoCloudStorage.js';
|
||||
|
||||
/**
|
||||
* Class for saving / loading GoJS {@link Model}s to / from Google Drive.
|
||||
* Uses the <a href="https://developers.google.com/drive/v3/reference/">Google Drive V3 API</a> by use of a
|
||||
* <a href="https://developers.google.com/api-client-library/javascript/">Google Client</a> API object.
|
||||
* 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 GoGoogleDrive must include a script tag with src set to https://apis.google.com/js/api.js.
|
||||
* @category Storage
|
||||
*/
|
||||
export class GoGoogleDrive extends gcs.GoCloudStorage {
|
||||
|
||||
private _pickerApiKey: string;
|
||||
private _oauthToken: string;
|
||||
private _scope: string;
|
||||
/**
|
||||
* Google Client object
|
||||
*/
|
||||
private _gapiClient: any;
|
||||
/**
|
||||
* Google Picker object
|
||||
*/
|
||||
private _gapiPicker: any;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {go.Diagram|go.Diagram[]} managedDiagrams An array of GoJS {@link Diagram}s whose model(s) will be saved to / loaded from Google Drive.
|
||||
* Can also be a single Diagram.
|
||||
* @param {string} clientId The client ID of the Google application linked with this instance of GoGoogleDrive (given in
|
||||
* <a href="https://console.developers.google.com">Google Developers Console</a> after registering a Google app)
|
||||
* @param {string} pickerApiKey The <a href="https://developers.google.com/picker/">Google Picker</a> API key. Once
|
||||
* <a href="https://developers.google.com/picker/docs/">obtained</a>, it can be found in the <a href="https://console.developers.google.com">Google Developers Console</a>
|
||||
* @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 GoGoogleDrive exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
*/
|
||||
constructor(managedDiagrams: go.Diagram | Array<go.Diagram>, clientId: string, pickerApiKey: string, defaultModel?: string, iconsRelativeDirectory?: string) {
|
||||
super(managedDiagrams, defaultModel, clientId, iconsRelativeDirectory);
|
||||
this._scope = 'https://www.googleapis.com/auth/drive';
|
||||
this._pickerApiKey = pickerApiKey;
|
||||
this._oauthToken = null;
|
||||
this._gapiClient = null;
|
||||
this._gapiPicker = null;
|
||||
this.ui.id = 'goGoogleDriveSavePrompt';
|
||||
this._serviceName = 'Google Drive';
|
||||
this._className = 'GoGoogleDrive';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Google Picker API key associated with this instance of GoGoogleDrive. This is set with a parameter during construction.
|
||||
* A Google Picker API key can be obtained by following the process detailed <a href="https://developers.google.com/picker/docs/">here</a>,
|
||||
* and it can be found in your <a href="https://console.developers.google.com"> Google Developers Console</a>. The pickerApiKey is used only in {@link #createPicker}.
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get pickerApiKey(): string { return this._pickerApiKey; }
|
||||
|
||||
/**
|
||||
* Get the scope for the application linked to this instance of GoGoogleDrive (via {@link #clientId}). Scope tells the
|
||||
* {@link #gapiClient} what permissions it has in making requests. Read more on scope <a href="https://developers.google.com/drive/v3/web/about-auth">here</a>.
|
||||
* The default value is 'https://www.googleapis.com/auth/drive', set during construction. This can only be modified by changing the source code for
|
||||
* GoGoogleDrive. As changing scope impacts gapiClient's permissions (and could break the usability of some or all functions of GoGoogleDrive), this is not recommended.
|
||||
* @function.
|
||||
* @return {string}
|
||||
*/
|
||||
get scope(): string { return this._scope; }
|
||||
|
||||
/**
|
||||
* Get Google API Client. The Google API Client is used in GoGoogleDrive to make many different requests to Google Drive, however, it
|
||||
* can be used with other Google Libraries to achieve many purposes. To read more about what can be done with a Google API Client object,
|
||||
* click <a href="https://developers.google.com/api-client-library/javascript/start/start-js">here</a>. gapiClient is set after a succesful
|
||||
* authorization in {@link #authorize}.
|
||||
*
|
||||
* gapiClient is really of type Object, not type any. However, the Google libraries are all written in JavaScript and do not provide
|
||||
* d.ts files. As such, to avoid TypeScript compilation errors, both gapiClient and {@link #gapiPicker} properties are declared as type any.
|
||||
* @function.
|
||||
* @return {any}
|
||||
*/
|
||||
get gapiClient(): any { return this._gapiClient; }
|
||||
|
||||
/**
|
||||
* Get <a href="https://developers.google.com/picker/docs/">Google Picker</a> API Object. Used to show the Google filepicker when loading
|
||||
* / deleting files, in the {@link #createPicker} function. gapiPicker is set after a succesful authorization in {@link #authorize}.
|
||||
*
|
||||
* gapiPicker is really of type Object, not type any. However, the Google libraries are all written in JavaScript and do not
|
||||
* provide d.ts files. As such, to avoid TypeScript compilation errors, both {@link #gapiClient} and gapiPicker properties are declared as type any.
|
||||
* @function.
|
||||
* @return {any}
|
||||
*/
|
||||
get gapiPicker(): any { return this._gapiPicker; }
|
||||
|
||||
/**
|
||||
* Check if there is a signed in user who has authorized the application connected to this instance of GoGoogleDrive (via {@link #clientId}.
|
||||
* If not, prompt user to sign into their Google Account and authorize the application. On successful authorization, set {@link #gapiClient} and {@link #gapiPicker}.
|
||||
* @param {boolean} refreshToken Whether to get a new token (change current Google User)(true) or attempt to fetch a token for the currently signed in Google User (false).
|
||||
* @return {Promise<boolean>} Returns a Promise that resolves with a boolean stating whether authorization was succesful (true) or failed (false)
|
||||
*/
|
||||
public authorize(refreshToken: boolean = false) {
|
||||
const storage = this;
|
||||
let gapi = null;
|
||||
if (window['gapi']) gapi = window['gapi'];
|
||||
else return;
|
||||
if (refreshToken) {
|
||||
const href: string = document.location.href;
|
||||
document.location.href = 'https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=' + href;
|
||||
}
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
function auth() {
|
||||
gapi.auth.authorize({
|
||||
'client_id': storage.clientId,
|
||||
'scope': storage.scope,
|
||||
'immediate': false
|
||||
}, function(authResult) {
|
||||
if (authResult && !authResult.error) {
|
||||
storage._oauthToken = authResult.access_token;
|
||||
}
|
||||
storage._gapiClient = gapi.client;
|
||||
if (window['google']) storage._gapiPicker = window['google']['picker'];
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
gapi.load('client:auth', auth);
|
||||
gapi.load('picker', {});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch <a href="https://developers.google.com/picker/docs/">Google Picker</a>, a filepicker UI used to graphically select files in
|
||||
* Google Drive to load or delete. This is accomplished with {@link #gapiPicker}, which is set after succesful authorization, so this
|
||||
* function may only be called after a successful call to {@link #authorize}.
|
||||
* @param {Function} cb Callback function that takes the chosen file from the picker as a parameter
|
||||
*/
|
||||
public createPicker(cb: Function) {
|
||||
const storage = this;
|
||||
if (storage._oauthToken) {
|
||||
// (appId is just the first number of clientId before '-')
|
||||
const appId = storage.clientId.substring(0, this.clientId.indexOf('-'));
|
||||
const view = new storage.gapiPicker.View(storage.gapiPicker.ViewId.DOCS);
|
||||
view.setMimeTypes('application/json');
|
||||
view.setQuery('*.diagram');
|
||||
const picker = new storage.gapiPicker.PickerBuilder()
|
||||
.enableFeature(storage.gapiPicker.Feature.NAV_HIDDEN)
|
||||
.enableFeature(storage.gapiPicker.Feature.MULTISELECT_ENABLED)
|
||||
.setAppId(appId)
|
||||
.setOrigin(window.location.protocol + '//' + window.location.host)
|
||||
.setOAuthToken(storage._oauthToken)
|
||||
.addView(view)
|
||||
.setDeveloperKey(storage.pickerApiKey)
|
||||
.setCallback(function(args) {
|
||||
cb(args);
|
||||
})
|
||||
.build();
|
||||
picker.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get <a href="https://developers.google.com/drive/v3/reference/about#resource">information</a> about the
|
||||
* currently logged in Google user. Some fields of particular note include:
|
||||
* - displayName
|
||||
* - emailAdrdress
|
||||
* - kind
|
||||
* @return {Promise} Returns a Promise that resolves with information about the currently logged in Google user
|
||||
*/
|
||||
public getUserInfo() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const request = storage.gapiClient.request({
|
||||
'path': '/drive/v3/about',
|
||||
'method': 'GET',
|
||||
'params': { 'fields': 'user' },
|
||||
callback: function(resp) {
|
||||
if (resp) resolve(resp.user);
|
||||
else reject(resp);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Google Drive file reference object at a given path. Fields include:
|
||||
* - id: The Google Drive-given ID of the file at the provided path
|
||||
* - name: The name of the file saved to Google Drive at the provided path
|
||||
* - mimeType: For diagram files, this will always be `text/plain`
|
||||
* - kind: This will usually be `drive#file`.
|
||||
*
|
||||
* **Note:** Name, ID, and path values are requisite for creating valid {@link DiagramFile}s. When creating a DiagramFile for a
|
||||
* diagram saved to Google Drive, provide the same value for name and path properties.
|
||||
* @param {string} path A valid GoogleDrive file ID -- not a path. Named 'path' only to preserve system nomenclature
|
||||
* @return {Promise} Returns a Promise that resolves with a Google Drive file reference object at a given path
|
||||
*/
|
||||
public getFile(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const req = storage.gapiClient.request({
|
||||
path: '/drive/v3/files/' + path,
|
||||
method: 'GET',
|
||||
callback: function(resp) {
|
||||
if (!resp.error) {
|
||||
resolve(resp);
|
||||
} else {
|
||||
reject(resp.error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file exists at a given path
|
||||
* @param {string} path A valid GoogleDrive file ID -- not a path. Named 'path' only to preserve system nomenclature
|
||||
* @return {Promise} Returns a Promise that resolves with a boolean stating whether a file exists at a given path
|
||||
*/
|
||||
public checkFileExists(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const req = storage.gapiClient.request({
|
||||
path: '/drive/v3/files/' + path,
|
||||
method: 'GET',
|
||||
callback: function(resp) {
|
||||
const bool = (!!resp);
|
||||
resolve(bool);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the custom GoGoogleDrive save prompt; a div with an HTML input element that accepts a file name to save the current {@link #managedDiagrams}
|
||||
* data to in Google Drive.
|
||||
* @return {Promise} Returns a Promise that resolves (in {@link #save}, {@link #load}, or {@link #remove}) with a {@link DiagramFile} representing the saved/loaded/deleted file
|
||||
*/
|
||||
public showUI() {
|
||||
const storage = this;
|
||||
const ui = storage.ui;
|
||||
ui.innerHTML = ''; // clear div
|
||||
ui.style.visibility = 'visible';
|
||||
|
||||
ui.innerHTML = "<img class='icons' src='" + storage.iconsRelativeDirectory + "googleDrive.jpg'></img><strong>Save Diagram As</strong><hr></hr>";
|
||||
// user input div
|
||||
const userInputDiv: HTMLElement = document.createElement('div');
|
||||
userInputDiv.id = 'userInputDiv';
|
||||
userInputDiv.innerHTML += '<input id="userInput" placeholder="Enter filename"></input>';
|
||||
ui.appendChild(userInputDiv);
|
||||
|
||||
const submitDiv: HTMLElement = document.createElement('div');
|
||||
submitDiv.id = 'submitDiv';
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.id = 'actionButton';
|
||||
actionButton.textContent = 'Save';
|
||||
actionButton.onclick = function() {
|
||||
storage.saveWithUI();
|
||||
};
|
||||
submitDiv.appendChild(actionButton);
|
||||
ui.appendChild(submitDiv);
|
||||
|
||||
const cancelDiv: HTMLElement = document.createElement('div');
|
||||
cancelDiv.id = 'cancelDiv';
|
||||
const 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current {@link #managedDiagrams}'s model data to the current Google user's Google Drive using the custom {@link #ui} save prompt.
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public saveWithUI() {
|
||||
const storage = this;
|
||||
const ui = storage.ui;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (ui.style.visibility === 'hidden') {
|
||||
resolve(storage.showUI());
|
||||
} else {
|
||||
const saveName: string = (document.getElementById('userInput') as HTMLInputElement).value;
|
||||
storage.save(saveName);
|
||||
resolve(storage.hideUI());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save {@link #managedDiagrams}' model data to GoGoogleDrive. 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 GoGoogleDrive 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 name (not a path, not an id) to save this diagram file in Google Drive under. Named 'path' only to preserve system nomenclature
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public save(path?: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) { // save as
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
let overwrite: boolean = false;
|
||||
let overwriteFile: Object = null;
|
||||
// get saved diagrams
|
||||
const request = storage.gapiClient.request({
|
||||
'path': '/drive/v3/files',
|
||||
'method': 'GET',
|
||||
'params': { 'q': 'trashed=false and name contains ".diagram" and mimeType = "application/json"' },
|
||||
callback: function(resp) {
|
||||
const savedDiagrams: Array<Object> = resp.files;
|
||||
if (savedDiagrams) {
|
||||
for (let i = 0; i < savedDiagrams.length; i++) {
|
||||
if (savedDiagrams[i]['name'] === path) {
|
||||
overwrite = true;
|
||||
overwriteFile = savedDiagrams[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const boundary = '-------314159265358979323846';
|
||||
const delimiter = '\r\n--' + boundary + '\r\n';
|
||||
const closeDelim = '\r\n--' + boundary + '--';
|
||||
const contentType = 'application/json';
|
||||
|
||||
const metadata: Object = {
|
||||
'name': path,
|
||||
'mimeType': contentType
|
||||
};
|
||||
|
||||
const data = storage.makeSaveFile();
|
||||
|
||||
const multipartRequestBody: string =
|
||||
delimiter +
|
||||
'Content-Type: application/json\r\n\r\n' +
|
||||
JSON.stringify(metadata) +
|
||||
delimiter +
|
||||
'Content-Type: ' + contentType + '\r\n\r\n' +
|
||||
data +
|
||||
closeDelim;
|
||||
|
||||
const req = storage.gapiClient.request({
|
||||
'path': '/upload/drive/v3/files',
|
||||
'method': 'POST',
|
||||
'params': { 'uploadType': 'multipart' },
|
||||
'headers': {
|
||||
'Content-Type': 'multipart/related; boundary="' + boundary + '"'
|
||||
},
|
||||
'body': multipartRequestBody
|
||||
});
|
||||
req.execute(function(response) {
|
||||
const savedFile: gcs.DiagramFile = { name: response.name, id: response.id, path: response.name };
|
||||
storage.currentDiagramFile = savedFile;
|
||||
resolve(savedFile); // used if save was called without UI
|
||||
|
||||
// if save has been called in saveDiagramWithUI, need to resolve / reset the Deferred Promise instance variable
|
||||
storage._deferredPromise.promise.resolve(savedFile);
|
||||
storage._deferredPromise.promise = storage.makeDeferredPromise();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (storage.currentDiagramFile.path) { // save
|
||||
const fileId: string = storage.currentDiagramFile.id;
|
||||
const saveFile: string = storage.makeSaveFile();
|
||||
storage.gapiClient.request({
|
||||
path: '/upload/drive/v3/files/' + fileId,
|
||||
method: 'PATCH',
|
||||
params: { uploadType: 'media' },
|
||||
body: saveFile,
|
||||
callback: function(resp) {
|
||||
if (!resp.error) {
|
||||
// successful save
|
||||
const savedFile: gcs.DiagramFile = { name: resp.name, id: resp.id, path: resp.name };
|
||||
resolve(savedFile);
|
||||
} else if (resp.error.code === 401) {
|
||||
storage.authorize(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve(storage.saveWithUI()); // must use UI prompt to get a name if no 'path' is provided
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the contents of a saved diagram from Google Drive using the Google Picker (see {@link #gapiPicker} and {@link #createPicker}).
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public loadWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const loadFunction: Function = function(data) {
|
||||
if (data.action === 'picked') {
|
||||
const file = data.docs[0];
|
||||
storage.gapiClient.request({
|
||||
'path': '/drive/v3/files/' + file.id + '?alt=media',
|
||||
'method': 'GET',
|
||||
callback: function(modelData) {
|
||||
if (file.name.indexOf('.diagram') !== -1) {
|
||||
const loadedFile = { name: file.name, path: file.name, id: file.id };
|
||||
resolve(storage.load(file.id));
|
||||
storage.currentDiagramFile = loadedFile;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
storage.createPicker(loadFunction); // TODO
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a saved diagram from Google Drive using a given Google Drive file ID. No UI of any sort appears.
|
||||
* @param {string} path A valid GoogleDrive file ID -- not a path. Named 'path' only to preserve GoCloudStorage system nomenclature
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public load(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
storage.getFile(path).then(function(file: any) {
|
||||
storage.gapiClient.request({
|
||||
'path': '/drive/v3/files/' + file.id + '?alt=media',
|
||||
'method': 'GET',
|
||||
callback: function(modelData) {
|
||||
if (modelData) {
|
||||
if (file.name.indexOf('.diagram') !== -1) {
|
||||
storage.loadFromFileContents(JSON.stringify(modelData));
|
||||
const loadedFile: gcs.DiagramFile = { name: file['name'], path: file['name'], id: file['id'] };
|
||||
storage.currentDiagramFile = loadedFile;
|
||||
resolve(loadedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}).catch(function(e) {
|
||||
reject(e.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a selected diagram from a user's Google Drive using the Google Picker (see {@link #gapiPicker} and {@link #createPicker}).
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public removeWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const deleteFunction = function(data: Object) {
|
||||
if (data['action'] === 'picked') {
|
||||
const file = data['docs'][0];
|
||||
resolve(storage.remove(file.id));
|
||||
}
|
||||
};
|
||||
storage.createPicker(deleteFunction);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a the diagram from a user's Google Drive with the given Google Drive file ID. No UI of any sort appears.
|
||||
* @param {string} path A valid GoogleDrive file ID -- not a path. Named 'path' only to preserve system nomenclature
|
||||
* @return {Promise} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public remove(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
storage.getFile(path).then(function(deletedFile: Object) {
|
||||
storage.gapiClient.request({
|
||||
'path': 'drive/v3/files/' + path,
|
||||
'method': 'DELETE',
|
||||
callback: function() {
|
||||
if (storage.currentDiagramFile && path === storage.currentDiagramFile.id) storage.currentDiagramFile = { name: null, path: null, id: null };
|
||||
deletedFile['path'] = deletedFile['name']; // google drive file references don't include path
|
||||
resolve(deletedFile);
|
||||
}
|
||||
});
|
||||
}).catch(function(e) {
|
||||
reject(e.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Go Local Storage
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
import * as gcs from './GoCloudStorage.js';
|
||||
|
||||
/**
|
||||
* Class for saving / loading GoJS {@link Model}s to / from Local Storage.
|
||||
* GoLocalStorage is the only {@link GoCloudStorage} subclass than can be used in a local page;
|
||||
* that is, one not served by a web server.
|
||||
*
|
||||
* **Note**: This class will not work with browsers that do not have Local Storage support
|
||||
* (like some old versions of Internet Explorer).
|
||||
* @category Storage
|
||||
*/
|
||||
export class GoLocalStorage extends gcs.GoCloudStorage {
|
||||
|
||||
private _localStorage: Storage;
|
||||
/**
|
||||
* The number of files to display in {@link #ui} before loading more
|
||||
*/
|
||||
private static _MIN_FILES_IN_UI = 100;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {go.Diagram|go.Diagram[]} managedDiagrams
|
||||
* An array of GoJS {@link Diagram}s whose model(s) will be saved to / loaded from Local Storage.
|
||||
* Can also be a single Diagram.
|
||||
* @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 GoLocalStorage exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
*/
|
||||
constructor(managedDiagrams: go.Diagram | Array<go.Diagram>, defaultModel?: string, iconsRelativeDirectory?: string) {
|
||||
super(managedDiagrams, defaultModel, null, iconsRelativeDirectory);
|
||||
try {
|
||||
this._localStorage = window.localStorage;
|
||||
} catch (e) {
|
||||
throw new Error('Cannot access localStorage. Make sure your browser supports localStorage.' +
|
||||
'If so, and this issue persists, try unblocking third-party cookies and site data in your browser settings');
|
||||
}
|
||||
this.ui.id = 'goLocalStorageCustomFilepicker';
|
||||
this._serviceName = 'Local Storage';
|
||||
this._className = 'GoLocalStorage';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the browser window's <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage">Local Storage</a> property.
|
||||
* @function.
|
||||
* @return {Storage}
|
||||
*/
|
||||
get localStorage(): Storage { return this._localStorage; }
|
||||
|
||||
/**
|
||||
* Check if Local Storage is supported by the current browser.
|
||||
* @param {boolean} refreshToken This parameter can be ignored. It exists only to maintain GoCloudStorage system structure
|
||||
* @return {Promise<boolean>} Returns a Promise that resolves with a boolean (true if local storage is supported, false if not)
|
||||
*/
|
||||
public authorize(refreshToken: boolean = false): Promise<any> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
try {
|
||||
storage.localStorage.setItem('item', 'item');
|
||||
storage.localStorage.removeItem('item');
|
||||
resolve(true);
|
||||
} catch (e) {
|
||||
// local storage not supported
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the custom Go Local Storage filepicker {@link #ui}.
|
||||
* @param {string} action Clarify what action is being done after file selection. Must be one of the following:
|
||||
* - New
|
||||
* - Open
|
||||
* - Save
|
||||
* - Delete
|
||||
* @param {number} numAdditionalFiles Optional: Number of files to show in UI, in addition to a static numerical property (that can only be
|
||||
* modified by changing source code). This prevents long wait times while the UI loads if there are a large number of diagram files stored in Local Storage.
|
||||
* @return {Promise<any>} Returns a Promise that resolves (in {@link #save}, {@link #load}, or {@link #remove} with an {@link DiagramFile}
|
||||
* representing the saved/loaded/deleted file
|
||||
*/
|
||||
public showUI(action: string, numAdditionalFiles?: number) {
|
||||
const storage = this;
|
||||
const ui = storage.ui;
|
||||
const spacestring = 'qwe45qw34'; // used as a placeholder for spaces in IDs
|
||||
if (!numAdditionalFiles) numAdditionalFiles = 0;
|
||||
const maxFilesToShow: number = GoLocalStorage._MIN_FILES_IN_UI + numAdditionalFiles;
|
||||
ui.innerHTML = "<img class='icons' src='" + storage.iconsRelativeDirectory + "localStorage.png'></img>";
|
||||
const title: string = action + ' Diagram File';
|
||||
ui.innerHTML += '<strong>' + title + '</strong><hr></hr>';
|
||||
|
||||
// document.getElementsByTagName('body')[0].appendChild(ui);
|
||||
ui.style.visibility = 'visible';
|
||||
const filesDiv = document.createElement('div');
|
||||
filesDiv.id = 'fileOptions';
|
||||
|
||||
// filter out non-diagram files in local storage (only until max allowed files is reached)
|
||||
const savedDiagrams: Array<Object> = [];
|
||||
const numFilesToCheck: number = GoLocalStorage._MIN_FILES_IN_UI + numAdditionalFiles;
|
||||
let numFilesChecked: number = 0;
|
||||
let hasCheckedAllFiles: boolean = false;
|
||||
if (storage.localStorage.length !== 0) {
|
||||
for (const key in storage.localStorage) {
|
||||
if (savedDiagrams.length < maxFilesToShow) {
|
||||
numFilesChecked++;
|
||||
const fileContent: string = storage.localStorage.getItem(key);
|
||||
if (fileContent && (fileContent.indexOf('GraphLinksModel') !== -1 || fileContent.indexOf('TreeModel') !== -1)) {
|
||||
const file: Object = { key: key, model: fileContent };
|
||||
savedDiagrams.push(file);
|
||||
}
|
||||
if (numFilesChecked === storage.localStorage.length) hasCheckedAllFiles = true;
|
||||
}
|
||||
}
|
||||
} else hasCheckedAllFiles = true;
|
||||
if (savedDiagrams.length !== 0) {
|
||||
// list diagram files in local storage as selectable files (as many as MIN_FILES_IN_UI + additionalFiles param)
|
||||
for (let i = 0; i < savedDiagrams.length; i++) {
|
||||
const kvp: Object = savedDiagrams[i];
|
||||
const file: string = kvp['key'];
|
||||
const fileId: string = file.replace(/ /g, spacestring);
|
||||
if (action !== 'Save') {
|
||||
filesDiv.innerHTML +=
|
||||
"<div class='fileOption'>" +
|
||||
'<input id=' + fileId + " type='radio' name='localStorageFile' />" +
|
||||
'<label id =' + fileId + '-label' + " for='" + fileId + "'>" + file + '</label>' +
|
||||
'</div>';
|
||||
} else {
|
||||
filesDiv.innerHTML +=
|
||||
"<div class='fileOption'>" +
|
||||
'<label id =' + fileId + '-label' + " for='" + fileId + "'>" + file + '</label>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there may be more diagram files to show, say so and provide user with option to try loading more in the UI
|
||||
if (!hasCheckedAllFiles) {
|
||||
const num: number = numAdditionalFiles + 50;
|
||||
filesDiv.innerHTML += "<p>There may be more diagram files not shown. <a id='localStorageLoadMoreFiles'>Click here</a> to try loading more.</p>";
|
||||
document.getElementById('localStorageLoadMoreFiles').onclick = function() {
|
||||
storage.showUI(action, num);
|
||||
};
|
||||
}
|
||||
ui.appendChild(filesDiv);
|
||||
|
||||
// italicize currently open file, if a file is currently open
|
||||
if (storage.currentDiagramFile.id) {
|
||||
const str: string = storage.currentDiagramFile.id.replace(/ /g, spacestring);
|
||||
const el: HTMLElement = document.getElementById(str + '-label');
|
||||
if (el) el.style.fontStyle = 'italic';
|
||||
}
|
||||
|
||||
// user input div (only for save)
|
||||
if (action === 'Save') {
|
||||
const userInputDiv = document.createElement('div');
|
||||
userInputDiv.id = 'userInputDiv';
|
||||
userInputDiv.innerHTML += '<span>Save Diagram As </span><input id="userInput" placeholder="Enter filename"></input>';
|
||||
ui.appendChild(userInputDiv);
|
||||
}
|
||||
|
||||
const submitDiv = document.createElement('div');
|
||||
submitDiv.id = 'submitDiv';
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.textContent = action;
|
||||
actionButton.id = 'actionButton';
|
||||
actionButton.onclick = function() {
|
||||
storage.processUIResult(action);
|
||||
};
|
||||
submitDiv.appendChild(actionButton);
|
||||
ui.appendChild(submitDiv);
|
||||
|
||||
|
||||
const cancelDiv = document.createElement('div');
|
||||
const 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']; // will not resolve until action (save, load, delete) completes
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @hidden
|
||||
* Process the result of pressing the action button on the custom GoLocalStorage filepicker {@link #ui}.
|
||||
* @param {string} action The action being done. Acceptable values:
|
||||
* - Save
|
||||
* - Load
|
||||
* - Delete
|
||||
*/
|
||||
public processUIResult(action: string) {
|
||||
const storage = this;
|
||||
// Helper: Return key of the file selected from the custom localstorage filepicker menu
|
||||
function getSelectedFile() {
|
||||
const radios = document.getElementsByName('localStorageFile');
|
||||
let selectedFile: string = null;
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
if ((radios[i] as HTMLInputElement).checked) {
|
||||
selectedFile = radios[i].id.replace(/qwe45qw34/g, ' ');
|
||||
}
|
||||
}
|
||||
if (selectedFile) return selectedFile;
|
||||
else return null;
|
||||
}
|
||||
|
||||
const file: string = getSelectedFile();
|
||||
switch (action) {
|
||||
case 'Save': {
|
||||
let name = (document.getElementById('userInput') as HTMLInputElement).value;
|
||||
if (name) {
|
||||
name += '.diagram';
|
||||
storage.save(name);
|
||||
} else {
|
||||
// handle bad name
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Load': {
|
||||
storage.load(file);
|
||||
break;
|
||||
}
|
||||
case 'Delete': {
|
||||
storage.remove(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
storage.hideUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a diagram file saved to Local Storage. This data includes:
|
||||
* - content: The content of the saved file (a string respresentation of a GoJS Diagram Model)
|
||||
* - id: The key of the file in local storage
|
||||
* - name: Same as id value
|
||||
* - path: Same as id value
|
||||
*
|
||||
* **Note:** Id, name, and path are all provided (despite being the same). They are required for creating valid {@link DiagramFile}s.
|
||||
* @param {string} path A valid key corresponding to a saved diagram file in Local Storage
|
||||
* @return {Promise<any>} Returns a Promise that resolves with information about a diagram file saved to local storage
|
||||
*/
|
||||
public getFile(path: string) {
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
return new Promise(function(resolve, reject) {
|
||||
const fileContent: string = (!!window.localStorage.getItem(path)) ? window.localStorage.getItem(path) : null;
|
||||
const file: Object = { name: path, content: fileContent, path: path, id: path };
|
||||
resolve(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file exists in Local Storage at a given path.
|
||||
* @param {string} path A valid key corresponding to a saved diagram file in Local Storage
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a boolean stating whether a file exists in LocalStorage at a given path
|
||||
*/
|
||||
public checkFileExists(path: string) {
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
return new Promise(function(resolve, reject) {
|
||||
const fileExists: boolean = !!(window.localStorage.getItem(path));
|
||||
resolve(fileExists);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current {@link #managedDiagrams}'s model data to Local Storage using the custom filepicker {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public saveWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storage.showUI('Save'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save {@link #managedDiagrams}' model data to Local Storage. 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 key in Local Storage 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 string to save diagram model data to (becomes the key for the file in Local Storage)
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public save(path?: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) {
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
const item: string = storage.makeSaveFile();
|
||||
storage.localStorage.setItem(path, item);
|
||||
const savedFile: gcs.DiagramFile = { name: path, id: path, path: path };
|
||||
storage.currentDiagramFile = savedFile;
|
||||
resolve(savedFile); // used if saveDiagramAs was called without UI
|
||||
|
||||
// if saveDiagramAs has been called in processUIResult, need to resolve / reset the Deferred Promise instance variable
|
||||
storage._deferredPromise['promise'].resolve(savedFile);
|
||||
storage._deferredPromise['promise'] = storage.makeDeferredPromise();
|
||||
} else if (storage.currentDiagramFile.path) {
|
||||
const saveName: string = storage.currentDiagramFile['path'];
|
||||
const savedFile: gcs.DiagramFile = { name: saveName, path: saveName, id: saveName };
|
||||
const item: string = storage.makeSaveFile();
|
||||
storage.localStorage.setItem(saveName, item);
|
||||
resolve(saveName);
|
||||
} else {
|
||||
resolve(storage.saveWithUI());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a given file; load to {@link #managedDiagrams} model. Use the custom filepicker {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public loadWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storage.showUI('Load'));
|
||||
}).catch(function(e: any) {
|
||||
throw Error(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a given file; load to {@link #managedDiagrams} model.
|
||||
* @param {string} path A valid localstorage key to load diagram model data from
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public load(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) {
|
||||
const fileContents: string = storage.localStorage.getItem(path);
|
||||
if (fileContents) {
|
||||
storage.loadFromFileContents(fileContents);
|
||||
const loadedFile: gcs.DiagramFile = { name: path, id: path, path: path };
|
||||
storage.currentDiagramFile = loadedFile;
|
||||
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 local storage with path ' + path);
|
||||
} else throw Error('Cannot load file from local storage with path ' + path);
|
||||
}).catch(function(e: any) {
|
||||
// console.error(e);
|
||||
throw Error(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a diagram from Local Storage using the custom filepicker menu {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public removeWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storage.showUI('Delete'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a given diagram from Local Storage.
|
||||
* @param {string} path A valid localstorage key to delete diagram model data from
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public remove(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) {
|
||||
const deletedFile: gcs.DiagramFile = { name: path, path: path, id: path };
|
||||
if (storage.currentDiagramFile && path === storage.currentDiagramFile['name']) storage.currentDiagramFile = { name: null, path: null, id: null };
|
||||
storage.localStorage.removeItem(path); // remove file from local storage
|
||||
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();
|
||||
} else throw Error('Cannot delete file from local storage with path ' + path);
|
||||
});
|
||||
}
|
||||
}
|
||||
+465
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Go Net Core (unfinished)
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
import * as gcs from './GoCloudStorage.js';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
* Class for saving / loading GoJS {@link Model}s to / from Local Storage.
|
||||
* GoLocalStorage is the only {@link GoCloudStorage} subclass than can be used in a local page; that is, one not served by a web server.
|
||||
*
|
||||
* **Note**: that this class will not work with browsers that do not have Local Storage support (like some old versions of Internet Explorer).
|
||||
* @category Storage
|
||||
*/
|
||||
export class GoNetCore extends gcs.GoCloudStorage {
|
||||
|
||||
/**
|
||||
* The number of files to display in {@link #ui} before loading more
|
||||
*/
|
||||
private static _MIN_FILES_IN_UI = 100;
|
||||
private _rootEndpoint: string;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {go.Diagram|go.Diagram[]} managedDiagrams An array of GoJS {@link Diagram}s whose model(s) will be saved to / loaded from Local Storage.
|
||||
* Can also be a single Diagram.
|
||||
* @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 GoLocalStorage exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
*/
|
||||
constructor(managedDiagrams: go.Diagram | Array<go.Diagram>, rootEndpoint: string, defaultModel?: string, iconsRelativeDirectory?: string) {
|
||||
super(managedDiagrams, defaultModel);
|
||||
this._rootEndpoint = rootEndpoint;
|
||||
this.ui.id = 'goNetCoreCustomFilepicker';
|
||||
this._serviceName = 'Microsoft ASP .NET Core Web API';
|
||||
this._className = 'GoNetCore';
|
||||
}
|
||||
|
||||
get rootEndpoint(): string { return this._rootEndpoint; }
|
||||
|
||||
/**
|
||||
* Check if Local Storage is supported by the current browser.
|
||||
* @param {boolean} refreshToken This parameter can be ignored. It exists only to maintain GoCloudStorage system structure
|
||||
* @return {Promise<boolean>} Returns a Promise that resolves with a boolean (true if local storage is supported, false if not)
|
||||
*/
|
||||
public authorize(refreshToken: boolean = false) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(true); // TODO? no auth at all right now
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO TODO TODO
|
||||
* Show the custom Go Net Core filepicker {@link #ui}.
|
||||
* @param {string} action Clarify what action is being done after file selection. Must be one of the following:
|
||||
* - New
|
||||
* - Open
|
||||
* - Save
|
||||
* - Delete
|
||||
* @param {number} numAdditionalFiles Optional: Number of files to show in UI, in addition to a static numerical property (that can only be
|
||||
* modified by changing source code). This prevents long wait times while the UI loads if there are a large number of diagram files stored in Local Storage.
|
||||
* @return {Promise<any>} Returns a Promise that resolves (in {@link #save}, {@link #load}, or {@link #remove} with an {@link DiagramFile}
|
||||
* representing the saved/loaded/deleted file
|
||||
*/
|
||||
public showUI(action: string, numAdditionalFiles?: number) {
|
||||
const storage = this;
|
||||
const ui = storage.ui;
|
||||
const spacestring = 'qwe45qw34'; // used as a placeholder for spaces in IDs
|
||||
if (!numAdditionalFiles) numAdditionalFiles = 0;
|
||||
const maxFilesToShow: number = GoNetCore._MIN_FILES_IN_UI + numAdditionalFiles;
|
||||
ui.innerHTML = ''; // "<img class='icons' src='" + storage.iconsRelativeDirectory + "localStorage.png'></img>"; // TODO
|
||||
const title: string = action + ' Diagram File';
|
||||
ui.innerHTML += '<strong>' + title + '</strong><hr></hr>';
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(ui);
|
||||
ui.style.visibility = 'visible';
|
||||
const filesDiv = document.createElement('div');
|
||||
filesDiv.id = 'fileOptions';
|
||||
|
||||
// filter out non-diagram files in local storage (only until max allowed files is reached)
|
||||
const savedDiagrams: Array<Object> = [];
|
||||
const numFilesToCheck: number = GoNetCore._MIN_FILES_IN_UI + numAdditionalFiles;
|
||||
let numFilesChecked: number = 0;
|
||||
let hasCheckedAllFiles: boolean = false;
|
||||
|
||||
storage.getFiles().then(function(files: Array<any>) {
|
||||
if (files.length !== 0) {
|
||||
for (const i in files) {
|
||||
const item = files[i];
|
||||
if (savedDiagrams.length < maxFilesToShow) {
|
||||
numFilesChecked++;
|
||||
const fileContent: string = item.file;
|
||||
if (fileContent && fileContent.indexOf('GraphLinksModel' || 'TreeModel') !== -1) {
|
||||
const file: Object = { name: item.name, id: item.id };
|
||||
savedDiagrams.push(file);
|
||||
}
|
||||
if (numFilesChecked === files.length) hasCheckedAllFiles = true;
|
||||
}
|
||||
}
|
||||
} else hasCheckedAllFiles = true;
|
||||
if (savedDiagrams.length !== 0) {
|
||||
// list diagram files in local storage as selectable files (as many as MIN_FILES_IN_UI + additionalFiles param)
|
||||
for (let i = 0; i < savedDiagrams.length; i++) {
|
||||
const item: any = savedDiagrams[i];
|
||||
const name: string = item.name;
|
||||
const id: string = item.id;
|
||||
if (action !== 'Save') {
|
||||
filesDiv.innerHTML +=
|
||||
"<div class='fileOption'>" +
|
||||
'<input id=' + id + " type='radio' name='localStorageFile' />" +
|
||||
'<label id =' + id + '-label' + " for='" + name + "'>" + name + '</label>' +
|
||||
'</div>';
|
||||
} else {
|
||||
filesDiv.innerHTML +=
|
||||
"<div class='fileOption'>" +
|
||||
'<label id =' + id + '-label' + " for='" + id + "'>" + name + '</label>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there may be more diagram files to show, say so and provide user with option to try loading more in the UI
|
||||
if (!hasCheckedAllFiles) {
|
||||
const num: number = numAdditionalFiles + 50;
|
||||
filesDiv.innerHTML += "<p>There may be more diagram files not shown. <a id='netCoreLoadMoreFiles'>Click here</a> to try loading more.</p>";
|
||||
document.getElementById('netCoreLoadMoreFiles').onclick = function() {
|
||||
storage.showUI(action, num);
|
||||
};
|
||||
}
|
||||
ui.appendChild(filesDiv);
|
||||
|
||||
// italicize currently open file, if a file is currently open
|
||||
if (storage.currentDiagramFile.id) {
|
||||
const el: HTMLElement = document.getElementById(storage.currentDiagramFile.id + '-label');
|
||||
if (el) el.style.fontStyle = 'italic';
|
||||
}
|
||||
|
||||
// user input div (only for save)
|
||||
if (action === 'Save') {
|
||||
const userInputDiv = document.createElement('div');
|
||||
userInputDiv.id = 'userInputDiv';
|
||||
userInputDiv.innerHTML += '<span>Save Diagram As </span><input id="userInput" placeholder="Enter filename"></input>';
|
||||
ui.appendChild(userInputDiv);
|
||||
}
|
||||
|
||||
const submitDiv = document.createElement('div');
|
||||
submitDiv.id = 'submitDiv';
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.textContent = action;
|
||||
actionButton.id = 'actionButton';
|
||||
actionButton.onclick = function() {
|
||||
storage.processUIResult(action);
|
||||
};
|
||||
submitDiv.appendChild(actionButton);
|
||||
ui.appendChild(submitDiv);
|
||||
|
||||
const cancelDiv = document.createElement('div');
|
||||
const 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']; // will not resolve until action (save, load, delete) completes
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO TODO TODO
|
||||
* @private
|
||||
* @hidden
|
||||
* Process the result of pressing the action button on the custom GoLocalStorage filepicker {@link #ui}.
|
||||
* @param {string} action The action being done. Acceptable values:
|
||||
* - Save
|
||||
* - Load
|
||||
* - Delete
|
||||
*/
|
||||
public processUIResult(action: string) {
|
||||
const storage = this;
|
||||
// Helper: Return key of the file selected from the custom localstorage filepicker menu
|
||||
function getSelectedFile() {
|
||||
const radios = document.getElementsByName('localStorageFile');
|
||||
let selectedFile: string = null;
|
||||
for (let i = 0; i < radios.length; i++) {
|
||||
if ((radios[i] as HTMLInputElement).checked) {
|
||||
selectedFile = radios[i].id.replace(/qwe45qw34/g, ' ');
|
||||
}
|
||||
}
|
||||
if (selectedFile) return selectedFile;
|
||||
else return null;
|
||||
}
|
||||
|
||||
const file: string = getSelectedFile();
|
||||
switch (action) {
|
||||
case 'Save': {
|
||||
let name = (document.getElementById('userInput') as HTMLInputElement).value;
|
||||
if (name) {
|
||||
name += '.diagram';
|
||||
storage.save(name);
|
||||
} else {
|
||||
// handle bad name
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'Load': {
|
||||
storage.load(file);
|
||||
break;
|
||||
}
|
||||
case 'Delete': {
|
||||
storage.remove(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
storage.hideUI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about a diagram file saved to .NET Core database. This data includes:
|
||||
* - content: The content of the saved file (a string respresentation of a GoJS Diagram Model)
|
||||
* - id: The key of the file in local storage
|
||||
* - name: Same as id value
|
||||
* - path: Same as id value
|
||||
*
|
||||
* **Note:** Id, name, and path are all provided (despite being the same). They are requisite for creating valid {@link DiagramFile}s.
|
||||
* @param {string} path A valid id corresponding to a saved diagram file in the .NET Core database
|
||||
* @return {Promise<any>} Returns a Promise that resolves with information about a diagram file saved to local storage
|
||||
*/
|
||||
public getFile(path: string) {
|
||||
const storage = this;
|
||||
const url: string = storage.rootEndpoint + path;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (path) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve((JSON.parse(xhr.response)));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
} else reject('Cannot get diagram file from ASP .NET Core Web API with id ' + path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the files in the ASP.NET Core database.
|
||||
* Returns a Promise that resolves with JSON data about every file, including name, id, and content
|
||||
*/
|
||||
public getFiles() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('GET', storage.rootEndpoint, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(JSON.parse(xhr.response));
|
||||
} else reject(xhr.response);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file exists in LocalStorage at a given path.
|
||||
* @param {string} path A valid key corresponding to a saved diagram file in Local Storage
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a boolean stating whether a file exists in LocalStorage at a given path
|
||||
*/
|
||||
public checkFileExists(path: string) {
|
||||
const storage = this;
|
||||
const url: string = storage.rootEndpoint + path;
|
||||
return new Promise(function(resolve, reject) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve((true));
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current {@link #managedDiagrams}'s model data to Local Storage using the custom filepicker {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public saveWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storage.showUI('Save'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save {@link #managedDiagrams}' model data to Local Storage. 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 key in Local Storage 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 string to save diagram model data to (becomes the key for the file in Local Storage)
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file
|
||||
*/
|
||||
public save(path?: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
// PUT -- update current entry
|
||||
if (storage.currentDiagramFile.id && !path) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
storage.getFile(storage.currentDiagramFile.id).then(function(resp) {
|
||||
const item = {
|
||||
id: storage.currentDiagramFile.id,
|
||||
name: storage.currentDiagramFile.name,
|
||||
file: storage.makeSaveFile()
|
||||
};
|
||||
const savedFile: gcs.DiagramFile = { id: item.id, name: item.name, path: item.name };
|
||||
xhr.open('PUT', storage.rootEndpoint + storage.currentDiagramFile.id, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status > 200 && xhr.status < 300) {
|
||||
resolve(savedFile);
|
||||
} else {
|
||||
reject(xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify(item));
|
||||
});
|
||||
} else {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
const item = {
|
||||
name: (path !== null) ? path : 'New diagram',
|
||||
file: storage.makeSaveFile()
|
||||
};
|
||||
xhr.open('POST', storage.rootEndpoint, true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status > 200 && xhr.status < 300) {
|
||||
const id = JSON.parse(xhr.response).id;
|
||||
const savedFile: gcs.DiagramFile = { id: id, name: item.name, path: item.name };
|
||||
|
||||
storage.currentDiagramFile = savedFile;
|
||||
resolve(savedFile); // used if saveDiagram was called without UI
|
||||
|
||||
// if saveDiagram has been called in processUIResult, need to resolve / reset the Deferred Promise instance variable
|
||||
storage._deferredPromise['promise'].resolve(savedFile);
|
||||
storage._deferredPromise['promise'] = storage.makeDeferredPromise();
|
||||
} else {
|
||||
reject(xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify(item));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a given file; load to {@link #managedDiagrams} model. Use the custom filepicker {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public loadWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storage.showUI('Load'));
|
||||
}).catch(function(e: any) {
|
||||
throw Error(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a given file; load to {@link #managedDiagrams} model.
|
||||
* @param {string} path A valid localstorage key to load diagram model data from
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public load(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('GET', storage.rootEndpoint + path, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
const respJSON = JSON.parse(xhr.response);
|
||||
storage.loadFromFileContents(respJSON.file);
|
||||
const loadedFile: gcs.DiagramFile = { id: respJSON.id, path: respJSON.name, name: respJSON.name };
|
||||
storage.currentDiagramFile = loadedFile;
|
||||
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 {
|
||||
reject(xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
} else throw Error('Cannot load file from .NET Core Web API with path ' + path);
|
||||
}).catch(function(e: any) {
|
||||
throw Error(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a diagram from Local Storage using the custom filepicker menu {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public removeWithUI() {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
resolve(storage.showUI('Delete'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a given diagram from Local Storage.
|
||||
* @param {string} path A valid localstorage key to delete diagram model data from
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public remove(path: string) {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) {
|
||||
storage.getFile(path).then(function(resp: any) {
|
||||
const deletedFile: gcs.DiagramFile = { name: resp.name, path: resp.name, id: path };
|
||||
if (storage.currentDiagramFile && resp.name === storage.currentDiagramFile.name) storage.currentDiagramFile = { name: null, path: null, id: null };
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('DELETE', storage.rootEndpoint + path, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
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();
|
||||
} else {
|
||||
reject(xhr.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
} else throw Error('Cannot delete file from local storage with id ' + path);
|
||||
});
|
||||
}
|
||||
}
|
||||
+485
@@ -0,0 +1,485 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Go One Drive
|
||||
*/
|
||||
|
||||
// import { Promise } from 'es6-promise';
|
||||
import * as go from 'gojs';
|
||||
import * as gcs from './GoCloudStorage.js';
|
||||
|
||||
/**
|
||||
* Class for saving / loading GoJS {@link Diagram#model}s to / from Microsoft One Drive.
|
||||
* As with all {@link GoCloudStorage} subclasses (with the exception of {@link GoLocalStorage}, any page using GoDropBox must be served on a web server.
|
||||
*
|
||||
* **Note**: Makes use of <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">OneDrive FilePicker for JavaScript v7.2</a>. Any page
|
||||
* using GoOneDrive must include a script tag with src set to https://js.live.net/v7.2/OneDrive.js.
|
||||
* @category Storage
|
||||
*/
|
||||
export class GoOneDrive extends gcs.GoCloudStorage {
|
||||
|
||||
private _oneDriveFilepicker: any;
|
||||
private _oauthToken: string;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {go.Diagram[]} managedDiagrams An array of GoJS {@link Diagram}s whose model(s) will be saved to
|
||||
* / loaded from OneDrive. Can also be a single Diagram.
|
||||
* @param {string} clientId The client ID of the application in use (given by Microsoft in Microsoft Dev Center)
|
||||
* @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 GoOneDrive exists, in which
|
||||
* the storage service brand icons can be found. The default value is "../goCloudStorageIcons/".
|
||||
*/
|
||||
constructor(managedDiagrams: go.Diagram | Array<go.Diagram>, clientId: string, defaultModel?: string, iconsRelativeDirectory?: string) {
|
||||
super(managedDiagrams, defaultModel, clientId, iconsRelativeDirectory);
|
||||
this._oauthToken = null;
|
||||
this.ui.id = 'goOneDriveSavePrompt';
|
||||
if (window['OneDrive']) {
|
||||
this._oneDriveFilepicker = window['OneDrive'];
|
||||
}
|
||||
this.authorize(false); // // on construction, check if there is an access_token in the window URI (there will be if redirected from a permissions grant page)
|
||||
this._serviceName = 'Microsoft OneDrive';
|
||||
this._className = 'GoOneDrive';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get / set the global oauthToken. Only used to authorize requests in {@link #load}, {@link #save}, and {@link #remove}
|
||||
* when the {@link #oneDriveFilepicker} property is not used. Not needed when the oneDriveFilePicker is used, as action-specific tokens (issued by the Microsoft-provided
|
||||
* <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">OneDrive Filepicker</a>) are issued then. oauthToken is null after construction,
|
||||
* but can be set or refreshed with calls to {@link #authorize}.
|
||||
*/
|
||||
get oauthToken(): string { return this._oauthToken; }
|
||||
set oauthToken(value: string) { this._oauthToken = value; }
|
||||
|
||||
/**
|
||||
* Get <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">OneDrive Filepicker</a> object. Used to display a Microsoft user's OneDrive files.
|
||||
*/
|
||||
get oneDriveFilepicker(): any { return this._oneDriveFilepicker; }
|
||||
|
||||
/**
|
||||
* Get OAuth 2.0 token for Microsoft OneDrive API requests with a specific Microsoft account. Sets {@link #oauthToken}.
|
||||
* @param {boolean} refreshToken Whether to get a new access 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).
|
||||
*/
|
||||
public authorize(refreshToken?: boolean): Promise<boolean> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (!refreshToken && window.location.hash.indexOf('access_token') !== -1) {
|
||||
const accessToken: string = window.location.hash.substring(window.location.hash.indexOf('=') + 1, window.location.hash.indexOf('&'));
|
||||
storage.oauthToken = accessToken;
|
||||
resolve(true);
|
||||
} else if (refreshToken) {
|
||||
const authUrl: string = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=' + storage.clientId +
|
||||
'&scope=files.readwrite.all&response_type=token&redirect_uri=' + window.location.href + '';
|
||||
window.location.href = authUrl;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the currently logged in Microsoft user. Some fields of particular note include:
|
||||
* - displayName
|
||||
* - givenName
|
||||
* - id
|
||||
* - jobTitle
|
||||
* - userPrincipalName (email)
|
||||
*
|
||||
* **Note:** If {@link #oauthToken} is not valid or has expired, a page redirect to the Microsoft Account sign in will occur.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with information about the currently logged in Microsoft user
|
||||
*/
|
||||
public getUserInfo(): Promise<any> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
if (!storage.oauthToken) {
|
||||
if (window.location.hash.indexOf('access_token') === -1) {
|
||||
reject('No acessToken in current uri');
|
||||
storage.authorize(true);
|
||||
} else {
|
||||
reject('oauthToken not set');
|
||||
storage.authorize(false);
|
||||
}
|
||||
} else {
|
||||
xhr.open('GET', 'https://graph.microsoft.com/v1.0/me');
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + storage.oauthToken);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
resolve(JSON.parse(xhr.response));
|
||||
} else if (xhr.status === 401) { // unauthorized request (expired token)
|
||||
storage.authorize(true);
|
||||
reject(xhr.response);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a file exists at a given path.
|
||||
* @param {string} path A valid MS OneDrive filepath to save current diagram model to
|
||||
* Path must be of the form: `/drive/root:/{item-path}`
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a boolean stating whether a file exists at a given path
|
||||
*/
|
||||
public checkFileExists(path: string): Promise<any> {
|
||||
const storage = this;
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://graph.microsoft.com/v1.0' + path, true);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + storage.oauthToken);
|
||||
xhr.onreadystatechange = function() {
|
||||
let bool; let err;
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
bool = true;
|
||||
} else if (xhr.status === 401) {
|
||||
storage.authorize(true);
|
||||
} else if (xhr.status === 404) {
|
||||
bool = false;
|
||||
} else {
|
||||
err = xhr.response;
|
||||
}
|
||||
resolve(bool);
|
||||
if (err) reject(err);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OneDrive file reference object at a given path. Properties of particular note include:
|
||||
* - name: The name of the file in OneDrive
|
||||
* - id: The OneDrive-given file ID
|
||||
* - parentReference
|
||||
* - path: The path of the parent folder of the file at the provided path
|
||||
*
|
||||
* **Note:** Name, ID, and path are requisite for creating valid {@link DiagramFile}s. A path can be constructed by concatenating the
|
||||
* parentReference.path with name. DiagramFiles optionally also contain parentReference and token values (both used exclusively by GoOneDrive and its methods).
|
||||
* @param {string} path A valid MS OneDrive filepath to save current diagram model to
|
||||
* Path must be of the form: `/drive/root:/{item-path}`
|
||||
* @param {string} token Optional: A token received by OneDrive filepicker (loadDiagramWithUI) to allow for its file to be loaded.
|
||||
* If no token is given, use global oauthToken
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a OneDrive file reference object at a given path
|
||||
*/
|
||||
public getFile(path: string, token?: string): Promise<any> {
|
||||
const storage = this;
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('GET', 'https://graph.microsoft.com/v1.0' + path, true);
|
||||
const t: string = (token) ? token : storage.oauthToken;
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + t);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) { // 200=OK
|
||||
const file: Object = JSON.parse(xhr.response);
|
||||
resolve(file);
|
||||
} else if (xhr.status === 401) { // unauthorized request
|
||||
storage.authorize(true);
|
||||
} else {
|
||||
reject(xhr.response);
|
||||
}
|
||||
}
|
||||
}; // end filexhr
|
||||
xhr.send();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the custom GoOneDrive save prompt {@link #ui}.
|
||||
* @return {Promise<any>} Returns a Promise that resolves (in {@link #save}, {@link #load}, or {@link #remove}) with a {@link DiagramFile}
|
||||
* representing the saved/loaded/deleted file
|
||||
*/
|
||||
public showUI(): Promise<any> {
|
||||
const storage = this;
|
||||
const ui = storage.ui;
|
||||
ui.innerHTML = ''; // clear div
|
||||
ui.style.visibility = 'visible';
|
||||
ui.innerHTML = "<img class='icons' src='" + storage.iconsRelativeDirectory + "oneDrive.png'></img><strong>Save Diagram As</strong><hr></hr>";
|
||||
|
||||
// user input div
|
||||
const userInputDiv: HTMLElement = document.createElement('div');
|
||||
userInputDiv.id = 'userInputDiv';
|
||||
userInputDiv.innerHTML += '<input id="userInput" placeholder="Enter filename"></input>';
|
||||
ui.appendChild(userInputDiv);
|
||||
|
||||
const submitDiv: HTMLElement = document.createElement('div');
|
||||
submitDiv.id = 'submitDiv';
|
||||
const actionButton = document.createElement('button');
|
||||
actionButton.id = 'actionButton';
|
||||
actionButton.textContent = 'Save';
|
||||
actionButton.onclick = function() {
|
||||
storage.saveWithUI();
|
||||
};
|
||||
submitDiv.appendChild(actionButton);
|
||||
ui.appendChild(submitDiv);
|
||||
const cancelDiv: HTMLElement = document.createElement('div');
|
||||
cancelDiv.id = 'cancelDiv';
|
||||
const 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the each {@link #managedDiagrams}' model data to a user's One Drive account, using
|
||||
* <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">One Drive FilePicker</a>.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file.
|
||||
*/
|
||||
public saveWithUI(): Promise<any> {
|
||||
const storage = this;
|
||||
const ui = storage.ui;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (ui.style.visibility === 'hidden') {
|
||||
resolve(storage.showUI());
|
||||
} else {
|
||||
let saveName = (document.getElementById('userInput') as HTMLInputElement).value;
|
||||
if (saveName && saveName.indexOf('.diagram') === -1) saveName += '.diagram';
|
||||
const odOptions: Object = {
|
||||
clientId: storage.clientId,
|
||||
action: 'query',
|
||||
openInNewWindow: true,
|
||||
success: function(selection) {
|
||||
const folder: Object = selection.value[0];
|
||||
const token: string = selection.accessToken;
|
||||
|
||||
storage.currentDiagramFile = {
|
||||
id: null,
|
||||
name: saveName,
|
||||
token: token,
|
||||
parentReference: {
|
||||
driveId: folder['parentReference']['driveId'],
|
||||
id: folder['id']
|
||||
},
|
||||
path: 'placeholder' // will be defined in saveDiagram
|
||||
};
|
||||
storage.hideUI();
|
||||
storage.save();
|
||||
}
|
||||
};
|
||||
if (saveName && saveName !== '' && saveName !== undefined) storage.oneDriveFilepicker.save(odOptions);
|
||||
else reject('Cannot save file to OneDrive with save name ' + saveName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save {@link #managedDiagrams}' model data to Microsoft OneDrive. 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 OneDrive corresponding to currentDiagramFile.path with current managedDiagrams' model data.
|
||||
* @param {string} path A valid MS OneDrive filepath to save current diagram model to. Path syntax is
|
||||
* `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the saved file.
|
||||
*/
|
||||
public save(path?: string): Promise<any> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) { // save as
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
if (path.indexOf('.diagram') === -1) path += '.diagram';
|
||||
const bodyContent: string = storage.makeSaveFile();
|
||||
xhr.open('PUT', 'https://graph.microsoft.com/v1.0' + path + ':/content', true);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + storage.oauthToken);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
const file: Object = JSON.parse(xhr.response);
|
||||
const savedFile: gcs.DiagramFile = {
|
||||
name: file['name'], id: file['id'],
|
||||
path: file['parentReference']['path'] + '/' + file['name'], parentReference: file['parentReference']
|
||||
};
|
||||
resolve(savedFile);
|
||||
} else if (xhr.status === 401) { // unauthorized request
|
||||
storage.authorize(true);
|
||||
} else {
|
||||
throw Error(xhr.response);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(bodyContent);
|
||||
} else if (storage.currentDiagramFile.path) { // save
|
||||
const token: string = storage.currentDiagramFile.token;
|
||||
const url: string = storage.generateGraphUrl(storage.currentDiagramFile, true, true);
|
||||
const bodyContent: string = storage.makeSaveFile();
|
||||
|
||||
const t: string = (!token) ? storage.oauthToken : storage.currentDiagramFile.token;
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('PUT', url, true);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + t);
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201)) {
|
||||
const file: Object = JSON.parse(xhr.response);
|
||||
const savedFile: gcs.DiagramFile = {
|
||||
name: file['name'], id: file['id'],
|
||||
path: file['parentReference']['path'] + '/' + file['name'], token: token, parentReference: file['parentReference']
|
||||
};
|
||||
storage.currentDiagramFile = savedFile;
|
||||
resolve(savedFile); // used if saveDiagram was called without UI
|
||||
|
||||
// if save has been called in processUIResult, need to resolve / reset the Deferred Promise instance variable
|
||||
storage._deferredPromise.promise.resolve(savedFile);
|
||||
storage._deferredPromise.promise = storage.makeDeferredPromise();
|
||||
} else {
|
||||
reject(xhr.response); // failed save
|
||||
}
|
||||
}; // end xhr onload
|
||||
xhr.send(bodyContent);
|
||||
} else {
|
||||
resolve(storage.saveWithUI());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load diagram model data from a given OneDrive-specific file path into {@link #managedDiagrams} using the MS OneDrive filepicker.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public loadWithUI(): Promise<any> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const odOptions: Object = {
|
||||
clientId: storage.clientId,
|
||||
action: 'share',
|
||||
multiSelect: false,
|
||||
advanced: {
|
||||
filter: '.diagram' // only show diagram files
|
||||
},
|
||||
success: function(files: Object) {
|
||||
const file: Object = files['value'][0];
|
||||
const token: string = files['accessToken'];
|
||||
const filePath = file['parentReference']['path'] + '/' + file['name'];
|
||||
resolve(storage.load(filePath, token));
|
||||
}
|
||||
};
|
||||
storage.oneDriveFilepicker.open(odOptions);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the contents of a saved diagram from MS OneDrive to diagram model.
|
||||
* @param {string} path A valid Microsoft OneDrive filepath to load diagram model data from. Path syntax is
|
||||
* `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @param {string} token A token received by <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">OneDrive Filepicker</a>
|
||||
* (passed from {@link #loadWithUI}) to allow for its file to be loaded. If no token is given, use global {@link #oauthToken}
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the loaded file
|
||||
*/
|
||||
public load(path: string, token?: string): Promise<any> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
if (path) {
|
||||
const t: string = (token) ? token : storage.oauthToken;
|
||||
storage.getFile(path, t).then(function(file: Object) {
|
||||
const downloadLink: string = file['@microsoft.graph.downloadUrl'];
|
||||
// Download file from download link
|
||||
const downloadxhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
downloadxhr.open('GET', downloadLink, true);
|
||||
downloadxhr.onreadystatechange = function() {
|
||||
if (downloadxhr.readyState === 4) {
|
||||
if (downloadxhr.status === 200) {
|
||||
storage.loadFromFileContents(downloadxhr.response);
|
||||
const loadedFile: gcs.DiagramFile = {
|
||||
name: file['name'], id: file['id'], path: file['parentReference']['path'] + '/' + file['name'], token: token,
|
||||
parentReference: { id: file['parentReference']['id'], driveId: file['parentReference']['driveId'] }
|
||||
};
|
||||
storage.currentDiagramFile = loadedFile;
|
||||
resolve(loadedFile);
|
||||
}
|
||||
}
|
||||
}; // end downloadxhr
|
||||
downloadxhr.send();
|
||||
});
|
||||
} else reject('Cannot load file from OneDrive with path ' + path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a diagram from a user's OneDrive using the <a href="https://dev.onedrive.com/sdk/js-v72/js-picker-overview.htm">OneDrive Filepicker</a>.
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public removeWithUI(): Promise<any> {
|
||||
const storage = this;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
const odOptions = {
|
||||
clientId: storage.clientId,
|
||||
action: 'share',
|
||||
openInNewWindow: true,
|
||||
success: function(files: Object) {
|
||||
if (files) {
|
||||
const file: Object = files['value'][0];
|
||||
const token: string = files['accessToken'];
|
||||
const filePath: string = file['parentReference']['path'] + '/' + file['name'];
|
||||
resolve(new Promise(function(res: Function, rej: Function) {
|
||||
res(storage.remove(filePath, token));
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
storage.oneDriveFilepicker.open(odOptions);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a diagram from a user's OneDrive at a given path.
|
||||
* @param {string} path A valid Microsoft OneDrive filepath to delete. Path syntax is
|
||||
* `/drive/root:/{path-to-file}/{filename}`; i.e. `/drive/root:/Documents/example.diagram`
|
||||
* @param {string} token A token received by OneDrive filepicker (passed from {@link #removeWithUI}) to allow for
|
||||
* its file to be deleted. If no token is given, use global {@link #oauthToken}
|
||||
* @return {Promise<any>} Returns a Promise that resolves with a {@link DiagramFile} representing the deleted file
|
||||
*/
|
||||
public remove(path: string, token?: string): Promise<any> {
|
||||
const storage = this;
|
||||
const t: string = (token) ? token : storage.oauthToken;
|
||||
return new Promise(function(resolve: Function, reject: Function) {
|
||||
storage.getFile(path, t).then(function(file: Object) {
|
||||
const deletedFile: gcs.DiagramFile = { name: file['name'], id: file['id'], path: file['parentReference']['path'] + '/' + file['name'] };
|
||||
const xhr: XMLHttpRequest = new XMLHttpRequest();
|
||||
xhr.open('DELETE', 'https://graph.microsoft.com/v1.0' + path, true);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer' + t);
|
||||
xhr.onload = function() {
|
||||
if (xhr.readyState === 4 && xhr.status === 204) {
|
||||
if (storage.currentDiagramFile && path === storage.currentDiagramFile.path) storage.currentDiagramFile = { id: null, path: null, name: null };
|
||||
resolve(deletedFile);
|
||||
} else if (xhr.status === 401) { // unauthorized request
|
||||
storage.authorize(true);
|
||||
} else {
|
||||
reject(xhr.response);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}).catch(function(err: string) {
|
||||
throw Error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return a Microsoft Graph URL for a target item
|
||||
* @param {Object} driveItem The item to generate the URL for
|
||||
* @param {Boolean} targetParentFolder Indicates whether to target the parent folder + filename instead of the item itself
|
||||
* @param {Boolean} itemRelativeApiPath Indicates whether to append /content to the item URL
|
||||
* @return {string} Returns a Microsoft Graph URL for a target item
|
||||
*/
|
||||
public generateGraphUrl(driveItem: Object, targetParentFolder: boolean, itemRelativeApiPath: boolean) {
|
||||
let url: string = 'https://graph.microsoft.com/v1.0/';
|
||||
if (targetParentFolder) url += 'drives/' + driveItem['parentReference']['driveId'] + '/items/' + driveItem['parentReference']['id'] + '/children/' + driveItem['name'];
|
||||
else url += 'drives/' + driveItem['parentReference']['driveId'] + '/items/' + driveItem['id'];
|
||||
|
||||
if (itemRelativeApiPath) url += '/content';
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*/
|
||||
|
||||
import * as GoCloudStorage from './GoCloudStorage.js';
|
||||
import * as GoDropBox from './GoDropBox.js';
|
||||
import * as GoGoogleDrive from './GoGoogleDrive.js';
|
||||
import * as GoLocalStorage from './GoLocalStorage.js';
|
||||
import * as GoNetCore from './GoNetCore.js';
|
||||
import * as GoOneDrive from './GoOneDrive.js';
|
||||
|
||||
module.exports = {
|
||||
GoLocalStorage: require('./GoLocalStorage').GoLocalStorage,
|
||||
GoDropBox: require('./GoDropBox').GoDropBox,
|
||||
GoGoogleDrive: require('./GoGoogleDrive').GoGoogleDrive,
|
||||
GoOneDrive: require('./GoOneDrive').GoOneDrive,
|
||||
GoNetCore: require('./GoNetCore').GoNetCore,
|
||||
GoCloudStorageManager: require('./GoCloudStorageManager').GoCloudStorageManager
|
||||
};
|
||||
Reference in New Issue
Block a user