react-native-canvas
Version:
A Canvas component for React Native
338 lines (337 loc) • 11.2 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var WEBVIEW_TARGET = "@@WEBVIEW_TARGET";
var ID = function () { return Math.random().toString(32).slice(2); };
var flattenObjectCopyValue = function (flatObj, srcObj, key) {
var value = srcObj[key];
if (typeof value === "function") {
return;
}
if (typeof value === "object" && value instanceof Node) {
return;
}
flatObj[key] = flattenObject(value);
};
var flattenObject = function (object) {
if (typeof object !== "object" || object === null) {
return object;
}
var flatObject = {};
for (var key in object) {
flattenObjectCopyValue(flatObject, object, key);
}
for (var key in Object.getOwnPropertyNames(object)) {
flattenObjectCopyValue(flatObject, object, key);
}
return flatObject;
};
var AutoScaledCanvas = /** @class */ (function () {
function AutoScaledCanvas(element) {
this.element = element;
}
AutoScaledCanvas.prototype.toDataURL = function () {
var _a;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return (_a = this.element).toDataURL.apply(_a, args);
};
AutoScaledCanvas.prototype.autoScale = function () {
if (this.savedHeight !== undefined) {
this.element.height = this.savedHeight;
}
if (this.savedWidth !== undefined) {
this.element.width = this.savedWidth;
}
window.autoScaleCanvas(this.element);
};
Object.defineProperty(AutoScaledCanvas.prototype, "width", {
get: function () {
return this.element.width;
},
set: function (value) {
this.savedWidth = value;
this.autoScale();
return value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(AutoScaledCanvas.prototype, "height", {
get: function () {
return this.element.height;
},
set: function (value) {
this.savedHeight = value;
this.autoScale();
return value;
},
enumerable: false,
configurable: true
});
return AutoScaledCanvas;
}());
var toMessage = function (result) {
if (result instanceof Blob) {
return {
type: "blob",
payload: btoa(result),
meta: {},
};
}
if (result instanceof Object) {
if (!result[WEBVIEW_TARGET]) {
var id = ID();
result[WEBVIEW_TARGET] = id;
targets[id] = result;
}
return {
type: "json",
payload: flattenObject(result),
args: toArgs(flattenObject(result)),
meta: {
target: result[WEBVIEW_TARGET],
constructor: result.__constructorName__ || result.constructor.name,
},
};
}
return {
type: "json",
payload: JSON.stringify(result),
meta: {},
};
};
/**
* Gets the all the args required for creating the object.
* Also converts typed arrays to normal arrays.
*
* For example with ImageData we need a Uint8ClampedArray,
* but if we sent it as JSON it will be sent as an object
* not an array. So we convert any typed arrays into arrays
* first, they will be converted to Uint8ClampedArrays in
* `webview-binders.js`.
*
*/
var toArgs = function (result) {
var args = [];
for (var key in result) {
if (result[key] !== undefined && key !== "@@WEBVIEW_TARGET") {
if (typedArrays[result[key].constructor.name] !== undefined) {
result[key] = Array.from(result[key]);
}
args.push(result[key]);
}
}
return args;
};
/**
* Creates objects from args. If any argument have the object
* which contains `className` it means we need to convert that
* argument into an object.
*
* We need to do this because when we pass data between the WebView
* and RN using JSON, it strips/removes the class data from the object.
* So this will raise errors as the WebView will expect arguments to be
* of a certain class.
*
* For example for ImageData we expect to receive
* [{className: Uint8ClampedArray, classArgs: [Array(4)]}, 100, 100]
* We need to convert the first parameter into an object first.
*
*/
var createObjectsFromArgs = function (args) {
var _a;
for (var index = 0; index < args.length; index += 1) {
var currentArg = args[index];
if (currentArg && currentArg.className !== undefined) {
var className = currentArg.className, classArgs = currentArg.classArgs;
var object = new ((_a = constructors[className]).bind.apply(_a, __spreadArray([void 0], classArgs, false)))();
args[index] = object;
}
}
return args;
};
// const print = (...args) => {
// const message = JSON.stringify({
// type: 'log',
// payload: args,
// });
// window.ReactNativeWebView.postMessage(message);
// };
var canvas = document.createElement("canvas");
var autoScaledCanvas = new AutoScaledCanvas(canvas);
var targets = {
canvas: autoScaledCanvas,
context2D: canvas.getContext("2d"),
};
var constructors = {
Image: Image,
Path2D: Path2D,
CanvasGradient: CanvasGradient,
ImageData: ImageData,
Uint8ClampedArray: Uint8ClampedArray,
};
var typedArrays = {
Uint8ClampedArray: Uint8ClampedArray,
};
/**
* In iOS 9 constructors doesn't have bind defined which fails
* Babel object constructors utility function
*/
Image.bind =
Image.bind ||
function () {
return Image;
};
Path2D.bind =
Path2D.bind ||
function () {
return Path2D;
};
ImageData.bind =
ImageData.bind ||
function () {
return ImageData;
};
Uint8ClampedArray.bind =
Uint8ClampedArray.bind ||
function () {
return Uint8ClampedArray;
};
var populateRefs = function (arg) {
if (arg && arg.__ref__) {
return targets[arg.__ref__];
}
return arg;
};
document.body.appendChild(canvas);
/**
* NOTE: Depending on the message type, the message sender will potentially get a callback via
* window.ReactNativeWebView.postMessage(...). The postMessage function causes Bus to resolve
* a Promise to the caller.
*
* For example, ctx.fillRect(...) returns a Promise that is then resolved from the code below.
*
* 'set' is currently the exception - it doesn't resolve at all.
* Therefore, Bus should not be saving message ids for 'set' messages.
* See the function 'post' in Bus.js.
*/
function handleMessage(_a) {
var _b, _c;
var id = _a.id, type = _a.type, payload = _a.payload;
switch (type) {
case "exec": {
var target = payload.target, method = payload.method, args = payload.args;
var result = (_b = targets[target])[method].apply(_b, args.map(populateRefs));
var message = toMessage(result);
/**
* In iOS 9 some classes name are not defined so we compare to
* known constructors to find the name.
*/
if (typeof result === "object" && !message.meta.constructor) {
for (var constructorName in constructors) {
if (result instanceof constructors[constructorName]) {
message.meta.constructor = constructorName;
}
}
}
window.ReactNativeWebView.postMessage(JSON.stringify(__assign({ id: id }, message)));
break;
}
case "set": {
var target = payload.target, key = payload.key, value = payload.value;
targets[target][key] = populateRefs(value);
break;
}
case "construct": {
var constructor = payload.constructor, target = payload.id, _d = payload.args, args = _d === void 0 ? [] : _d;
var newArgs = createObjectsFromArgs(args);
var object = void 0;
try {
object = new ((_c = constructors[constructor]).bind.apply(_c, __spreadArray([void 0], newArgs, false)))();
}
catch (error) {
throw new Error("Error while constructing ".concat(constructor, " ").concat(error.message));
}
object.__constructorName__ = constructor;
var message = toMessage({});
targets[target] = object;
window.ReactNativeWebView.postMessage(JSON.stringify(__assign({ id: id }, message)));
break;
}
case "listen": {
var types = payload.types, target_1 = payload.target;
for (var _i = 0, types_1 = types; _i < types_1.length; _i++) {
var eventType = types_1[_i];
targets[target_1].addEventListener(eventType, function (e) {
var _a;
var message = toMessage({
type: "event",
payload: {
type: e.type,
target: __assign(__assign({}, flattenObject(targets[target_1])), (_a = {}, _a[WEBVIEW_TARGET] = target_1, _a)),
},
});
window.ReactNativeWebView.postMessage(JSON.stringify(__assign({ id: id }, message)));
});
}
break;
}
}
}
var handleError = function (err, message) {
window.ReactNativeWebView.postMessage(JSON.stringify({
id: message.id,
type: "error",
payload: {
message: err.message,
stack: err.stack,
},
}));
document.removeEventListener("message", handleIncomingMessage);
};
function handleIncomingMessage(e) {
var data = JSON.parse(e.data);
if (Array.isArray(data)) {
for (var _i = 0, data_1 = data; _i < data_1.length; _i++) {
var message = data_1[_i];
try {
handleMessage(message);
}
catch (err) {
handleError(err, message);
}
}
}
else {
try {
handleMessage(data);
}
catch (err) {
handleError(err, data);
}
}
}
// iOS
window.addEventListener("message", handleIncomingMessage);
// Android
document.addEventListener("message", handleIncomingMessage);