@thalesness/react-native-canvas-scratch-card
Version:
react-native-canvas-scratch-card allows you to scratch to reveal the image background
392 lines (361 loc) • 10.9 kB
JavaScript
"use strict";
import React from "react";
import PropTypes from "prop-types";
import ReactNative, {
requireNativeComponent,
NativeModules,
UIManager,
PanResponder,
PixelRatio,
Platform,
ViewPropTypes,
processColor
} from "react-native";
import { requestPermissions } from "./handlePermissions";
const RNSketchCanvas = requireNativeComponent("RNSketchCanvas", SketchCanvas, {
nativeOnly: {
nativeID: true,
onChange: true
}
});
const SketchCanvasManager = NativeModules.RNSketchCanvasManager || {};
class SketchCanvas extends React.Component {
static propTypes = {
style: ViewPropTypes.style,
strokeColor: PropTypes.string,
fillColor: PropTypes.string,
strokeWidth: PropTypes.number,
onPathsChange: PropTypes.func,
onStrokeStart: PropTypes.func,
onStrokeChanged: PropTypes.func,
onStrokeEnd: PropTypes.func,
onSketchSaved: PropTypes.func,
user: PropTypes.string,
touchEnabled: PropTypes.bool,
text: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string,
font: PropTypes.string,
fontSize: PropTypes.number,
fontColor: PropTypes.string,
overlay: PropTypes.oneOf(["TextOnSketch", "SketchOnText"]),
anchor: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }),
position: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }),
coordinate: PropTypes.oneOf(["Absolute", "Ratio"]),
alignment: PropTypes.oneOf(["Left", "Center", "Right"]),
lineHeightMultiple: PropTypes.number
})
),
localSourceImage: PropTypes.shape({
filename: PropTypes.string,
directory: PropTypes.string,
mode: PropTypes.oneOf(["AspectFill", "AspectFit", "ScaleToFill"])
}),
permissionDialogTitle: PropTypes.string,
permissionDialogMessage: PropTypes.string
};
static defaultProps = {
style: null,
strokeColor: "#000000",
fillColor: "#000000",
strokeWidth: 3,
onPathsChange: () => {},
onStrokeStart: () => {},
onStrokeChanged: () => {},
onStrokeEnd: () => {},
onSketchSaved: () => {},
user: null,
touchEnabled: true,
text: null,
localSourceImage: null,
permissionDialogTitle: "",
permissionDialogMessage: ""
};
state = {
text: null
};
constructor(props) {
super(props);
this._pathsToProcess = [];
this._paths = [];
this._path = null;
this._handle = null;
this._screenScale = Platform.OS === "ios" ? 1 : PixelRatio.get();
this._offset = { x: 0, y: 0 };
this._size = { width: 0, height: 0 };
this._initialized = false;
this.state.text = this._processText(
props.text ? props.text.map(t => Object.assign({}, t)) : null
);
}
componentWillReceiveProps(nextProps) {
this.setState({
text: this._processText(
nextProps.text ? nextProps.text.map(t => Object.assign({}, t)) : null
)
});
}
_processText(text) {
text && text.forEach(t => (t.fontColor = processColor(t.fontColor)));
return text;
}
clear() {
this._paths = [];
this._path = null;
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.clear,
[]
);
}
undo() {
let lastId = -1;
this._paths.forEach(
d => (lastId = d.drawer === this.props.user ? d.path.id : lastId)
);
if (lastId >= 0) this.deletePath(lastId);
return lastId;
}
addPath(data) {
if (this._initialized) {
if (this._paths.filter(p => p.path.id === data.path.id).length === 0)
this._paths.push(data);
const pathData = data.path.data.map(p => {
const coor = p.split(",").map(pp => parseFloat(pp).toFixed(2));
return `${(coor[0] * this._screenScale * this._size.width) /
data.size.width},${(coor[1] * this._screenScale * this._size.height) /
data.size.height}`;
});
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.addPath,
[
data.path.id,
processColor(data.path.color),
data.path.width * this._screenScale,
pathData
]
);
} else {
this._pathsToProcess.filter(p => p.path.id === data.path.id).length ===
0 && this._pathsToProcess.push(data);
}
}
deletePath(id) {
this._paths = this._paths.filter(p => p.path.id !== id);
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.deletePath,
[id]
);
}
save(
imageType,
transparent,
folder,
filename,
includeImage,
includeText,
cropToImageSize
) {
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.save,
[
imageType,
folder,
filename,
transparent,
includeImage,
includeText,
cropToImageSize
]
);
}
getPaths() {
return this._paths;
}
getBase64(
imageType,
transparent,
includeImage,
includeText,
cropToImageSize,
callback
) {
if (Platform.OS === "ios") {
SketchCanvasManager.transferToBase64(
this._handle,
imageType,
transparent,
includeImage,
includeText,
cropToImageSize,
callback
);
} else {
NativeModules.SketchCanvasModule.transferToBase64(
this._handle,
imageType,
transparent,
includeImage,
includeText,
cropToImageSize,
callback
);
}
}
componentWillMount() {
this.panResponder = PanResponder.create({
// Ask to be the responder:
onStartShouldSetPanResponder: (evt, gestureState) => true,
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
if (!this.props.touchEnabled) return;
const e = evt.nativeEvent;
this._offset = { x: e.pageX - e.locationX, y: e.pageY - e.locationY };
this._path = {
id: parseInt(Math.random() * 100000000),
color: this.props.strokeColor,
width: this.props.strokeWidth,
data: []
};
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.newPath,
[
this._path.id,
processColor(this._path.color),
this._path.width * this._screenScale
]
);
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.addPoint,
[
parseFloat(
(gestureState.x0 - this._offset.x).toFixed(2) * this._screenScale
),
parseFloat(
(gestureState.y0 - this._offset.y).toFixed(2) * this._screenScale
)
]
);
const x = parseFloat((gestureState.x0 - this._offset.x).toFixed(2)),
y = parseFloat((gestureState.y0 - this._offset.y).toFixed(2));
this._path.data.push(`${x},${y}`);
this.props.onStrokeStart(x, y);
},
onPanResponderMove: (evt, gestureState) => {
if (!this.props.touchEnabled) return;
if (this._path) {
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.addPoint,
[
parseFloat(
(gestureState.moveX - this._offset.x).toFixed(2) *
this._screenScale
),
parseFloat(
(gestureState.moveY - this._offset.y).toFixed(2) *
this._screenScale
)
]
);
const x = parseFloat(
(gestureState.moveX - this._offset.x).toFixed(2)
),
y = parseFloat((gestureState.moveY - this._offset.y).toFixed(2));
this._path.data.push(`${x},${y}`);
this.props.onStrokeChanged(x, y);
}
},
onPanResponderRelease: (evt, gestureState) => {
if (!this.props.touchEnabled) return;
if (this._path) {
this.props.onStrokeEnd({
path: this._path,
size: this._size,
drawer: this.props.user
});
this._paths.push({
path: this._path,
size: this._size,
drawer: this.props.user
});
}
UIManager.dispatchViewManagerCommand(
this._handle,
UIManager.RNSketchCanvas.Commands.endPath,
[]
);
},
onShouldBlockNativeResponder: (evt, gestureState) => {
return true;
}
});
}
async componentDidMount() {
const isStoragePermissionAuthorized = await requestPermissions(
this.props.permissionDialogTitle,
this.props.permissionDialogMessage
);
}
render() {
return (
<RNSketchCanvas
ref={ref => {
this._handle = ReactNative.findNodeHandle(ref);
}}
style={this.props.style}
onLayout={e => {
this._size = {
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height
};
this._initialized = true;
this._pathsToProcess.length > 0 &&
this._pathsToProcess.forEach(p => this.addPath(p));
}}
{...this.panResponder.panHandlers}
onChange={e => {
if (e.nativeEvent.hasOwnProperty("pathsUpdate")) {
this.props.onPathsChange(e.nativeEvent.pathsUpdate);
} else if (
e.nativeEvent.hasOwnProperty("success") &&
e.nativeEvent.hasOwnProperty("path")
) {
this.props.onSketchSaved(e.nativeEvent.success, e.nativeEvent.path);
} else if (e.nativeEvent.hasOwnProperty("success")) {
this.props.onSketchSaved(e.nativeEvent.success);
}
}}
localSourceImage={this.props.localSourceImage}
fillColor={processColor(this.props.fillColor)}
permissionDialogTitle={this.props.permissionDialogTitle}
permissionDialogMessage={this.props.permissionDialogMessage}
text={this.state.text}
/>
);
}
}
SketchCanvas.MAIN_BUNDLE =
Platform.OS === "ios"
? UIManager.RNSketchCanvas.Constants.MainBundlePath
: "";
SketchCanvas.DOCUMENT =
Platform.OS === "ios"
? UIManager.RNSketchCanvas.Constants.NSDocumentDirectory
: "";
SketchCanvas.LIBRARY =
Platform.OS === "ios"
? UIManager.RNSketchCanvas.Constants.NSLibraryDirectory
: "";
SketchCanvas.CACHES =
Platform.OS === "ios"
? UIManager.RNSketchCanvas.Constants.NSCachesDirectory
: "";
module.exports = SketchCanvas;