Commit 3db472a4 authored by wind.wang's avatar wind.wang

init

parents
const fs = require('fs');
const path = require('path');
const parse5 = require('parse5');
const flow = require('lodash/fp/flow');
const babel = require('@babel/core');
const Node = {
map: transform => node => {
const transformed = transform(node);
const {childNodes} = transformed;
return {
...transformed,
childNodes: childNodes && childNodes.map(Node.map(transform)),
};
},
};
const ENTRY = process.argv[2];
const entryPath = require.resolve(ENTRY);
const entryContent = fs.readFileSync(entryPath, 'utf-8');
const newContent = flow([
parse5.parse,
Node.map(node => {
if (node.nodeName === 'script') {
const src = node.attrs.find(attr => attr.name === 'src');
if (src.value) {
const scriptPath = path.resolve(path.dirname(entryPath), src.value);
const scriptContent = babel.transform(fs.readFileSync(scriptPath, 'utf-8'), {
comments: false,
}).code;
const [newScript] = parse5.parseFragment(`<script>${scriptContent}</script>`, node.parent).childNodes;
return newScript;
}
}
return node;
}),
parse5.serialize,
])(entryContent);
fs.writeFileSync(`${entryPath}.js`, `export default \`${newContent}\``);
console.log(`${ENTRY} -> ${ENTRY}.js`);
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}
# See https://help.github.com/ignore-files/ for more about ignoring files.
# expo
.expo/
# dependencies
/node_modules
# misc
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
import React, {Component} from 'react';
import {Image, ScrollView, StatusBar, Text, View, StyleSheet} from 'react-native';
import Canvas, {Image as CanvasImage, Path2D, ImageData} from 'react-native-canvas';
const Example = ({sample, children}) => (
<View style={styles.example}>
<View style={styles.exampleLeft}>{children}</View>
<View style={styles.exampleRight}>
<Image source={sample} style={{width: 100, height: 100}} />
</View>
</View>
);
export default class App extends Component {
handleImageData(canvas) {
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext('2d');
context.fillStyle = 'purple';
context.fillRect(0, 0, 100, 100);
context.getImageData(0, 0, 100, 100)
.then(imageData => {
const data = Object.values(imageData.data);
const length = Object.keys(data).length;
for (let i = 0; i < length; i += 4) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
}
const imgData = new ImageData(canvas, data, 100, 100);
context.putImageData(imgData, 0, 0);
});
}
async handlePurpleRect(canvas) {
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext('2d');
context.fillStyle = 'purple';
context.fillRect(0, 0, 100, 100);
const {width} = await context.measureText('yo');
}
handleRedCircle(canvas) {
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.arc(50, 50, 49, 0, Math.PI * 2, true);
context.fill();
}
handleImageRect(canvas) {
const image = new CanvasImage(canvas);
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext('2d');
image.src = 'https://image.freepik.com/free-vector/unicorn-background-design_1324-79.jpg';
image.addEventListener('load', () => {
context.drawImage(image, 0, 0, 100, 100);
});
}
handlePath(canvas) {
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.fillRect(0, 0, 100, 100);
const ellipse = new Path2D(canvas);
ellipse.ellipse(50, 50, 25, 35, (45 * Math.PI) / 180, 0, 2 * Math.PI);
context.fillStyle = 'purple';
context.fill(ellipse);
context.save();
context.scale(0.5, 0.5);
context.translate(50, 20);
const rectPath = new Path2D(canvas, 'M10 10 h 80 v 80 h -80 Z');
context.fillStyle = 'pink';
context.fill(rectPath);
context.restore();
}
async handleGradient(canvas) {
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext('2d');
const gradient = await ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0, 'green');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 100, 100);
}
/**
* Extracted from https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations
*/
handlePanorama(canvas) {
const CanvasXSize = 100;
const CanvasYSize = 100;
canvas.width = CanvasXSize;
canvas.height = CanvasYSize;
const ctx = canvas.getContext('2d');
const img = new CanvasImage(canvas);
// User Variables - customize these to change the image being scrolled, its
// direction, and the speed.
img.src = 'https://mdn.mozillademos.org/files/4553/Capitan_Meadows,_Yosemite_National_Park.jpg';
const speed = 30; // lower is faster
const scale = 1.05;
const y = -4.5; // vertical offset
// Main program
const dx = 0.75;
let imgW;
let imgH;
let x = 0;
let clearX;
let clearY;
img.addEventListener('load', () => {
imgW = img.width * scale;
imgH = img.height * scale;
if (imgW > CanvasXSize) {
x = CanvasXSize - imgW;
} // image larger than canvas
if (imgW > CanvasXSize) {
clearX = imgW;
} else {
// image width larger than canvas
clearX = CanvasXSize;
}
if (imgH > CanvasYSize) {
clearY = imgH;
} else {
// image height larger than canvas
clearY = CanvasYSize;
}
// set refresh rate
return setInterval(draw, speed);
});
function draw() {
ctx.clearRect(0, 0, clearX, clearY); // clear the canvas
// if image is <= Canvas Size
if (imgW <= CanvasXSize) {
// reset, start from beginning
if (x > CanvasXSize) {
x = -imgW + x;
}
// draw additional image1
if (x > 0) {
ctx.drawImage(img, -imgW + x, y, imgW, imgH);
}
// draw additional image2
if (x - imgW > 0) {
ctx.drawImage(img, -imgW * 2 + x, y, imgW, imgH);
}
} else {
// if image is > Canvas Size
// reset, start from beginning
if (x > CanvasXSize) {
x = CanvasXSize - imgW;
}
// draw additional image
if (x > CanvasXSize - imgW) {
ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);
}
}
// draw image
ctx.drawImage(img, x, y, imgW, imgH);
// amount to move
x += dx;
}
}
handleEmbedHTML(canvas) {
const image = new CanvasImage(canvas);
canvas.width = 100;
canvas.height = 100;
const context = canvas.getContext('2d');
const htmlString = '<b>Hello, World!</b>';
const svgString = `
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml" style="font-size: 40px; background: lightblue; width: 100vw; height: 100vh;">
<span style="background: pink;">
${htmlString}
</span>
</div>
</foreignObject>
</svg>
`;
image.src = `data:image/svg+xml,${encodeURIComponent(svgString)}`;
image.addEventListener('load', () => {
context.drawImage(image, 0, 0, 100, 100);
});
}
render() {
return (
<View style={styles.container}>
<StatusBar hidden={true} />
<ScrollView style={styles.examples}>
<Example sample={require('./images/purple-black-rect.png')}>
<Canvas ref={this.handleImageData} />
</Example>
<Example sample={require('./images/purple-rect.png')}>
<Canvas ref={this.handlePurpleRect} />
</Example>
<Example sample={require('./images/red-circle.png')}>
<Canvas ref={this.handleRedCircle} />
</Example>
<Example sample={require('./images/image-rect.png')}>
<Canvas ref={this.handleImageRect} />
</Example>
<Example sample={require('./images/path.png')}>
<Canvas ref={this.handlePath} />
</Example>
<Example sample={require('./images/gradient.png')}>
<Canvas ref={this.handleGradient} />
</Example>
<Example sample={require('./images/panorama.png')}>
<Canvas ref={this.handlePanorama} />
</Example>
<Example sample={require('./images/embed-html.png')}>
<Canvas ref={this.handleEmbedHTML} />
</Example>
</ScrollView>
</View>
);
}
}
const full = {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
};
const cell = {
flex: 1,
padding: 10,
justifyContent: 'center',
alignItems: 'center',
};
const styles = StyleSheet.create({
container: {
...full,
},
examples: {
...full,
padding: 5,
paddingBottom: 0,
},
example: {
paddingBottom: 5,
flex: 1,
flexDirection: 'row',
},
exampleLeft: {
...cell,
},
exampleRight: {
...cell,
},
});
import React from 'react';
import App from './App';
import renderer from 'react-test-renderer';
it('renders without crashing', () => {
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toBeTruthy();
});
This diff is collapsed.
{
"expo": {
"sdkVersion": "27.0.0"
}
}
{
"name": "example",
"version": "0.1.0",
"private": true,
"devDependencies": {
"jest-expo": "~27.0.0",
"react-native-scripts": "1.14.0",
"react-test-renderer": "16.3.1"
},
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "jest"
},
"jest": {
"preset": "jest-expo"
},
"dependencies": {
"expo": "^27.0.1",
"react": "16.3.1",
"react-native": "~0.55.2",
"react-native-canvas": "^0.1.22"
}
}
This diff is collapsed.
{
"name": "react-native-canvas",
"license": "MIT",
"version": "0.1.26",
"main": "dist/Canvas.js",
"scripts": {
"build": "babel src --out-dir dist --copy-files --compact false && node bundle-html.js ./dist/index.html",
"copy-to-example": "rsync -rv dist example/node_modules/react-native-canvas",
"build-to-example": "npm run build; npm run copy-to-example;",
"prepare": "npm run build"
},
"devDependencies": {
"@babel/cli": "7.0.0-beta.47",
"@babel/core": "7.0.0-beta.47",
"@babel/plugin-proposal-decorators": "7.0.0-beta.47",
"babel-preset-react-native": "^5.0.2",
"eslint": "^3",
"eslint-config-fbjs-opensource": "^1.0.0",
"parse5": "^5.0.0",
"prettier": "^1.14.2"
},
"dependencies": {
"ctx-polyfill": "^1.1.4",
"react-native-webview": "^5.2.0"
},
"repository": "https://github.com/iddan/react-native-canvas"
}
<div align="center">
<img src="https://emojipedia-us.s3.amazonaws.com/thumbs/240/apple/96/fireworks_1f386.png"/>
<h1>react-native-canvas</h1>
</div>
A Canvas component for React Native
```bash
npm install react-native-canvas
```
### Usage
```JSX
import React, { Component } from 'react';
import Canvas from 'react-native-canvas';
class App extends Component {
handleCanvas = (canvas) => {
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'purple';
ctx.fillRect(0, 0, 100, 100);
}
render() {
return (
<Canvas ref={this.handleCanvas}/>
)
}
}
```
### API
#### Canvas
###### Canvas#height
Reflects the height of the canvas in pixels
###### Canvas#width
Reflects the width of the canvas in pixels
###### Canvas#getContext()
Returns a canvas rendering context. Currently only supports 2d context.
###### Canvas#toDataURL()
Returns a `Promise` that resolves to DataURL.
#### CanvasRenderingContext2D
Standard CanvasRenderingContext2D. [MDN](https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D). Only difference is `await` should be used to retrieve values from methods.
```javascript
const ctx = canvas.getContext('2d');
```
#### Image
WebView Image constructor. Unlike in the browsers accepts canvas as first argument. [MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image)
```javascript
const image = new Image(canvas, height, width);
```
/**
* @typedef {Object} Message
* @property {string} id
*/
export default class Bus {
_paused = false;
/**
* @type {Object.<string, function>}
*/
_messageListeners = {};
/**
* @type {Message[]}
*/
_queue = [];
/**
* @param {function} send
*/
constructor(send) {
this._send = send;
}
/**
* @param {Message} message
* @return {Promise.<Message>}
*/
post(message) {
return new Promise(resolve => {
this._messageListeners[message.id] = resolve;
if (!this._paused) {
this._send(message);
} else {
this._queue.push(message);
}
});
}
/**
* @param {Message} message
* @return {void}
*/
handle(message) {
this._messageListeners[message.id](message);
}
/**
* @returns {void}
*/
pause() {
this._paused = true;
}
/**
* @returns {void}
*/
resume() {
this._paused = false;
this._send(this._queue);
this._queue = [];
}
}
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {View, Platform, ViewStylePropTypes} from 'react-native';
import {WebView} from 'react-native-webview';
import Bus from './Bus';
import {webviewTarget, webviewProperties, webviewMethods, constructors, WEBVIEW_TARGET} from './webview-binders';
import CanvasRenderingContext2D from './CanvasRenderingContext2D';
import html from './index.html.js';
export {default as Image} from './Image';
export {default as ImageData} from './ImageData';
export {default as Path2D} from './Path2D';
import './CanvasGradient';
@webviewTarget('canvas')
@webviewProperties({width: 300, height: 150})
@webviewMethods(['toDataURL'])
export default class Canvas extends Component {
static propTypes = {
style: PropTypes.shape(ViewStylePropTypes),
baseUrl: PropTypes.string,
originWhitelist: PropTypes.arrayOf(PropTypes.string),
};
addMessageListener = listener => {
this.listeners.push(listener);
return () => this.removeMessageListener(listener);
};
removeMessageListener = listener => {
this.listeners.splice(this.listeners.indexOf(listener), 1);
};
loaded = false;
/**
* in the mounting process this.webview can be set to null
*/
webviewPostMessage = message => this.webview && this.webview.postMessage(JSON.stringify(message));
bus = new Bus(this.webviewPostMessage);
listeners = [];
context2D = new CanvasRenderingContext2D(this);
constructor() {
super();
this.bus.pause();
}
getContext = (contextType, contextAttributes) => {
switch (contextType) {
case '2d': {
return this.context2D;
}
}
return null;
};
postMessage = async message => {
const {stack} = new Error();
const {type, payload} = await this.bus.post({id: Math.random(), ...message});
switch (type) {
case 'error': {
const error = new Error(payload.message);
error.stack = stack;
throw error;
}
case 'json': {
return payload;
}
case 'blob': {
return atob(payload);
}
}
};
handleMessage = e => {
let data = JSON.parse(e.nativeEvent.data);
switch (data.type) {
case 'log': {
// eslint-disable-line no-console
console.log(...data.payload);
break;
}
case 'error': {
throw new Error(data.payload.message);
}
default: {
if (data.payload) {
const constructor = constructors[data.meta.constructor];
if (constructor) {
const {args, payload} = data;
data = {
...data,
payload: Object.assign(new constructor(this, ...args), payload, {[WEBVIEW_TARGET]: data.meta.target}),
};
}
for (const listener of this.listeners) {
listener(data.payload);
}
}
this.bus.handle(data);
}
}
};
handleRef = element => {
this.webview = element;
};
handleLoad = () => {
this.loaded = true;
this.bus.resume();
};
render() {
const {width, height} = this;
const {style, baseUrl = '', originWhitelist = ['*']} = this.props;
if (Platform.OS === 'android') {
return (
<View style={{width, height, overflow: 'hidden', flex: 0, ...style}}>
<WebView
ref={this.handleRef}
style={{width, height, overflow: 'hidden', backgroundColor: 'transparent'}}
source={{html, baseUrl}}
originWhitelist={originWhitelist}
onMessage={this.handleMessage}
onLoad={this.handleLoad}
mixedContentMode="always"
scalesPageToFit={false}
javaScriptEnabled
domStorageEnabled
thirdPartyCookiesEnabled
allowUniversalAccessFromFileURLs
/>
</View>
);
}
return (
<View style={{width, height, overflow: 'hidden', flex: 0, ...style}}>
<WebView
ref={this.handleRef}
style={{width, height, overflow: 'hidden', backgroundColor: 'transparent'}}
source={{html, baseUrl}}
originWhitelist={originWhitelist}
onMessage={this.handleMessage}
onLoad={this.handleLoad}
scrollEnabled={false}
scalesPageToFit={false}
/>