Skip to content

Commit 5ebfebe

Browse files
authored
feat: initialPaths and onInitialPathsLoaded props (#112)
* feat: init `initialPaths` and `onInitialPathsLoaded` prop and listener * perf: draw paths performance improvements * chore: cleanup benchmark logging * refactor: separate examples to new files * docs: update README.md * docs: fixed "build-in" typos
1 parent 3277031 commit 5ebfebe

File tree

19 files changed

+1076
-140
lines changed

19 files changed

+1076
-140
lines changed

README.md

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ A React Native component for drawing by touching on both iOS and Android, with T
2525
- Can draw multiple multiline text on canvas
2626
- Support for custom UI components
2727
- Permission handling for Android image saving
28+
- **Initial paths loading with native batch processing for optimal performance**
29+
- **Real-time path loading feedback with onInitialPathsLoaded callback**
2830

2931
## Installation
3032

@@ -99,6 +101,8 @@ AppRegistry.registerComponent('example', () => Example);
99101
| permissionDialogMessage | `string` | Android Only: Provide a Dialog Message for the Image Saving PermissionDialog. Defaults to empty string if not set |
100102
| onGenerateBase64 | `function` | An optional function which accepts 1 argument `result` containing the base64 string of the canvas. Called when `getBase64()` is invoked. |
101103
| onCanvasReady | `function` | An optional function called when the canvas is ready for interaction. |
104+
| initialPaths | `array` | Array of paths to load into the canvas when it becomes ready. Uses native batch processing for optimal performance. Each path should follow the [Path object](#objects) format. |
105+
| onInitialPathsLoaded | `function` | An optional function which accepts 1 argument `eventData`. Called when `initialPaths` have been processed and loaded into the canvas. `eventData` is an object with `{ loadedCount: number }` property containing the number of paths successfully loaded. |
102106

103107
#### Methods
104108

@@ -113,6 +117,7 @@ AppRegistry.registerComponent('example', () => Example);
113117
| save(imageType, transparent, folder, filename, includeImage, cropToImageSize) | Save image to camera roll or filesystem. If `localSourceImage` is set and a background image is loaded successfully, set `includeImage` to true to include background image and set `cropToImageSize` to true to crop output image to background image.<br/>Android: Save image in `imageType` format with transparent background (if `transparent` sets to True) to **/sdcard/Pictures/`folder`/`filename`** (which is Environment.DIRECTORY_PICTURES).<br/>iOS: Save image in `imageType` format with transparent background (if `transparent` sets to True) to camera roll or file system. If `folder` and `filename` are set, image will save to **temporary directory/`folder`/`filename`** (which is NSTemporaryDirectory()) |
114118
| getPaths() | Get the paths that drawn on the canvas |
115119
| getBase64(imageType, transparent, includeImage, includeText, cropToImageSize) | Get the base64 string of the canvas. The result will be sent through the `onGenerateBase64` event handler. Parameters:<br/>- `imageType`: "png" or "jpg"<br/>- `transparent`: whether to include transparency<br/>- `includeImage`: whether to include background image<br/>- `includeText`: whether to include text<br/>- `cropToImageSize`: whether to crop to background image size |
120+
| setInitialPaths(initialPaths) | Set initial paths to the canvas using native batch processing. This method is called automatically when the `initialPaths` prop is provided, but can also be called manually for dynamic path loading. |
116121

117122
#### Constants
118123

@@ -125,7 +130,7 @@ AppRegistry.registerComponent('example', () => Example);
125130
| LIBRARY | Android: empty string, '' <br/>iOS: equivalent to NSLibraryDirectory |
126131
| CACHES | Android: empty string, '' <br/>iOS: equivalent to NSCachesDirectory |
127132

128-
### ● Using with build-in UI components
133+
### ● Using with built-in UI components
129134

130135
<img src="https://i.imgur.com/O0vVdD6.png" height="400" />
131136

@@ -256,6 +261,8 @@ AppRegistry.registerComponent('example', () => Example);
256261
| savePreference | `function` | A function which is called when saving image and should return an object (see [below](#objects)). |
257262
| onSketchSaved | `function` | See [above](#properties) |
258263
| onCanvasReady | `function` | An optional function called when the canvas is ready for interaction. |
264+
| initialPaths | `array` | Array of paths to load into the canvas when it becomes ready. Uses native batch processing for optimal performance. Each path should follow the [Path object](#objects) format. |
265+
| onInitialPathsLoaded | `function` | An optional function which accepts 1 argument `eventData`. Called when `initialPaths` have been processed and loaded into the canvas. `eventData` is an object with `{ loadedCount: number }` property containing the number of paths successfully loaded. |
259266

260267
#### Methods
261268

@@ -312,6 +319,84 @@ Note: Because native module cannot read the file in JS bundle, file path cannot
312319
- ScaleToFill<br/>
313320
<img src="https://i.imgur.com/r9dRnAC.png" height="200" />
314321

322+
## Initial Paths
323+
324+
---
325+
326+
The `initialPaths` prop allows you to pre-load paths into the canvas when it becomes ready. This feature uses native batch processing for optimal performance, making it ideal for loading saved sketches or collaborative drawing sessions.
327+
328+
### Usage
329+
330+
```tsx
331+
import React from 'react';
332+
import { View } from 'react-native';
333+
import { SketchCanvas } from '@sourcetoad/react-native-sketch-canvas';
334+
import type { Path } from '@sourcetoad/react-native-sketch-canvas';
335+
336+
const savedPaths: Path[] = [
337+
{
338+
path: {
339+
id: 12345,
340+
color: '#FF0000',
341+
width: 5,
342+
data: [
343+
'100.0,100.0',
344+
'150.0,150.0',
345+
'200.0,200.0'
346+
]
347+
},
348+
size: { width: 400, height: 600 }
349+
}
350+
];
351+
352+
export default function Example() {
353+
return (
354+
<View style={{ flex: 1 }}>
355+
<SketchCanvas
356+
style={{ flex: 1 }}
357+
initialPaths={savedPaths}
358+
onInitialPathsLoaded={(eventData) => {
359+
const loadedCount = eventData.loadedCount || 0;
360+
console.log(`Loaded ${loadedCount} paths successfully`);
361+
}}
362+
onCanvasReady={() => {
363+
console.log('Canvas is ready for interaction');
364+
}}
365+
/>
366+
</View>
367+
);
368+
}
369+
```
370+
371+
### Performance Benefits
372+
373+
- **Batch Processing**: Paths are loaded in a single native operation instead of individual calls
374+
- **Optimized Rendering**: Native layer handles path scaling and coordinate conversion efficiently
375+
- **Memory Efficient**: Reduces JavaScript bridge overhead for large path datasets
376+
377+
### Use Cases
378+
379+
- **Saved Sketches**: Restore previously saved drawings
380+
- **Collaborative Drawing**: Load paths from other users in real-time
381+
- **Template Loading**: Pre-populate canvas with template drawings
382+
- **Undo/Redo**: Efficiently restore canvas state
383+
384+
### Event Handling
385+
386+
The `onInitialPathsLoaded` callback provides feedback when initial paths have been processed:
387+
388+
```tsx
389+
onInitialPathsLoaded={(eventData) => {
390+
const loadedCount = eventData.loadedCount || 0;
391+
392+
if (loadedCount > 0) {
393+
console.log(`Successfully loaded ${loadedCount} paths`);
394+
} else {
395+
console.log('No paths were loaded');
396+
}
397+
}}
398+
```
399+
315400
## Objects
316401

317402
---
@@ -412,10 +497,15 @@ Note: Because native module cannot read the file in JS bundle, file path cannot
412497

413498
---
414499

415-
The source code includes 7 examples, using build-in UI components, using with only canvas, and sync between two canvases.
500+
The source code includes 9 examples, using built-in UI components, using with only canvas, sync between two canvases, and performance testing with initial paths loading.
416501

417502
Check full example app in the [example](./example) folder
418503

504+
### New Examples
505+
506+
- **Example 8**: API consistency testing for initial paths loading with various edge cases
507+
- **Example 9**: Performance testing suite for large datasets using the new `initialPaths` feature
508+
419509
### Jest Setup
420510

421511
If you're using Jest in your project, you'll need to mock the TurboModule registry. Add the following to your Jest setup file:
@@ -447,3 +537,22 @@ jest.mock('react-native/Libraries/TurboModule/TurboModuleRegistry', () => {
447537
```
448538

449539
This mock ensures that the native module constants are available during testing.
540+
541+
### TypeScript Support
542+
543+
All types are exported for TypeScript consumers:
544+
545+
```tsx
546+
import type {
547+
ImageType,
548+
OnChangeEventType,
549+
Size,
550+
PathData,
551+
Path,
552+
CanvasText,
553+
SavePreference,
554+
LocalSourceImage,
555+
SketchCanvasProps,
556+
RNSketchCanvasProps,
557+
} from '@sourcetoad/react-native-sketch-canvas';
558+
```

android/src/main/java/com/sourcetoad/reactnativesketchcanvas/RNTSketchCanvasManager.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import com.facebook.react.uimanager.ViewManagerDelegate
1212
import com.facebook.react.uimanager.annotations.ReactProp
1313
import com.facebook.react.viewmanagers.RNTSketchCanvasManagerDelegate
1414
import com.facebook.react.viewmanagers.RNTSketchCanvasManagerInterface
15+
import com.sourcetoad.reactnativesketchcanvas.event.OnCanvasReadyEvent
16+
import com.sourcetoad.reactnativesketchcanvas.event.OnChangeEvent
17+
import com.sourcetoad.reactnativesketchcanvas.event.OnGenerateBase64Event
18+
import com.sourcetoad.reactnativesketchcanvas.event.OnInitialPathsLoadedEvent
1519

1620

1721
@ReactModule(name = RNTSketchCanvasViewManager.NAME)
@@ -68,6 +72,11 @@ class RNTSketchCanvasViewManager :
6872
MapBuilder.of(
6973
"registrationName",
7074
"onCanvasReady"
75+
),
76+
OnInitialPathsLoadedEvent.EVENT_NAME,
77+
MapBuilder.of(
78+
"registrationName",
79+
"onInitialPathsLoaded"
7180
)
7281
)
7382
}
@@ -173,4 +182,8 @@ class RNTSketchCanvasViewManager :
173182
view?.getSketchCanvas()
174183
?.getBase64(imageType, transparent, includeImage, includeText, cropToImageSize)
175184
}
185+
186+
override fun addInitialPaths(view: RNTSketchCanvasView?, pathsArray: ReadableArray?) {
187+
view?.getSketchCanvas()?.addInitialPaths(pathsArray)
188+
}
176189
}

0 commit comments

Comments
 (0)