@magnetman103/react-native-sketch-canvas
Version:
A fork of react native sketch canvas from sourcetoad for modification.
280 lines (262 loc) • 9.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireWildcard(require("react"));
var _reactNative = require("react-native");
var _reactNativeGestureHandler = require("react-native-gesture-handler");
var _DrawCanvas = _interopRequireDefault(require("./DrawCanvas.js"));
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
const SketchContainer = /*#__PURE__*/(0, _react.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 = (0, _react.useRef)([]);
const pathsToProcessRef = (0, _react.useRef)([]);
const currentPathRef = (0, _react.useRef)(null);
// We'll use a single state for size and to trigger redraws when needed
const [size, setSize] = (0, _react.useState)({
width: 0,
height: 0
});
const [, setForceUpdate] = (0, _react.useState)(0); // For forcing re-renders when needed
// Reference to the DrawCanvas component
const canvasRef = (0, _react.useRef)(null);
// Force component to update when paths change
const triggerUpdate = () => {
setForceUpdate(prev => prev + 1);
};
const lastYAmount = (0, _react.useRef)(0);
// Create the pan gesture
const panGesture = _reactNativeGestureHandler.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
(0, _react.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
(0, _react.useImperativeHandle)(ref, () => ({
clear,
undo,
addPath,
deletePath,
getPaths,
save,
getBase64
}));
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
style: styles.container,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
gesture: panGesture,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.container, style],
onLayout: e => {
setSize({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height
});
},
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DrawCanvas.default, {
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 = _reactNative.StyleSheet.create({
container: {
flex: 1
},
canvas: {
flex: 1
}
});
var _default = exports.default = SketchContainer;
//# sourceMappingURL=SketchContainer.js.map