/*
* Copyright (C) 1998-2018 by Northwoods Software Corporation
* All Rights Reserved.
*
* GoCloudStorage.js
*/
import * as go from "../../release/go";
import {Promise} from "es6-promise";
/**
* 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
* Microsoft OneDrive Filepicker 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
* parent information
* 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 removes .resolve() functionality???
}
/**
* An abstract class for storing GoJS Diagram
* models 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: 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 _deferredPromise: DeferredPromise;
private _iconsRelativeDirectory: string;
/**
* @constructor
* @param {go.Diagram|go.Diagram[]} managedDiagrams An array of GoJS Diagrams 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 .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|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 (defaultModel) this._defaultModel = defaultModel;
if (clientId) this._clientId = clientId;
else clientId = null;
this._iconsRelativeDirectory = (!!iconsRelativeDirectory) ? iconsRelativeDirectory : "../goCloudStorageIcons/";
const menu = document.createElement('div');
menu.className = 'goCustomFilepicker';
menu.style.visibility = 'hidden';
document.getElementsByTagName('body')[0].appendChild(menu);
this._ui = menu;
this._deferredPromise = { promise: this.makeDeferredPromise() };
// enable autosaving capability
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 Diagrams associated with this instance of GoCloudStorage.
* Set with a parameter during construction.
* @function.
* @return {go.Diagram[]}
*/
get managedDiagrams(): go.Diagram[] { return this._managedDiagrams }
set managedDiagrams(value: 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
* 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 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 }
/**
*
Get / set deferredPromise property. This is a special property that is simply an Object with a "promise" field, which contains an
* ES6 Promise. 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 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
*
* ```
* @function.
* @return {Object}
*
get deferredPromise(): DeferredPromise { return this._deferredPromise }
set deferredPromise(value: DeferredPromise) { this._deferredPromise = value }*/
/**
*
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} Returns a Promise that resolves with a boolean stating whether authorization was succesful (true) or failed (false).
*/
public authorize(refreshToken: boolean = false) {
return new Promise(function(resolve: Function, reject: Function){
console.error("authorize not implemented");
reject(false);
});
}
/**
* @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