forked from pqina/pintura-component-react-native-expo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
PinturaEditor.js
141 lines (124 loc) · 5.28 KB
/
PinturaEditor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { WebView } from 'react-native-webview';
import { View, Platform } from 'react-native';
import React, { forwardRef, useRef, useState, useEffect } from 'react';
import { Asset } from 'expo-asset';
const upperCaseFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1);
// This allows passing functions to webview
const getFunctionParts = (fn) => {
const fnStr = fn.toString();
const params = fnStr.match(/^(?:function [a-z]*)?\(([ ,a-z_0-9]*)/i);
const body = fnStr.match(/^(?:.*?=>|function.*?{)(.+)/is);
return {
body: body ? body[1].replace(/}$/, '').trim() : '',
params: params ? params[1] : '',
};
};
// This replaces undefined values and in outgoing messages so they're not lost
const stringifyMessage = (obj) =>
JSON.stringify(obj, (k, v) => {
if (v === undefined) return '__UNDEFINED__';
if (typeof v === 'function') {
const { params, body } = getFunctionParts(v);
if (!body && !params) return undefined;
return '__FUNCTION__' + params + '____' + body;
}
return v;
});
// This restores undefined values in incoming messages
const deepReplaceValues = (obj, replacer) => {
let out = Array.isArray(obj) ? [...obj] : { ...obj };
Object.entries(out).forEach(([key, value]) => {
if (Array.isArray(value) || typeof value === 'object') {
out[key] = deepReplaceValues(value, replacer);
} else {
out[key] = replacer(key, value);
}
});
return out;
};
const replaceValues = (key, value) => (value === '__UNDEFINED__' ? undefined : value);
const parseMessage = (str) => deepReplaceValues(JSON.parse(str), replaceValues);
/* eslint-disable @typescript-eslint/no-var-requires */
const Editor = forwardRef((props, ref) => {
const { style, styleRules, watermark,...options } = props;
const [source, setSource] = useState({});
const webViewRef = useRef(null);
// this sets up proxy so we can call functions on the editor instance
useEffect(() => {
const handler = {
get: (target, prop) => {
if (prop === 'history') return new Proxy({ group: 'history' }, handler); // eslint-disable-line no-undef
return (...args) => {
const name = [target.group, prop].filter(Boolean).join('.');
webViewRef.current.postMessage(
stringifyMessage({
editorFunction: [name, ...args],
})
);
};
},
};
ref.current.editor = new Proxy({}, handler); // eslint-disable-line no-undef
}, [webViewRef, ref]);
// this passes options to the editor
useEffect(() => {
webViewRef.current.postMessage(stringifyMessage({ editorOptions: options, watermark }));
}, [watermark, webViewRef, options]);
// this passes style rules to the editor
useEffect(() => {
webViewRef.current.postMessage(stringifyMessage({ editorStyleRules: styleRules }));
}, [webViewRef, styleRules]);
// load editor template
useEffect(() => {
const template = require('./bin/pintura.html'); // eslint-disable-line no-undef
Platform.OS === 'android'
? Asset.loadAsync(template).then(([{ localUri }]) => {
setSource({
uri: localUri,
});
})
: setSource(template);
}, []);
return (
<View ref={ref} style={{ ...style, backgroundColor: 'transparent' }}>
<WebView
ref={webViewRef}
style={{ width: '100%', height: '100%', backgroundColor: 'transparent' }}
javaScriptEnabled={true}
scrollEnabled={false}
domStorageEnabled={true}
allowFileAccess={true}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
allowsLinkPreview={false}
allowsInlineMediaPlayback={true}
automaticallyAdjustContentInsets={false}
originWhitelist={['*']}
textZoom={100}
source={source}
onMessage={async (event) => {
// message from WebView
const { type, detail } = parseMessage(event.nativeEvent.data);
// webview ready, lets send over first batch of options
if (type === 'webviewloaded') {
return webViewRef.current.postMessage(
stringifyMessage({
editorStyleRules: styleRules,
editorOptions: options,
watermark
})
);
}
// if is log
if (type === 'log')
return console.log(detail.map((d) => JSON.stringify(d)).join(', ')); // eslint-disable-line no-undef
// get handler
const handler = options[`on${upperCaseFirstLetter(type)}`];
// call handler
handler && handler(detail);
}}
/>
</View>
);
});
export default Editor;