UNPKG

@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
/* * 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);