@magnetman103/react-native-sketch-canvas
Version:
A fork of react native sketch canvas from sourcetoad for modification.
274 lines (256 loc) • 8.08 kB
JavaScript
;
import React, { useRef, useState, forwardRef, useImperativeHandle, useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
import DrawCanvas from "./DrawCanvas.js";
import { jsx as _jsx } from "react/jsx-runtime";
const SketchContainer = /*#__PURE__*/forwardRef((props, ref) => {
const {
strokeColor,
strokeWidth,
style,
user,
onStrokeStart,
onStrokeChanged,
onStrokeEnd,
onPathsChange,
onSketchSaved,
onGenerateBase64,
onCanvasReady,
touchEnabled,
scrollY,
eraserOn
} = props;
// Use refs instead of state for path data (similar to original implementation)
const pathsRef = useRef([]);
const pathsToProcessRef = useRef([]);
const currentPathRef = useRef(null);
// We'll use a single state for size and to trigger redraws when needed
const [size, setSize] = useState({
width: 0,
height: 0
});
const [, setForceUpdate] = useState(0); // For forcing re-renders when needed
// Reference to the DrawCanvas component
const canvasRef = useRef(null);
// Force component to update when paths change
const triggerUpdate = () => {
setForceUpdate(prev => prev + 1);
};
const lastYAmount = useRef(0);
// Create the pan gesture
const panGesture = Gesture.Pan().onStart(event => {
if (!event.stylusData && !touchEnabled || eraserOn) {
return;
}
const newPath = {
id: parseInt(String(Math.random() * 100000000), 10),
color: strokeColor,
width: strokeWidth,
data: []
};
const x = parseFloat(event.x.toFixed(2));
const y = parseFloat(event.y.toFixed(2));
newPath.data.push(`${x},${y}`);
currentPathRef.current = newPath;
// Start path in the DrawCanvas
if (canvasRef.current) {
canvasRef.current.newPath(newPath.id, newPath.color, newPath.width);
canvasRef.current.addPoint(x, y);
}
onStrokeStart?.(x, y);
triggerUpdate(); // Force update to render current path
}).onUpdate(event => {
const x = parseFloat(event.x.toFixed(2));
const y = parseFloat(event.y.toFixed(2));
if (eraserOn && (event.stylusData || touchEnabled)) {
// only fire if stylus is used and eraser is on
onStrokeChanged?.(x, y);
return;
}
// handle case when touch is enabled and eraser is on? (future)
if (!event.stylusData && !touchEnabled || !currentPathRef.current) {
if (scrollY) {
// If scrollY is provided, call it with the current scroll position
scrollY(lastYAmount.current - event.translationY);
lastYAmount.current = event.translationY;
}
return;
}
// Add point to current path
if (currentPathRef.current) {
currentPathRef.current.data.push(`${x},${y}`);
// Add point directly to the canvas
if (canvasRef.current) {
canvasRef.current.addPoint(x, y);
}
}
onStrokeChanged?.(x, y);
}).onEnd(event => {
if (eraserOn && (event.stylusData || touchEnabled)) {
onStrokeEnd?.({
path: {
id: -1,
color: strokeColor,
width: strokeWidth,
data: []
},
size: {
width: 0,
height: 0
},
drawer: null
});
return;
}
if (!event.stylusData && !touchEnabled || !currentPathRef.current) {
if (scrollY) {
scrollY(0, event.velocityY);
lastYAmount.current = 0;
}
return;
}
if (currentPathRef.current) {
// Create path for internal tracking
const newPath = {
path: currentPathRef.current,
size,
drawer: user
};
// Add to paths array
pathsRef.current = [...pathsRef.current, newPath];
// Create properly typed object for the callback
const strokeEndData = {
path: currentPathRef.current,
size,
drawer: user || null
};
// End the path in the canvas
if (canvasRef.current) {
canvasRef.current.endPath();
}
onStrokeEnd?.(strokeEndData);
}
currentPathRef.current = null;
triggerUpdate(); // Force update after completing the path
}).minDistance(1).runOnJS(true);
// Methods that can be called from outside
const clear = () => {
pathsRef.current = [];
pathsToProcessRef.current = [];
currentPathRef.current = null;
if (canvasRef.current) {
canvasRef.current.clear();
}
triggerUpdate();
};
const undo = () => {
let lastId = -1;
pathsRef.current.forEach(d => lastId = d.drawer === user ? d.path.id : lastId);
if (lastId >= 0) {
pathsRef.current = pathsRef.current.filter(p => p.path.id !== lastId);
if (canvasRef.current) {
canvasRef.current.deletePath(lastId);
}
triggerUpdate();
}
return lastId;
};
const addPath = data => {
// Check if path already exists
if (pathsRef.current.filter(p => p.path.id === data.path.id).length === 0) {
// Add to paths array
pathsRef.current = [...pathsRef.current, data];
// Add to processing queue
pathsToProcessRef.current = [...pathsToProcessRef.current, data];
// Process the path directly
if (canvasRef.current && size.width > 0 && size.height > 0) {
canvasRef.current.addPath(data);
}
triggerUpdate();
}
};
const deletePath = id => {
pathsRef.current = pathsRef.current.filter(p => p.path.id !== id);
pathsToProcessRef.current = pathsToProcessRef.current.filter(p => p.path.id !== id);
if (canvasRef.current) {
canvasRef.current.deletePath(id);
}
triggerUpdate();
};
const save = (imageType, transparent, folder, filename, includeImage, includeText, cropToImageSize) => {
if (canvasRef.current) {
canvasRef.current.save(imageType, transparent, folder, filename, includeImage, includeText, cropToImageSize);
}
};
const getBase64 = (imageType, transparent, includeImage, includeText, cropToImageSize) => {
if (canvasRef.current) {
canvasRef.current.getBase64(imageType, transparent, includeImage, includeText, cropToImageSize);
}
};
const getPaths = () => {
return pathsRef.current;
};
// Process any paths that need processing after layout
useEffect(() => {
if (size.width > 0 && size.height > 0 && pathsToProcessRef.current.length > 0 && canvasRef.current) {
pathsToProcessRef.current.forEach(path => {
canvasRef.current?.addPath(path);
});
// Clear the processing queue
pathsToProcessRef.current = [];
}
}, [size]);
// Expose methods to parent components
useImperativeHandle(ref, () => ({
clear,
undo,
addPath,
deletePath,
getPaths,
save,
getBase64
}));
return /*#__PURE__*/_jsx(GestureHandlerRootView, {
style: styles.container,
children: /*#__PURE__*/_jsx(GestureDetector, {
gesture: panGesture,
children: /*#__PURE__*/_jsx(View, {
style: [styles.container, style],
onLayout: e => {
setSize({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height
});
},
children: /*#__PURE__*/_jsx(DrawCanvas, {
ref: canvasRef,
style: styles.canvas,
strokeColor: strokeColor,
strokeWidth: strokeWidth,
paths: pathsRef.current,
pathsToProcess: pathsToProcessRef.current,
currentPath: currentPathRef.current,
onPathsChange: onPathsChange,
onStrokeStart: onStrokeStart,
onStrokeChanged: onStrokeChanged,
onStrokeEnd: onStrokeEnd,
onSketchSaved: onSketchSaved,
onGenerateBase64: onGenerateBase64,
onCanvasReady: onCanvasReady,
user: user
})
})
})
});
});
const styles = StyleSheet.create({
container: {
flex: 1
},
canvas: {
flex: 1
}
});
export default SketchContainer;
//# sourceMappingURL=SketchContainer.js.map