UNPKG

@rbmweb/canvas

Version:

react-native-sketch-canvas allows you to draw / sketch on both iOS and Android devices and sync the drawing data between users. Of course you can save as image.

397 lines (356 loc) 15 kB
import React from "react"; import PropTypes from "prop-types"; import ReactNative, { View, Text, TouchableOpacity, FlatList, ViewPropTypes } from "react-native"; import ImageEditor from "./src/ImageEditor"; import { requestPermissions } from "./src/handlePermissions"; export default class RNImageEditor extends React.Component { static propTypes = { containerStyle: ViewPropTypes.style, canvasStyle: ViewPropTypes.style, onStrokeStart: PropTypes.func, onStrokeChanged: PropTypes.func, onStrokeEnd: PropTypes.func, onClosePressed: PropTypes.func, onUndoPressed: PropTypes.func, onClearPressed: PropTypes.func, onPathsChange: PropTypes.func, user: PropTypes.string, closeComponent: PropTypes.node, eraseComponent: PropTypes.node, undoComponent: PropTypes.node, clearComponent: PropTypes.node, saveComponent: PropTypes.node, deleteSelectedShapeComponent: PropTypes.node, strokeComponent: PropTypes.func, strokeSelectedComponent: PropTypes.func, strokeWidthComponent: PropTypes.func, strokeColors: PropTypes.arrayOf(PropTypes.shape({ color: PropTypes.string })), defaultStrokeIndex: PropTypes.number, defaultStrokeWidth: PropTypes.number, minStrokeWidth: PropTypes.number, maxStrokeWidth: PropTypes.number, strokeWidthStep: PropTypes.number, savePreference: PropTypes.func, onSketchSaved: PropTypes.func, onShapeSelectionChanged: PropTypes.func, shapeConfiguration: PropTypes.shape({ shapeBorderColor: PropTypes.string, shapeBorderStyle: PropTypes.string, shapeBorderStrokeWidth: PropTypes.number, shapeColor: PropTypes.string, shapeStrokeWidth: PropTypes.number }), 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.string }), permissionDialogTitle: PropTypes.string, permissionDialogMessage: PropTypes.string }; static defaultProps = { containerStyle: null, canvasStyle: null, onStrokeStart: () => {}, onStrokeChanged: () => {}, onStrokeEnd: () => {}, onClosePressed: () => {}, onUndoPressed: () => {}, onClearPressed: () => {}, onPathsChange: () => {}, user: null, closeComponent: null, eraseComponent: null, undoComponent: null, clearComponent: null, saveComponent: null, deleteSelectedShapeComponent: null, strokeComponent: null, strokeSelectedComponent: null, strokeWidthComponent: null, strokeColors: [ { color: "#000000" }, { color: "#FF0000" }, { color: "#00FFFF" }, { color: "#0000FF" }, { color: "#0000A0" }, { color: "#ADD8E6" }, { color: "#800080" }, { color: "#FFFF00" }, { color: "#00FF00" }, { color: "#FF00FF" }, { color: "#FFFFFF" }, { color: "#C0C0C0" }, { color: "#808080" }, { color: "#FFA500" }, { color: "#A52A2A" }, { color: "#800000" }, { color: "#008000" }, { color: "#808000" } ], alphlaValues: ["33", "77", "AA", "FF"], defaultStrokeIndex: 0, defaultStrokeWidth: 3, minStrokeWidth: 3, maxStrokeWidth: 15, strokeWidthStep: 3, savePreference: null, onSketchSaved: () => {}, onShapeSelectionChanged: () => {}, shapeConfiguration: { shapeBorderColor: "transparent", shapeBorderStyle: "Dashed", shapeBorderStrokeWidth: 1, shapeColor: "#000000", shapeStrokeWidth: 3 }, text: null, localSourceImage: null, permissionDialogTitle: "", permissionDialogMessage: "" }; constructor(props) { super(props); this.state = { color: props.strokeColors[props.defaultStrokeIndex].color, strokeWidth: props.defaultStrokeWidth, alpha: "FF" }; this._colorChanged = false; this._strokeWidthStep = props.strokeWidthStep; this._alphaStep = -1; } clear() { this._sketchCanvas.clear(); } undo() { return this._sketchCanvas.undo(); } addPath(data) { this._sketchCanvas.addPath(data); } deletePath(id) { this._sketchCanvas.deletePath(id); } deleteSelectedShape() { this._sketchCanvas.deleteSelectedShape(); } unselectShape() { this._sketchCanvas.unselectShape(); } addShape(config) { this._sketchCanvas.addShape(config); } increaseSelectedShapeFontsize() { this._sketchCanvas.increaseSelectedShapeFontsize(); } decreaseSelectedShapeFontsize() { this._sketchCanvas.decreaseSelectedShapeFontsize(); } changeSelectedShapeText(newText) { this._sketchCanvas.changeSelectedShapeText(newText); } save() { if (this.props.savePreference) { const p = this.props.savePreference(); this._sketchCanvas.save( p.imageType, p.transparent, p.folder ? p.folder : "", p.filename, p.includeImage !== false, p.includeText !== false, p.cropToImageSize || false ); } else { const date = new Date(); this._sketchCanvas.save( "png", false, "", date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + ("0" + date.getDate()).slice(-2) + " " + ("0" + date.getHours()).slice(-2) + "-" + ("0" + date.getMinutes()).slice(-2) + "-" + ("0" + date.getSeconds()).slice(-2), true, true, false ); } } nextStrokeWidth() { if ( (this.state.strokeWidth >= this.props.maxStrokeWidth && this._strokeWidthStep > 0) || (this.state.strokeWidth <= this.props.minStrokeWidth && this._strokeWidthStep < 0) ) this._strokeWidthStep = -this._strokeWidthStep; this.setState({ strokeWidth: this.state.strokeWidth + this._strokeWidthStep }); } _renderItem = ({ item, index }) => ( <TouchableOpacity style={{ marginHorizontal: 2.5 }} onPress={() => { if (this.state.color === item.color) { const index = this.props.alphlaValues.indexOf(this.state.alpha); if (this._alphaStep < 0) { this._alphaStep = index === 0 ? 1 : -1; this.setState({ alpha: this.props.alphlaValues[index + this._alphaStep] }); } else { this._alphaStep = index === this.props.alphlaValues.length - 1 ? -1 : 1; this.setState({ alpha: this.props.alphlaValues[index + this._alphaStep] }); } } else { this.setState({ color: item.color }); this._colorChanged = true; } }} > {this.state.color !== item.color && this.props.strokeComponent && this.props.strokeComponent(item.color)} {this.state.color === item.color && this.props.strokeSelectedComponent && this.props.strokeSelectedComponent(item.color + this.state.alpha, index, this._colorChanged)} </TouchableOpacity> ); componentDidUpdate() { this._colorChanged = false; } async componentDidMount() { const isStoragePermissionAuthorized = await requestPermissions( this.props.permissionDialogTitle, this.props.permissionDialogMessage ); } render() { return ( <View style={this.props.containerStyle}> <View style={{ flexDirection: "row" }}> <View style={{ flexDirection: "row", flex: 1, justifyContent: "flex-start" }}> {this.props.closeComponent && ( <TouchableOpacity onPress={() => { this.props.onClosePressed(); }} > {this.props.closeComponent} </TouchableOpacity> )} {this.props.eraseComponent && ( <TouchableOpacity onPress={() => { this.setState({ color: "#00000000" }); }} > {this.props.eraseComponent} </TouchableOpacity> )} {this.props.deleteSelectedShapeComponent && ( <TouchableOpacity style={{ opacity: this.props.touchEnabled ? 0.5 : 1 }} disabled={this.props.touchEnabled} onPress={() => { this.deleteSelectedShape(); }} > {this.props.deleteSelectedShapeComponent} </TouchableOpacity> )} </View> <View style={{ flexDirection: "row", flex: 1, justifyContent: "flex-end" }}> {this.props.strokeWidthComponent && ( <TouchableOpacity onPress={() => { this.nextStrokeWidth(); }} > {this.props.strokeWidthComponent(this.state.strokeWidth)} </TouchableOpacity> )} {this.props.undoComponent && ( <TouchableOpacity onPress={() => { this.props.onUndoPressed(this.undo()); }} > {this.props.undoComponent} </TouchableOpacity> )} {this.props.clearComponent && ( <TouchableOpacity onPress={() => { this.clear(); this.props.onClearPressed(); }} > {this.props.clearComponent} </TouchableOpacity> )} {this.props.saveComponent && ( <TouchableOpacity onPress={() => { this.save(); }} > {this.props.saveComponent} </TouchableOpacity> )} </View> </View> <ImageEditor ref={(ref) => (this._sketchCanvas = ref)} style={this.props.canvasStyle} strokeColor={this.state.color + (this.state.color.length === 9 ? "" : this.state.alpha)} shapeConfiguration={this.props.shapeConfiguration} onStrokeStart={this.props.onStrokeStart} onStrokeChanged={this.props.onStrokeChanged} onStrokeEnd={this.props.onStrokeEnd} user={this.props.user} strokeWidth={this.state.strokeWidth} onSketchSaved={(success, path) => this.props.onSketchSaved(success, path)} onShapeSelectionChanged={(isShapeSelected) => this.props.onShapeSelectionChanged(isShapeSelected)} touchEnabled={this.props.touchEnabled} onPathsChange={this.props.onPathsChange} text={this.props.text} localSourceImage={this.props.localSourceImage} permissionDialogTitle={this.props.permissionDialogTitle} permissionDialogMessage={this.props.permissionDialogMessage} /> <View style={{ flexDirection: "row" }}> <FlatList data={this.props.strokeColors} extraData={this.state} keyExtractor={() => Math.ceil(Math.random() * 10000000).toString()} renderItem={this._renderItem} horizontal showsHorizontalScrollIndicator={false} /> </View> </View> ); } } RNImageEditor.MAIN_BUNDLE = ImageEditor.MAIN_BUNDLE; RNImageEditor.DOCUMENT = ImageEditor.DOCUMENT; RNImageEditor.LIBRARY = ImageEditor.LIBRARY; RNImageEditor.CACHES = ImageEditor.CACHES; export { ImageEditor };