wekan/packages/wekan-cfs-data-man/client/data-man-api.js
2021-04-29 13:26:49 +03:00

302 lines
8.3 KiB
JavaScript

/**
* @method DataMan
* @public
* @constructor
* @param {File|Blob|ArrayBuffer|Uint8Array|String} data The data that you want to manipulate.
* @param {String} [type] The data content (MIME) type, if known. Required if the first argument is an ArrayBuffer, Uint8Array, or URL
*/
DataMan = function DataMan(data, type) {
var self = this;
if (!data) {
throw new Error("DataMan constructor requires a data argument");
}
// The end result of all this is that we will have one of the following set:
// - self.blob
// - self.url
// Unless we already have in-memory data, we don't load anything into memory
// and instead rely on obtaining a read stream when the time comes.
if (typeof File !== "undefined" && data instanceof File) {
self.blob = data; // File inherits from Blob so this is OK
self._type = data.type;
} else if (typeof Blob !== "undefined" && data instanceof Blob) {
self.blob = data;
self._type = data.type;
} else if (typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer || EJSON.isBinary(data)) {
if (typeof Blob === "undefined") {
throw new Error("Browser must support Blobs to handle an ArrayBuffer or Uint8Array");
}
if (!type) {
throw new Error("DataMan constructor requires a type argument when passed an ArrayBuffer or Uint8Array");
}
self.blob = new Blob([data], {type: type});
self._type = type;
} else if (typeof data === "string") {
if (data.slice(0, 5) === "data:") {
self._type = data.slice(5, data.indexOf(';'));
self.blob = dataURItoBlob(data, self._type);
} else if (data.slice(0, 5) === "http:" || data.slice(0, 6) === "https:") {
if (!type) {
throw new Error("DataMan constructor requires a type argument when passed a URL");
}
self.url = data;
self._type = type;
} else {
throw new Error("DataMan constructor received unrecognized data string");
}
} else {
throw new Error("DataMan constructor received data that it doesn't support");
}
};
/**
* @method DataMan.prototype.getBlob
* @public
* @param {Function} [callback] - callback(error, blob)
* @returns {undefined|Blob}
*
* Passes a Blob representing this data to a callback or returns
* the Blob if no callback is provided. A callback is required
* if getting a Blob for a URL.
*/
DataMan.prototype.getBlob = function dataManGetBlob(callback) {
var self = this;
if (callback) {
if (self.blob) {
callback(null, self.blob);
} else if (self.url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', self.url, true);
xhr.responseType = "blob";
xhr.onload = function(data) {
self.blob = xhr.response;
callback(null, self.blob);
};
xhr.onerror = function(err) {
callback(err);
};
xhr.send();
}
} else {
if (self.url)
throw new Error('DataMan.getBlob requires a callback when managing a URL');
return self.blob;
}
};
/**
* @method DataMan.prototype.getBinary
* @public
* @param {Number} [start] - First byte position to read.
* @param {Number} [end] - Last byte position to read.
* @param {Function} callback - callback(error, binaryData)
* @returns {undefined}
*
* Passes a Uint8Array representing this data to a callback.
*/
DataMan.prototype.getBinary = function dataManGetBinary(start, end, callback) {
var self = this;
if (typeof start === "function") {
callback = start;
}
callback = callback || defaultCallback;
function read(blob) {
if (typeof FileReader === "undefined") {
callback(new Error("Browser does not support FileReader"));
return;
}
var reader = new FileReader();
reader.onload = function(evt) {
callback(null, new Uint8Array(evt.target.result));
};
reader.onerror = function(err) {
callback(err);
};
reader.readAsArrayBuffer(blob);
}
self.getBlob(function (error, blob) {
if (error) {
callback(error);
} else {
if (typeof start === "number" && typeof end === "number") {
var size = blob.size;
// Return the requested chunk of binary data
if (start >= size) {
callback(new Error("DataMan.getBinary: start position beyond end of data (" + size + ")"));
return;
}
end = Math.min(size, end);
var slice = blob.slice || blob.webkitSlice || blob.mozSlice;
if (typeof slice === 'undefined') {
callback(new Error('Browser does not support File.slice'));
return;
}
read(slice.call(blob, start, end, self._type));
} else {
// Return the entire binary data
read(blob);
}
}
});
};
/** @method DataMan.prototype.saveAs
* @public
* @param {String} [filename]
* @return {undefined}
*
* Tells the browser to save the data like a normal downloaded file,
* using the provided filename.
*
*/
DataMan.prototype.saveAs = function dataManSaveAs(filename) {
var self = this;
if (typeof window === "undefined")
throw new Error("window must be defined to use saveLocal");
if (!window.saveAs) {
console.warn('DataMan.saveAs: window.saveAs not supported by this browser - add cfs-filesaver package');
return;
}
self.getBlob(function (error, blob) {
if (error) {
throw error;
} else {
window.saveAs(blob, filename);
}
});
};
/**
* @method DataMan.prototype.getDataUri
* @public
* @param {function} callback callback(err, dataUri)
*/
DataMan.prototype.getDataUri = function dataManGetDataUri(callback) {
// XXX: We could consider using: URL.createObjectURL(blob);
// This will create a reference to the blob data instead of a clone
// This is part of the File API - as the rest - Not sure how to generally
// support from IE10, FF26, Chrome 31, safari 7, opera 19, ios 6, android 4
var self = this;
if (typeof callback !== 'function')
throw new Error("getDataUri requires callback function");
if (typeof FileReader === "undefined") {
callback(new Error("Browser does not support FileReader"));
return;
}
var fileReader = new FileReader();
fileReader.onload = function(event) {
var dataUri = event.target.result;
callback(null, dataUri);
};
fileReader.onerror = function(err) {
callback(err);
};
self.getBlob(function (error, blob) {
if (error) {
callback(error);
} else {
fileReader.readAsDataURL(blob);
}
});
};
/**
* @method DataMan.prototype.size
* @public
* @param {function} [callback] callback(err, size)
*
* Passes the size of the data to the callback, if provided,
* or returns it. A callback is required to get the size of a URL on the client.
*/
DataMan.prototype.size = function dataManSize(callback) {
var self = this;
if (callback) {
if (typeof self._size === "number") {
callback(null, self._size);
} else {
self.getBlob(function (error, blob) {
if (error) {
callback(error);
} else {
self._size = blob.size;
callback(null, self._size);
}
});
}
} else {
if (self.url) {
throw new Error("On the client, DataMan.size requires a callback when getting size for a URL on the client");
} else if (typeof self._size === "number") {
return self._size;
} else {
var blob = self.getBlob();
self._size = blob.size;
return self._size;
}
}
};
/**
* @method DataMan.prototype.type
* @public
*
* Returns the type of the data.
*/
DataMan.prototype.type = function dataManType() {
return this._type;
};
/**
* @method dataURItoBlob
* @private
* @param {String} dataURI The data URI
* @param {String} dataTYPE The content type
* @returns {Blob} A new Blob instance
*
* Converts a data URI to a Blob.
*/
function dataURItoBlob(dataURI, dataTYPE) {
var str = atob(dataURI.split(',')[1]), array = [];
for(var i = 0; i < str.length; i++) array.push(str.charCodeAt(i));
return new Blob([new Uint8Array(array)], {type: dataTYPE});
}
/**
* @method defaultCallback
* @private
* @param {Error} [err]
* @returns {undefined}
*
* Can be used as a default callback for client methods that need a callback.
* Simply throws the provided error if there is one.
*/
function defaultCallback(err) {
if (err) {
// Show gentle error if Meteor error
if (err instanceof Meteor.Error) {
console.error(err.message);
} else {
// Normal error, just throw error
throw err;
}
}
}