UNPKG

react-native-ui-blueprint

Version:
727 lines (693 loc) 28.1 kB
import React from 'react'; import { Animated, Dimensions, Image, PanResponder, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { animateGenericNative, base, extra, large, small, tiny } from "./Utils"; import Ruler from "./Ruler"; import Grid from "./Grid"; import ImageSelect from "./ImageSelect"; const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity); const MAX_SLIDER_VALUE = large * 2; const dimentions = Dimensions.get('screen'); const screenWidth = dimentions.width; const screenHeight = dimentions.height; const Slider = (props) => { const height = large; const maxmin = (MAX_SLIDER_VALUE) / 4; return (<View style={{ height: height, width: MAX_SLIDER_VALUE, borderRadius: height, justifyContent: 'center', alignItems: 'center', marginLeft: tiny, borderColor: '#18A0FB', backgroundColor: "#18A0FB33", borderWidth: 1, paddingHorizontal: tiny }} {...props.pan.panHandlers} pointerEvents={'auto'}> <Animated.View style={{ position: 'absolute', width: '100%', height: 1, backgroundColor: '#18A0FB' }} pointerEvents={'none'}/> <Animated.View style={{ width: height - 2, height: height - 2, borderRadius: height - 2, backgroundColor: '#18A0FB', transform: [ { translateX: props.value.interpolate({ inputRange: [0, MAX_SLIDER_VALUE], outputRange: [-maxmin, maxmin] }) } ] }} pointerEvents={'none'}/> </View>); }; /** * Add guidelines on screen */ export default class Blueprint extends React.PureComponent { constructor() { super(...arguments); this.state = { zoom: false, ruler: false, gridAlign: 'hidden', showSelectImageModal: false, packagerRunning: false }; this.interacting = false; this.zoomXValue = new Animated.Value(0); this.zoomYValue = new Animated.Value(0); this.zoomScaleValue = new Animated.Value(0); this.zoomX = 0; this.zoomY = 0; this.zoomScale = 0; this.zoomXInit = 0; this.zoomYInit = 0; this.zoomScaleInit = 0; this.imageScaleValue = new Animated.Value(0); this.imageOpacityValue = new Animated.Value(0); this.imageXValue = new Animated.Value(0); this.imageYValue = new Animated.Value(0); this.imageX = 0; this.imageY = 0; this.imageScale = 0; this.imageOpacity = 0; this.imageXInit = 0; this.imageYInit = 0; this.imageScaleInit = 0; this.imageOpacityInit = 0; this.animatedVisibility = new Animated.Value(0); this.visible = false; this.zoomScalePan = PanResponder.create({ onPanResponderGrant: () => { this.zoomScaleInit = this.zoomScale; this.interacting = true; }, onPanResponderMove: (event, gestureState) => { this.zoomScale = Math.max(0, Math.min(MAX_SLIDER_VALUE, (this.zoomScaleInit + gestureState.dx * 0.8))); this.zoomScaleValue.setValue(this.zoomScale); }, onPanResponderEnd: e => { this.interacting = false; this.hideSchedule(); }, onStartShouldSetPanResponder: (event, gestureState) => true, }); this.zoomXYPan = PanResponder.create({ onPanResponderGrant: () => { this.zoomXInit = this.zoomX; this.zoomYInit = this.zoomY; this.interacting = true; }, onPanResponderMove: (event, gestureState) => { // The higher the zoom, the slower the drag speed const speedMax = 1; const speedMin = 0.5; const speed = (this.zoomScale) * (speedMin - speedMax) / (MAX_SLIDER_VALUE) + speedMax; this.zoomX = Math.max(-(screenWidth / 2), Math.min((screenWidth / 2), (this.zoomXInit + gestureState.dx * speed))); this.zoomXValue.setValue(this.zoomX); this.zoomY = Math.max(-(screenHeight / 2), Math.min((screenHeight / 2), (this.zoomYInit + gestureState.dy * speed))); this.zoomYValue.setValue(this.zoomY); }, onPanResponderEnd: e => { this.interacting = false; this.hideSchedule(); }, onStartShouldSetPanResponder: (event, gestureState) => true, }); this.imageOpacityPan = PanResponder.create({ onPanResponderGrant: () => { this.imageOpacityInit = this.imageOpacity; this.interacting = true; }, onPanResponderMove: (event, gestureState) => { this.imageOpacity = Math.max(0, Math.min(MAX_SLIDER_VALUE, (this.imageOpacityInit + gestureState.dx * 0.8))); this.imageOpacityValue.setValue(this.imageOpacity); }, onPanResponderEnd: e => { this.interacting = false; this.hideSchedule(); }, onStartShouldSetPanResponder: (event, gestureState) => true, }); this.imageScalePan = PanResponder.create({ onPanResponderGrant: () => { this.imageScaleInit = this.imageScale; this.interacting = true; }, onPanResponderMove: (event, gestureState) => { // Reduces scale speed const reducer = 0.2; this.imageScale = Math.max(0, Math.min(MAX_SLIDER_VALUE, (this.imageScaleInit + gestureState.dx * reducer))); this.imageScaleValue.setValue(this.imageScale); }, onPanResponderEnd: e => { this.interacting = false; this.hideSchedule(); }, onStartShouldSetPanResponder: (event, gestureState) => true, }); this.imageXYPan = PanResponder.create({ onPanResponderGrant: () => { this.imageXInit = this.imageX; this.imageYInit = this.imageY; this.interacting = true; }, onPanResponderMove: (event, gestureState) => { // Reduces drag speed const reducer = 0.5; // The higher the zoom, the slower the drag speed const speedMax = 1; const speedMin = 0.5; const speed = (this.zoomScale) * (speedMin - speedMax) / (MAX_SLIDER_VALUE) + speedMax; this.imageX = Math.max(-(screenWidth / 2), Math.min((screenWidth / 2), (this.imageXInit + gestureState.dx * speed * reducer))); this.imageXValue.setValue(this.imageX); this.imageY = Math.max(-(screenHeight / 2), Math.min((screenHeight / 2), (this.imageYInit + gestureState.dy * speed * reducer))); this.imageYValue.setValue(this.imageY); }, onPanResponderEnd: e => { this.interacting = false; this.hideSchedule(); }, onStartShouldSetPanResponder: (event, gestureState) => true, }); /** * Schedule to hide Buttons after 4 seconds */ this.hideSchedule = () => { clearTimeout(this.timeout); this.timeout = setTimeout(() => { if (this.interacting) { // re schedule return this.hideSchedule(); } this.visible = false; animateGenericNative(this.animatedVisibility, 0); }, 4000); }; } componentDidMount() { fetch('http://localhost:8081/') .then(value => { this.setState({ packagerRunning: true }); }) .catch(reason => { // ignore }); } render() { if (this.props.disabled) { return this.props.children; } const { width, height } = Dimensions.get('screen'); return (<View style={[StyleSheet.absoluteFill, { backgroundColor: '#EDEDED' }]}> <Animated.View style={[ StyleSheet.absoluteFill, { backgroundColor: '#FFF', transform: [ { scale: this.zoomScaleValue.interpolate({ inputRange: [0, MAX_SLIDER_VALUE], outputRange: [1, 2] }) }, { translateX: this.zoomXValue.interpolate({ inputRange: [-screenWidth, screenWidth], outputRange: [-screenWidth, screenWidth] }) }, { translateY: this.zoomYValue.interpolate({ inputRange: [-screenHeight, screenHeight], outputRange: [-screenHeight, screenHeight] }) } ] } ]} pointerEvents={'box-none'}> <View style={StyleSheet.absoluteFill} pointerEvents={'auto'}> {this.props.children} </View> {this.state.image ? (<Animated.View style={[ StyleSheet.absoluteFill, { opacity: this.imageOpacityValue.interpolate({ inputRange: [0, MAX_SLIDER_VALUE], outputRange: [0, 1] }), transform: [ { scale: this.imageScaleValue.interpolate({ inputRange: [0, MAX_SLIDER_VALUE], outputRange: [0.3, 2] }) }, { translateX: this.imageXValue.interpolate({ inputRange: [-screenWidth, screenWidth], outputRange: [-screenWidth, screenWidth] }) }, { translateY: this.imageYValue.interpolate({ inputRange: [-screenHeight, screenHeight], outputRange: [-screenHeight, screenHeight] }) } ] } ]} pointerEvents={'none'}> <Image source={this.state.image} style={[ StyleSheet.absoluteFill, { width: screenWidth, height: screenHeight, resizeMode: 'contain' } ]}/> </Animated.View>) : null} {this.state.gridAlign !== 'hidden' ? (<Grid grid={this.props.grid} guides={this.props.guides} align={this.state.gridAlign}/>) : null} {this.state.ruler ? (<Ruler ref={(ruler) => { this.ruler = ruler || undefined; }}/>) : null} </Animated.View> <Animated.View style={[ StyleSheet.absoluteFill, { opacity: this.animatedVisibility } ]} pointerEvents={'box-none'}> <TouchableOpacity onPress={event => { clearTimeout(this.timeout); if (this.visible) { this.visible = false; animateGenericNative(this.animatedVisibility, 0); } else { this.visible = true; this.hideSchedule(); animateGenericNative(this.animatedVisibility, 1); } }} style={{ position: 'absolute', left: 0, bottom: large, height: extra, width: extra, marginLeft: tiny, borderRadius: extra, backgroundColor: '#18A0FB33', }}> <Image source={require('./../assets/logo.png')} style={{ width: extra, height: extra, resizeMode: 'stretch' }} width={extra} height={extra}/> </TouchableOpacity> <AnimatedTouchableOpacity style={{ position: 'absolute', left: 0, bottom: large + extra + tiny, flexDirection: 'row', alignItems: 'center', height: large, width: large, borderRadius: large, justifyContent: 'center', borderColor: '#18A0FB', borderWidth: 1, transform: [ { translateX: this.animatedVisibility.interpolate({ inputRange: [0, 1], outputRange: [-large, small] }) } ] }} onPress={() => { const OPTIONS = ['side', 'center', 'left', 'right', 'hidden']; let index = OPTIONS.indexOf(this.state.gridAlign); let newPosition = OPTIONS[index + 1] || 'side'; this.setState({ gridAlign: newPosition }); this.hideSchedule(); }}> <View style={this.state.gridAlign === 'center' ? { height: small, width: 2, alignSelf: 'center', backgroundColor: '#18A0FB', } : (this.state.gridAlign === 'hidden' ? { opacity: 0 } : { height: small, width: small, borderColor: '#18A0FB', borderRightWidth: this.state.gridAlign === 'left' ? 0 : 2, borderLeftWidth: this.state.gridAlign === 'right' ? 0 : 2 })}/> </AnimatedTouchableOpacity> <Animated.View style={{ position: 'absolute', left: 0, bottom: large * 2 + extra + tiny * 2, flexDirection: 'row', alignItems: 'center', transform: [ { translateX: this.animatedVisibility.interpolate({ inputRange: [0, 1], outputRange: [-(large * 4), small] }) } ] }}> <TouchableOpacity onPress={() => { this.hideSchedule(); this.setState({ ruler: !this.state.ruler }); }} style={{ height: large, width: large, borderRadius: large, justifyContent: 'center', alignItems: 'center', borderColor: '#18A0FB', borderWidth: 1 }}> <View style={{ height: small, width: small, borderColor: '#18A0FB', backgroundColor: "#18A0FB33", borderWidth: 1, }}/> </TouchableOpacity> {this.state.ruler ? (<React.Fragment> <TouchableOpacity onPress={() => { this.hideSchedule(); if (this.ruler) { this.ruler.changeUnit(); } }} style={{ height: large, width: large, borderRadius: large, justifyContent: 'center', alignItems: 'center', marginLeft: tiny, borderColor: '#18A0FB', borderWidth: 1 }}> <Text style={{ color: '#18A0FB', fontSize: base, fontFamily: 'System', lineHeight: large, textAlignVertical: 'center' }}>{'U'}</Text> </TouchableOpacity> <TouchableOpacity onPress={() => { this.hideSchedule(); if (this.ruler) { this.ruler.changeSensitivity(); } }} style={{ height: large, width: large, borderRadius: large, justifyContent: 'center', alignItems: 'center', marginLeft: tiny, borderColor: '#18A0FB', borderWidth: 1 }}> <Text style={{ color: '#18A0FB', fontSize: base, fontFamily: 'System', lineHeight: large, textAlignVertical: 'center' }}>{'S'}</Text> </TouchableOpacity> </React.Fragment>) : null} </Animated.View> <Animated.View style={{ position: 'absolute', left: 0, bottom: large * 3 + extra + tiny * 3, flexDirection: 'row', alignItems: 'center', transform: [ { translateX: this.animatedVisibility.interpolate({ inputRange: [0, 1], outputRange: [-(large * 3 + extra * 2 + tiny), small] }) } ] }}> <TouchableOpacity onPress={() => { this.hideSchedule(); this.setState({ zoom: !this.state.zoom }); }} style={{ height: large, width: large, borderRadius: large, justifyContent: 'center', alignItems: 'center', borderColor: '#18A0FB', borderWidth: 1 }}> <View style={{ height: base, width: base, borderRadius: base, justifyContent: 'center', alignItems: 'center', borderColor: '#18A0FB', backgroundColor: "#18A0FB33", borderWidth: 1, }}> <Text style={{ fontFamily: 'System', lineHeight: base * 1.2, fontSize: base, textAlignVertical: 'center', color: '#18A0FB' }}>{'+'}</Text> </View> </TouchableOpacity> {this.state.zoom ? (<React.Fragment> <Slider pan={this.zoomScalePan} value={this.zoomScaleValue}/> <Animated.View style={{ width: large, height: large, justifyContent: 'center', marginLeft: tiny, alignItems: 'center' }} pointerEvents={'box-only'} {...this.zoomXYPan.panHandlers}> <Image source={require('./../assets/move.png')} style={{ width: large, height: large, tintColor: '#18A0FB' }} width={large} height={large}/> </Animated.View> <Animated.View style={{ width: large, height: large, justifyContent: 'center', marginLeft: tiny, alignItems: 'center' }}> <TouchableOpacity onPress={() => { this.zoomX = this.zoomY = this.zoomScale = 0; animateGenericNative(this.zoomScaleValue, 0); animateGenericNative(this.zoomXValue, 0); animateGenericNative(this.zoomYValue, 0); }}> <Image source={require('./../assets/reset.png')} style={{ width: large, height: large, tintColor: '#18A0FB' }} width={large} height={large}/> </TouchableOpacity> </Animated.View> </React.Fragment>) : null} </Animated.View> {(this.props.images || this.props.imagesAsync) ? (<Animated.View style={{ position: 'absolute', left: 0, right: 0, bottom: large * 4 + extra + tiny * 4, flexDirection: 'row', alignItems: 'center', transform: [ { translateX: this.animatedVisibility.interpolate({ inputRange: [0, 1], outputRange: [-(large * 3 + MAX_SLIDER_VALUE * 2), small] }) } ] }}> <TouchableOpacity onPress={() => { this.hideSchedule(); if (this.state.image) { this.setState({ image: undefined }); } else { this.setState({ showSelectImageModal: !this.state.showSelectImageModal }, () => { this.interacting = this.state.showSelectImageModal; this.hideSchedule(); }); } }} style={{ height: large, width: large, borderRadius: large, justifyContent: 'center', alignItems: 'center', borderColor: '#18A0FB', borderWidth: 1 }}> <View style={{ height: small, width: small, borderColor: '#18A0FB', backgroundColor: "#18A0FB33", borderWidth: 1, opacity: 0.5, transform: [ { translateX: -2 }, { translateY: -2 }, ] }}/> <View style={{ position: 'absolute', height: small, width: small, borderColor: '#18A0FB', backgroundColor: "#18A0FB33", borderWidth: 1, opacity: 0.5, transform: [ { translateX: 2 }, { translateY: 2 }, ] }}/> </TouchableOpacity> {(this.state.image) ? (<React.Fragment> <Slider pan={this.imageOpacityPan} value={this.imageOpacityValue}/> <Slider pan={this.imageScalePan} value={this.imageScaleValue}/> <Animated.View style={{ width: large, height: large, marginLeft: tiny, justifyContent: 'center', alignItems: 'center' }} pointerEvents={'box-only'} {...this.imageXYPan.panHandlers}> <Image source={require('./../assets/move.png')} style={{ width: large, height: large, tintColor: '#18A0FB' }} width={large} height={large}/> </Animated.View> </React.Fragment>) : null} </Animated.View>) : null} {this.state.packagerRunning ? (<Animated.View style={{ position: 'absolute', left: 0, bottom: large * 5 + extra + tiny * 5, flexDirection: 'row', alignItems: 'center', transform: [ { translateX: this.animatedVisibility.interpolate({ inputRange: [0, 1], outputRange: [-(large + small), small] }) } ] }}> <TouchableOpacity onPress={() => { fetch('http://localhost:8081/reload') .then(value => { }) .catch(reason => { }); }}> <Image source={require('./../assets/reset.png')} style={{ width: large, height: large, tintColor: '#18A0FB' }} width={large} height={large}/> </TouchableOpacity> </Animated.View>) : null} {this.state.showSelectImageModal ? (<Animated.View style={[ StyleSheet.absoluteFill, { opacity: this.animatedVisibility, transform: [ { translateX: this.animatedVisibility.interpolate({ inputRange: [0, 1], outputRange: [-screenWidth, 0] }) } ] } ]} pointerEvents={'box-none'}> <ImageSelect width={screenWidth - tiny * 2} bottom={large + extra + tiny - 2} left={tiny} height={large * 3 + tiny * 2 + 4} images={this.props.images} imagesAsync={this.props.imagesAsync} onSelect={image => { // Reset values this.imageX = 0; this.imageY = 0; this.imageScale = MAX_SLIDER_VALUE / 3; this.imageOpacity = MAX_SLIDER_VALUE / 3; this.imageXValue.setValue(this.imageX); this.imageYValue.setValue(this.imageY); this.imageScaleValue.setValue(this.imageScale); this.imageOpacityValue.setValue(this.imageOpacity); this.interacting = false; this.hideSchedule(); this.setState({ image: image, showSelectImageModal: false }); }}/> </Animated.View>) : null} </Animated.View> </View>); } } //# sourceMappingURL=Blueprint.js.map