@s20.ai/safecam-360-rn-lite
Version:
A React Native component to view the interior panorama and exterior 360 degree views of a product featuring suppport for hotspots. Compatible with Android and iOS
307 lines (283 loc) • 8.99 kB
JavaScript
/*
* Copyright © 2022 S20.AI India Pvt. Ltd.
* Contact - origin@s20.ai
* License - Licensed as per https://docs.s20.ai/license.html
*/
import React, {useEffect, useRef} from 'react';
import {View, Text, StyleSheet, PanResponder} from 'react-native';
import {WebView} from 'react-native-webview';
import {useDispatch, connect} from 'react-redux';
import constants from '../../config/constants';
import {setCurrentHotspot} from '../../redux/actions/currentHotspotAction';
import {triggerHapticFeedback} from '../../utils/device';
import {setWidgetsVisible} from '../../redux/actions/currentViewAction';
const Interior360 = ({
interiorData,
currentHotspotState,
hotspotMode,
enableHotspots,
currentViewState,
hotspotPressed,
customInteriorUrl,
onInteraction,
miniMode,
interior360InitialPosition,
}) => {
const injectedJs = data => `
window.postMessage(${JSON.stringify(data)});
true;
`;
let webref = useRef(null);
let isZooming = false;
let lastPress = 0;
const dispatch = useDispatch();
useEffect(() => {
// initializeWebview()
}, []);
useEffect(() => {
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({currentView: currentViewState.type}),
);
}
}, [currentViewState]);
const messageHandler = e => {
if (e && e.nativeEvent.data) {
try {
let message = JSON.parse(e.nativeEvent.data);
if (message && message.hasOwnProperty('error')) {
console.log(message.error);
if (interiorData.hasOwnProperty('imageSourceCompressed')) {
//if image fails to load on device, it is most likely due to a device limitation
// pass compressed image to interior viewer incase this happens
interiorData.imageSource = interiorData.imageSourceCompressed;
webref.current.injectJavaScript(injectedJs({data: interiorData}));
}
}
// if(message){
// console.log('message')
// console.log(message)
// }
if (message && message.hasOwnProperty('panellumAction')) {
if (
message.panellumAction === constants.INTERACTION_TYPES.DOUBLE_TAP
) {
if (onInteraction) {
onInteraction({
currentView: constants.INTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.DOUBLE_TAP,
},
});
}
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({hotspotMode: 'show_all_hotspots'}),
);
}
}
if (
message.panellumAction === constants.INTERACTION_TYPES.TAP ||
message.panellumAction === 'click'
) {
dispatch(setWidgetsVisible(true));
}
}
if (message && message.hasOwnProperty('click')) {
if (hotspotPressed) {
hotspotPressed({
currentView: constants.INTERIOR_VIEW,
hotspot: {
type: message.hotspot.hotspotType,
title: message.hotspot.imageTitle,
description: message.hotspot.description,
},
});
}
dispatch(
setCurrentHotspot({
...message.hotspot,
address: message.hotspot.address,
}),
);
triggerHapticFeedback();
}
} catch (err) {
console.log(err);
console.log(e);
}
}
};
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 0,
left: 0,
maxHeight: miniMode ? 233 : '100%',
height: '100%',
width: '100%',
},
});
useEffect(() => {
if (webref && webref.current) {
webref.current.injectJavaScript(injectedJs({hotspotMode: hotspotMode}));
}
}, [hotspotMode]);
useEffect(() => {
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({currentHotspot: currentHotspotState}),
);
}
}, [currentHotspotState]);
const processPinch = () => {
if (!isZooming) {
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({hotspotMode: 'show_all_hotspots'}),
);
}
dispatch(setWidgetsVisible(false));
if (onInteraction) {
onInteraction({
currentView: constants.INTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.ZOOM_START,
},
});
}
isZooming = true;
}
};
const panResponder = React.useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => {
const {dx, dy} = gestureState;
return dx > 2 || dx < -2 || dy > 2 || dy < -2;
},
onPanResponderGrant: (evt, gestureState) => {
let touches = evt.nativeEvent.touches;
if (touches.length == 1) {
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({hotspotMode: 'show_all_hotspots'}),
);
}
if (onInteraction) {
onInteraction({
currentView: constants.INTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.DRAG_START,
},
});
}
}
},
onPanResponderMove: (evt, gestureState) => {
// if(webref && webref.current){
// webref.current.injectJavaScript(injectedJs({"gesture":"move"}));
// }
let touches = evt.nativeEvent.touches;
if (touches.length == 2) {
processPinch();
}
},
onPanResponderRelease: (evt, gestureState) => {
if (isZooming) {
if (onInteraction) {
onInteraction({
currentView: constants.INTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.ZOOM_END,
},
});
}
isZooming = false;
} else {
// if(webref && webref.current){
// webref.current.injectJavaScript(injectedJs({"gesture":"moveEnd"}));
// }
if (onInteraction) {
onInteraction({
currentView: constants.INTERIOR_VIEW,
interaction: {
type: constants.INTERACTION_TYPES.DRAG_END,
},
});
}
}
},
}),
).current;
return interiorData && Object.keys(interiorData).length > 0 ? (
<View
style={[
styles.container,
{
opacity: currentViewState.type === constants.INTERIOR_VIEW ? 1 : 0,
zIndex: currentViewState.type === constants.INTERIOR_VIEW ? 0 : -10,
},
]}>
<WebView
{...panResponder.panHandlers}
source={{uri: customInteriorUrl}}
cacheEnabled={false}
ref={r => (webref.current = r)}
// source={{uri: 'http://192.168.1.26:3000'}}
onMessage={e => {
messageHandler(e);
}}
onLoadEnd={() => {
if (!enableHotspots) {
interiorData.filteredHotspotMetadata = [];
}
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({hotspotMode: 'hide_hotspots'}),
);
if (
constants.INTERIOR_VIEWER_INITIAL_POSITIONS.includes(
interior360InitialPosition,
)
) {
webref.current.injectJavaScript(
injectedJs({initialPosition: interior360InitialPosition}),
);
}
webref.current.injectJavaScript(
injectedJs({currentView: currentViewState.type}),
);
setTimeout(() => {
if (webref && webref.current) {
webref.current.injectJavaScript(
injectedJs({data: interiorData}),
);
}
}, 100);
}
}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
javaScriptEnabled={true}
allowFileAccess={true}
allowFileAccessFromFileURLs={true}
allowUniversalAccessFromFileURLs={true}
originWhitelist={['*']}
// pullToRefreshEnabled={true}
// injectedJavaScriptBeforeContentLoaded={srcBase64}
/>
</View>
) : (
<Text style={{color: '#000'}}>Image not found. Please capture image</Text>
);
};
const mapStateToProps = ({currentHotspot, hotspotMode, currentView}) => {
return {
currentHotspotState: currentHotspot,
hotspotMode: hotspotMode,
widgetsVisible: currentView.widgetsVisible,
currentViewState: currentView.present,
orientation: currentView.orientation,
};
};
export default connect(mapStateToProps)(Interior360);