@s20.ai/safecam-360-rn
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
349 lines (279 loc) • 10.1 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, useState, useContext, useRef} from 'react';
import {View, Text, Dimensions, Platform, StyleSheet, PanResponder, TouchableWithoutFeedback} 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 { setImmersive } from '../../utils/device';
import { setWidgetsVisible } from '../../redux/actions/currentViewAction';
import { setCurrentMessageOff, setCurrentMessageOn } from '../../redux/actions/messageHintAction';
import DragToRotateViewMessage from '../common/overlay/DragToRotateViewMessage';
const Interior360 = ({
interiorData,
currentHotspotState,
hotspotMode,
enableHotspots,
widgetsVisible,
currentViewState,
hotspotPressed,
customInteriorUrl,
onInteraction,
messageHint,
orientation,
customFontFamily,
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])
useEffect(()=>{
if(currentViewState.type === constants.INTERIOR_VIEW){
displayDragToRotateMessage()
}
}, [currentViewState])
const displayDragToRotateMessage = () => {
if(!messageHint[constants.MESSAGES.INTERIOR_ROTATE].show && !messageHint[constants.MESSAGES.INTERIOR_ROTATE].never){
dispatch(setCurrentMessageOn(constants.MESSAGES.INTERIOR_ROTATE));
}
};
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"}));
}
dispatch(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, true))
}
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,
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(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, true))
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"}));
}
dispatch(setCurrentMessageOff(constants.MESSAGES.INTERIOR_ROTATE, true))
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}
/>
{/* {
messageHint[constants.MESSAGES.INTERIOR_ROTATE].show &&
<DragToRotateViewMessage customFontFamily={customFontFamily}/>
} */}
</View>
:
<Text style={{color: "#000"}}>Image not found. Please capture image</Text>
);
};
const mapStateToProps = ({
currentHotspot,
hotspotMode,
currentView,
messageHint
}) => {
return {
currentHotspotState: currentHotspot,
hotspotMode: hotspotMode,
widgetsVisible: currentView.widgetsVisible,
currentViewState: currentView.present,
orientation: currentView.orientation,
messageHint: messageHint
};
};
export default connect(mapStateToProps)(Interior360);