react-native-mask-segment-c.../App.tsx
a1518 8bc66a4ee9
Some checks failed
Publish to npm / publish (push) Has been cancelled
refactor: remove Chinese, add build obfuscation, polish README
- Remove all Chinese characters from src/, example/, ios/, patches/, tests/
- Add esbuild-based build obfuscation (minify + identifier mangle + no sourcemaps)
- Drop src/ from npm publish, only ship minified dist/
- Remove source maps and declaration maps from build output
- Add README icons and visual polish throughout
- Fix broken Table of Contents anchor links in README

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 19:07:32 -07:00

222 lines
6.2 KiB
TypeScript

/**
* Mask Segment Demo App
* Mask segmentation canvas based on OpenCV + Skia
*
* Note: This is a demo for library development testing, directly referencing ./src.
* For business project integration demonstration, please refer to the example/ directory (import using the public package name).
*/
import React, { useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
StatusBar,
StyleSheet,
Text,
View,
} from 'react-native';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
import MaskSegmentCanvas, {
type MaskSegmentCanvasRef,
type MaskSegmentSession,
type MaskSegmentWatchState,
MASK_SEMANTIC_COLORS,
} from './src';
import { resolveAssetPath } from './src/utils/resolveAssetPath';
import { prewarmPngBgrCacheAsync } from './src/utils/pngImage';
const TEST_ORIGIN = require('./assets/test/origin.png');
const TEST_MASK = require('./assets/test/mask.png');
const INTERACTIVE_WATCH_STATES: MaskSegmentWatchState[] = [
'interactive',
'mask_paths_ready',
];
function formatWatchStatus(state: MaskSegmentWatchState | ''): string {
if (!state) {
return '';
}
if (state === 'interactive') {
return 'Colorable (outline loading...)';
}
if (state === 'mask_paths_ready') {
return 'Ready';
}
if (state === 'error') {
return 'Failed';
}
return `Loading: ${state}`;
}
function App(): React.JSX.Element {
const canvasRef = useRef<MaskSegmentCanvasRef>(null);
const [testPaths, setTestPaths] = useState<{
origin: string;
mask: string;
} | null>(null);
const [loadError, setLoadError] = useState('');
const [watchState, setWatchState] = useState<MaskSegmentWatchState | ''>('');
const [sessionDraft, setSessionDraft] = useState<MaskSegmentSession | null>(
null,
);
const isFullyReady = watchState === 'mask_paths_ready';
const isInitLoading =
testPaths != null &&
watchState !== '' &&
!INTERACTIVE_WATCH_STATES.includes(watchState as MaskSegmentWatchState) &&
watchState !== 'error';
useEffect(() => {
let cancelled = false;
(async () => {
try {
const [origin, mask] = await Promise.all([
resolveAssetPath(TEST_ORIGIN, 'gym_test_origin.png'),
resolveAssetPath(TEST_MASK, 'gym_test_mask.png'),
]);
await prewarmPngBgrCacheAsync([origin, mask]);
if (!cancelled) {
setTestPaths({ origin, mask });
}
} catch (e) {
if (!cancelled) {
setLoadError(e instanceof Error ? e.message : String(e));
}
}
})();
return () => {
cancelled = true;
};
}, []);
return (
<SafeAreaProvider>
<SafeAreaView style={styles.root}>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
{loadError ? (
<View style={styles.center}>
<Text style={styles.errorText}>{loadError}</Text>
</View>
) : testPaths ? (
<>
{watchState ? (
<Text style={styles.watchText}>
Status: {formatWatchStatus(watchState)}
{isFullyReady ? ' · Carousel dashed line ready' : null}
</Text>
) : null}
<View style={styles.canvasHost}>
<MaskSegmentCanvas
ref={canvasRef}
originUrl={testPaths.origin}
maskUrl={testPaths.mask}
semanticColors={MASK_SEMANTIC_COLORS}
regionOutlineColor="rgba(20, 120, 235, 0.58)"
showDebugPickers
initialSession={sessionDraft ?? undefined}
onWatch={(state, durationMs, detail) => {
setWatchState(state);
if (__DEV__) {
const extra =
detail && Object.keys(detail).length > 0
? ` ${JSON.stringify(detail)}`
: '';
console.log(
`[Demo onWatch] ${state} ${durationMs.toFixed(0)}ms${extra}`,
);
}
}}
onPaintCallback={payload => {
if (__DEV__) {
if (payload.kind === 'brush_required') {
console.log('[Demo onPaint]', payload.hint, payload.regionName);
} else {
console.log('[Demo onPaint]', payload.regionName, payload.color);
}
}
}}
onError={message => {
setLoadError(message);
setWatchState('error');
}}
/>
{isInitLoading ? (
<View style={styles.initOverlay} pointerEvents="none">
<ActivityIndicator size="small" color="#333" />
<Text style={styles.initOverlayText}>
{formatWatchStatus(watchState)}
</Text>
</View>
) : null}
</View>
{sessionDraft ? (
<Text style={styles.sessionText}>
Restored MMKV draft ({sessionDraft.painted.length} regions)
</Text>
) : null}
</>
) : (
<View style={styles.center}>
<ActivityIndicator size="large" color="#333" />
<Text style={styles.loadingText}>Loading gym test image...</Text>
</View>
)}
</SafeAreaView>
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
root: {
flex: 1,
backgroundColor: '#fff',
},
center: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
padding: 24,
},
loadingText: {
marginTop: 12,
color: '#666',
fontSize: 14,
},
errorText: {
color: '#c33',
fontSize: 14,
textAlign: 'center',
},
watchText: {
paddingHorizontal: 12,
paddingTop: 8,
color: '#555',
fontSize: 12,
},
canvasHost: {
flex: 1,
position: 'relative',
},
initOverlay: {
...StyleSheet.absoluteFillObject,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'rgba(255, 255, 255, 0.72)',
gap: 8,
},
initOverlayText: {
color: '#666',
fontSize: 13,
},
sessionText: {
paddingHorizontal: 12,
paddingBottom: 8,
color: '#2a7',
fontSize: 12,
},
});
export default App;