diff options
| author | jules <jules@carbonpictures.com> | 2017-06-02 15:42:34 +0000 |
|---|---|---|
| committer | jules <jules@carbonpictures.com> | 2017-06-02 15:42:34 +0000 |
| commit | 5f26431f03228a85273e7f7d51abd6098ea9f2a5 (patch) | |
| tree | 6a709972cbb0babd68aaa10fe277b2c843fd7451 /client/src/lib/views | |
| parent | 291fe3eedd9a460fc44d2ea3ea81c7d79f2dfbcf (diff) | |
| parent | dd70fa81a205304cb48bbc0494ad34c16d496ff2 (diff) | |
merge
Diffstat (limited to 'client/src/lib/views')
| -rw-r--r-- | client/src/lib/views/contact.js | 307 | ||||
| -rw-r--r-- | client/src/lib/views/credits.js | 34 | ||||
| -rw-r--r-- | client/src/lib/views/home.js | 98 | ||||
| -rw-r--r-- | client/src/lib/views/information.js | 114 | ||||
| -rw-r--r-- | client/src/lib/views/livestream.js | 134 | ||||
| -rw-r--r-- | client/src/lib/views/nav.js | 119 | ||||
| -rw-r--r-- | client/src/lib/views/privacy.js | 106 |
7 files changed, 912 insertions, 0 deletions
diff --git a/client/src/lib/views/contact.js b/client/src/lib/views/contact.js new file mode 100644 index 0000000..74f79b5 --- /dev/null +++ b/client/src/lib/views/contact.js @@ -0,0 +1,307 @@ +import React, { Component } from 'react'; +import { + ActivityIndicator, + KeyboardAvoidingView, + TouchableHighlight, + TouchableWithoutFeedback, + StyleSheet, + TextInput, + Keyboard, + Text, + View +} from 'react-native'; + +import hosts from '../db/hosts' + +const validator = require("email-validator") + +import ScrollableContainer from '../components/scrollableContainer' +import ClearText from '../components/text' +import Heading from '../components/heading' +import Button from '../components/button' +import CheckBox from '../components/checkbox' +import Modal from '../components/modal' + +export default class Contact extends Component { + constructor() { + super() + this.state = { + name: '', + email: '', + message: '', + track: false, + publish: false, + sending: false, + done: false, + } + this.setName = this.setName.bind(this) + this.setEmail = this.setEmail.bind(this) + this.setTrack = this.setTrack.bind(this) + this.setPublish = this.setPublish.bind(this) + this.setMessage = this.setMessage.bind(this) + this.send = this.send.bind(this) + this.reset = this.reset.bind(this) + } + setName(name) { + this.setState({ name, error: false }) + } + setEmail(email) { + this.setState({ email, error: false }) + } + setTrack(track) { + this.setState({ track: ! track }) + } + setPublish(publish) { + this.setState({ publish: ! publish }) + } + setMessage(message) { + this.setState({ message: message }) + } + send() { + var isValidEmail = validator.validate(this.state.email) + if (! isValidEmail) { + // return this.setState({ error: true }) + this.setState({ sending: true, track: false }) + } + else { + this.setState({ sending: true }) + } +// fetch('POST', hosts.feedback, { +// 'Content-Type' : 'multipart/form-data', +// }, [ +// { name: 'name', data: this.state.name }, +// { name: 'email', data: this.state.email }, +// { name: 'message', data: this.state.message }, +// { name: 'track', data: this.state.track }, +// { name: 'publish', data: this.state.publish }, +// { name: 'secret', data: 'iNr51NbvUDqbBzyray7gdZZjigDtlJj9' }, +// ]).then(() => { +// this.setState({ done: true }) +// }).catch((e) => { +// console.warn(e) +// this.setState({ done: true }) +// }) + } + reset() { + this.setState({ + name: '', + email: '', + message: '', + track: false, + publish: false, + sending: false, + done: false, + }) + } + render() { + return ( + <ScrollableContainer heading='Contact' style={styles.container} bodyStyle={styles.body}> + <View style={styles.innerContainer}> + <ClearText style={styles.bodyText}> + {this.props.content.body} + </ClearText> + + {this.renderForm()} + {this.renderComments()} + </View> + + <Modal visible={this.state.sending}> + {this.renderActivity()} + </Modal> + </ScrollableContainer> + ) + } + renderForm() { + let error = this.state.error ? ( + <ClearText>Please enter a valid email address</ClearText> + ) : ( + <View></View> + ) + return ( + <View style={styles.keyboardAvoidingViewInner}> + <TextInput + value={this.state.name} + placeholder="Name" + autoCorrect={false} + onChangeText={this.setName} + style={styles.textInput} /> + <TextInput + value={this.state.email} + placeholder="Email" + autoCapitalize='none' + autoCorrect={false} + keyboardType='email-address' + onChangeText={this.setEmail} + style={styles.textInput} /> + {error} + <TextInput + multiline={true} + placeholder="Enter your message" + value={this.state.message} + onChangeText={this.setMessage} + style={[styles.textInput, styles.textArea]} /> + <View style={styles.checkboxes}> + <CheckBox + label='Sign me up for emails from the Armory' + containerStyle={styles.checkboxContainerStyle} + labelStyle={styles.checkboxLabelStyle} + checked={this.state.track} + onChange={this.setTrack} /> + <CheckBox + label='Publish my comment for others to read' + containerStyle={styles.checkboxContainerStyle} + labelStyle={styles.checkboxLabelStyle} + checked={this.state.publish} + onChange={this.setPublish} /> + </View> + <Button label='SEND' onPress={this.send} /> + </View> + ) + } + renderActivity() { + let activity; + if (! this.state.done) { + activity = ( + <View style={styles.message}> + <ActivityIndicator animating={true} color='#fff' /> + <Heading>Sending your message...</Heading> + </View> + ) + } + else { + activity = ( + <View style={styles.message}> + <Heading>Thanks!</Heading> + <ClearText>Your message has been delivered.</ClearText> + <ClearText style={styles.lastMessage}>Thank you for your feedback.</ClearText> + <Button onPress={this.reset} label='OK' /> + </View> + ) + } + return ( + <View style={styles.modal}> + {activity} + </View> + ) + } + renderComments() { + const comments = this.props.comments.map( (comment, i) => { + const date = comment[0].replace(/:\d\d$/,'') + return ( + <View style={styles.comment} key={'comment_' + i}> + <View style={styles.commentRow}> + <ClearText style={styles.commentName}>{comment[1]}</ClearText> + <ClearText style={styles.commentDate}>{date}</ClearText> + </View> + <ClearText style={styles.commentBody}>{comment[2]}</ClearText> + </View> + ) + }) + return ( + <TouchableWithoutFeedback> + <View style={styles.comments}> + {comments} + </View> + </TouchableWithoutFeedback> + ) + } +} + +const styles = StyleSheet.create({ + container: { + justifyContent: 'flex-start', + alignItems: 'center', + flex: 1, + }, + body: { + justifyContent: 'flex-start', + alignItems: 'center', + }, + innerContainer: { + justifyContent: 'flex-start', + alignItems: 'center', + }, + bodyText: { + width: 400, + marginBottom: 20, + }, + message: { + backgroundColor: 'rgb(0,0,0)', + borderRadius: 20, + padding: 20, + }, + modal: { + backgroundColor: 'rgba(0,0,0,0.9)', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + textInput: { + width: 350, + height: 40, + padding: 5, + fontSize: 15, + fontFamily: 'Futura-Medium', + backgroundColor: 'white', + borderColor: 'gray', + borderWidth: 1, + marginTop: 10, + marginLeft: 5, + marginBottom: 5, + marginRight: 5, + }, + textArea: { + width: 350, + height: 200, + }, + checkboxes: { + alignItems: 'flex-start', + marginLeft: 5, + }, + checkboxContainerStyle: { + marginTop: 10, + }, + checkboxLabelStyle: { + color: '#ddd', + fontFamily: 'Futura-Medium', + }, + keyboardAvoidingViewInner: { + alignItems: 'center', + }, + lastMessage: { + marginBottom: 10, + }, + + comments: { + marginTop: 20, + paddingBottom: 100, + width: 400, + }, + comment: { + borderWidth: 1, + borderColor: 'rgb(128,128,128)', + padding: 10, + margin: 10, + }, + commentRow: { + flexDirection: 'row', + }, + commentName: { + flex: 1, + fontSize: 13, + textAlign: 'left', + }, + commentDate: { + flex: 1, + fontSize: 13, + textAlign: 'right', + }, + commentBody: { + marginTop: 10, + fontSize: 13, + borderTopWidth: 1, + borderTopColor: 'rgb(64,64,64)', + textAlign: 'left', + }, +}) + diff --git a/client/src/lib/views/credits.js b/client/src/lib/views/credits.js new file mode 100644 index 0000000..dbe516b --- /dev/null +++ b/client/src/lib/views/credits.js @@ -0,0 +1,34 @@ +import React, { Component } from 'react'; +import { + StyleSheet, + View, +} from 'react-native'; +import HTMLView from 'react-native-htmlview' + +import htmlStyles from '../components/htmlStyles' +import ScrollableContainer from '../components/scrollableContainer' +import ClearText from '../components/text' + +export default class Credits extends Component { + constructor(props) { + super() + } + render() { + let body = '<p>' + this.props.content.body + '</p>' + return ( + <ScrollableContainer heading='Credits'> + <View style={styles.body}> + <View style={styles.inner}> + <HTMLView value={body} stylesheet={htmlStyles} onLinkPress={this.props.onLinkPress} /> + </View> + </View> + </ScrollableContainer> + ) + } +} + +const styles = StyleSheet.create({ + body: { + maxWidth: '650px', + }, +}) diff --git a/client/src/lib/views/home.js b/client/src/lib/views/home.js new file mode 100644 index 0000000..618ce64 --- /dev/null +++ b/client/src/lib/views/home.js @@ -0,0 +1,98 @@ +import React, { Component } from 'react'; +import { + Image, + StyleSheet, + TouchableOpacity, + View, + Platform +} from 'react-native'; +import { Link } from 'react-router-dom' +import ClearText from '../components/text' +import FadeInView from 'react-native-fade-in-view' + +const sections = [ + '/timeline', + '/drones', + '/livestream', + '/information', + '/contact' +] +const labels = [ + 'SURVEILLANCE', + 'MILITARY DRONE STATISTICS', + 'LIVESTREAM', + 'ABOUT THIS WORK', + 'CONTACT', +] +const images = [ + require('../../img/nav/home/timeline.png'), + require('../../img/nav/home/drones.png'), + require('../../img/nav/home/livestream.png'), + require('../../img/nav/home/information.png'), + require('../../img/nav/home/contact.png'), +] + +export default class Home extends Component { + constructor() { + super() + } + render() { + const links = sections.map((path, i) => { + const image = images[i] + const label = labels[i] + return view(path, image, label) + }) + return ( + <FadeInView duration={500} style={styles.container}> + {links} + </FadeInView> + ) + } +} + +function view (path, image, label, isSelected) { + let key = ('home_' + path).replace('/','') + let imageKey = 'image_' + key + String(isSelected) + return ( + <Link to={path} key={key}> + <View style={styles.navItem}> + <Image key={imageKey} style={styles.image} source={image} /> + <ClearText style={styles.text}>{label}</ClearText> + </View> + </Link> + ) +} + +const isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) +const isIpad = (navigator.userAgent.match(/iPad/i)) +const isAndroid = (navigator.userAgent.match(/Android/i)) +const isMobile = isIphone || isIpad || isAndroid +const isDesktop = ! isMobile + +const shortHeight = isIphone || window.innerHeight < 700 + +const buttonSide = shortHeight ? 50 : 70 +const buttonMargin = shortHeight ? 5 : 10 + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + marginTop: -80 + }, + navItem: { + justifyContent: 'center', + alignItems: 'center', + minHeight: shortHeight ? '10vh' : '13vh', + }, + text: { + lineHeight: 16, + marginBottom: shortHeight ? 5 : 10, + }, + image: { + width: buttonSide, + height: buttonSide, + margin: buttonMargin, + } +}) diff --git a/client/src/lib/views/information.js b/client/src/lib/views/information.js new file mode 100644 index 0000000..77d1af2 --- /dev/null +++ b/client/src/lib/views/information.js @@ -0,0 +1,114 @@ +import React, { Component } from 'react'; +import { + StyleSheet, + Image, +} from 'react-native'; + +import ScrollableContainer from '../components/scrollableContainer' +import ClearText from '../components/text' + +export default class Information extends Component { + constructor(props) { + super() + } + render() { + const content = this.props.content + return ( + <ScrollableContainer heading="ABOUT THIS WORK" bodyStyle={styles.bodyStyle}> + <ClearText style={styles.body}> + {this.props.content.show} + </ClearText> + + <Image source={require('../../img/aiweiwei.png')} resizeMode='cover' style={styles.face} /> + <ClearText style={styles.body}> + {this.props.content.aiWeiWeiBio} + </ClearText> + <Image source={require('../../img/herzogDeMeuron.png')} resizeMode='cover' style={styles.face} /> + <ClearText style={styles.body}> + {this.props.content.herzogBio} + </ClearText> + <ClearText style={styles.body}> + {this.props.content.deMeuronBio} + </ClearText> + <ClearText style={styles.footer}> + </ClearText> + </ScrollableContainer> + ); + } +} + +/* + <YouTube + ref={(ref) => this.youtubePlayer = ref} + videoId={content.video.token} + play={false} + hidden={false} + fullscreen={false} + loop={true} + controls={1} + showFullscreenButton={true} + showinfo={false} + modestbranding={true} + rel={false} + iv_load_policy={3} + + onReady={(e)=>{this.setState({isReady: true})}} + onChangeState={(e)=>{ + if (e.state == 'playing') { + this.props.onVideoPlay() + } + else { + this.props.onVideoPause() + } + }} + onChangeFullscreen={(e)=>{ + if (e.isFullscreen) { + this.props.onVideoPlay() + } + else { + this.props.onVideoPause() + } + }} + onError={(e)=>{this.setState({error: e.error})}} + onProgress={(e)=>{this.setState({currentTime: e.currentTime, duration: Math.round(e.duration)})}} + + style={styles.video} + /> +*/ + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'flex-start', + alignItems: 'flex-start', + }, + bodyStyle: { + marginRight: 10, + }, + video: { + alignSelf: 'stretch', + height: 400, + width: '100%', + backgroundColor: 'black', + marginVertical: 10 + }, + body: { + textAlign: 'left', + paddingRight: 10, + width: '90%', + marginBottom: 20, + maxWidth: 600, + }, + hero: { + width: '90%', + height: 300, + }, + face: { + width: '90vw', + height: 450, + marginBottom: 20, + }, + footer: { + marginBottom: 80, + }, +}) diff --git a/client/src/lib/views/livestream.js b/client/src/lib/views/livestream.js new file mode 100644 index 0000000..d3df460 --- /dev/null +++ b/client/src/lib/views/livestream.js @@ -0,0 +1,134 @@ +import React, { Component } from 'react'; +import { + StyleSheet, + View +} from 'react-native'; + +import Container from '../components/container' +import ClearText from '../components/text' +import Button from '../components/button' +import Youtube from '../components/youtube' + +export default class Livestream extends Component { + constructor(props) { + super() + this.state = { + ytid: getYTID(choice(props.content.streams).uri), + isReady: false, + currentTime: 0, + duration: 0, + quality: 0, + status: 'unloaded', + error: null, + } + } + render() { + const buttons = this.props.content.streams.map( (stream, i) => { + const ytid = getYTID(stream.uri || "") + if (! ytid) return null + let buttonStyle, textStyle + if (ytid === this.state.ytid) { + buttonStyle = styles.activeButton + textStyle = styles.activeButtonText + } + return ( + <Button + key={'button_' + i} + buttonStyle={buttonStyle} + textStyle={textStyle} + label={stream.text} + onPress={() => { + this.setState({ ytid }) + }} + /> + ) + }).filter(button => !!button) + + return ( + <Container heading='Livestream' bodyStyle={styles.bodyStyle}> + <View style={styles.videoContainer} className='videoContainer'> + <Youtube ytid={this.state.ytid} /> + <View style={styles.overlay}></View> + </View> + <View style={styles.buttons}> + {buttons} + </View> + <View style={styles.contentContainer}> + <ClearText style={styles.body}>{this.props.content.body}</ClearText> + </View> + </Container> + ) + } +} + +function randint(n){ return Math.floor(Math.random() * n) } +function choice(a){ return a[randint(a.length)] } +function getYTID(url) { + return ( + url.match(/v=([-_a-zA-Z0-9]{11})/i) + || url.match(/youtu.be\/([-_a-zA-Z0-9]{11})/i) + || url.match(/embed\/([-_a-zA-Z0-9]{11})/i) + )[1].split('&')[0]; + +} +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'flex-start', + alignItems: 'center', + }, + bodyStyle: { + flex: 1, + }, + body: { + textAlign: 'left', + marginTop: 40, + width: '75%', + }, + contentContainer: { + flex: 1, + justifyContent: 'flex-start', + alignItems: 'center', + width: '100%', + }, + videoContainer: { + justifyContent: 'flex-start', + height: 400, + width: '100%', + padding: 10, + }, + video: { + alignSelf: 'stretch', + height: 400, + width: '100%', + backgroundColor: 'black', + marginVertical: 10 + }, + overlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + width: '100%', + height: '100%', + backgroundColor: 'rgba(0,0,0,0)' + }, + buttons: { + width: '100%', + paddingTop: 20, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + button: { + margin: 20, + }, + activeButtonText: { + color: 'red', + }, + activeButton: { + backgroundColor: '#bbb', + borderBottomColor: '#888', + }, +}) diff --git a/client/src/lib/views/nav.js b/client/src/lib/views/nav.js new file mode 100644 index 0000000..83f9aa4 --- /dev/null +++ b/client/src/lib/views/nav.js @@ -0,0 +1,119 @@ +import React, { Component } from 'react'; +import { + Image, + StyleSheet, + Text, + TouchableOpacity, + Platform, + View +} from 'react-native'; +import { Link } from 'react-router-dom' +import FadeInView from 'react-native-fade-in-view' + +const sections = [ + '/', + '/timeline', + '/drones', + '/livestream', + '/information', + '/contact' +] +const images = [ + require('../../img/nav/sidebar/home.png'), + require('../../img/nav/sidebar/timeline.png'), + require('../../img/nav/sidebar/drones.png'), + require('../../img/nav/sidebar/livestream.png'), + require('../../img/nav/sidebar/information.png'), + require('../../img/nav/sidebar/contact.png'), +] + +export default class Nav extends Component { + constructor() { + super() + } + render() { + let opacity = 1 + if (this.props.location.pathname === '/' || this.props.location.pathname === '/home') { + opacity = 0 + } + const min = this.props.min || 0 + const max = this.props.max || 6 + const align = this.props.min == 0 ? styles.flexEnd : styles.flexStart + const links = sections.map((path, i) => { + const isSelected = this.props.location.pathname === path + const image = images[i] + return view(path, image, isSelected, align) + }).filter( (a,i) => { return min <= i && i < max }) + return ( + <View style={[styles.nav, align, {opacity: opacity}]}> + {links} + </View> + ) + } +} + +function view (path, image, isSelected, align) { + const key = 'link_' + (path.replace('/','') || 'home') + const style = [styles.image] + if (! isSelected) { + style.push(styles.unselectedNavItem) + } + else { + style.push(styles.selectedNavItem) + } + return ( + <View style={[styles.item, align]} key={key}> + <Link to={path}> + <View style={[styles.item, align]}> + <Image key={'image_' + key} style={style} source={image} /> + </View> + </Link> + </View> + ) +} + +const isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i)) +const isIpad = (navigator.userAgent.match(/iPad/i)) +const isAndroid = (navigator.userAgent.match(/Android/i)) +const isMobile = isIphone || isIpad || isAndroid +const isDesktop = ! isMobile + +const styles = StyleSheet.create({ + nav: { + width: '100%', + height: 35, + flex: 10, + flexGrow: 2, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'flex-end', + }, + flexEnd: { + alignItems: 'flex-end', + paddingRight: isMobile ? 5 : 10, + }, + flexStart: { + alignItems: 'flex-start', + paddingLeft: isMobile ? 5 : 10, + }, + unselectedNavItem: { + opacity: 0.5, + }, + selectedNavItem: { + opacity: 1.0, + }, + item: { + flex: 1, + justifyContent: 'center', + alignItems: 'flex-end', + }, + image: { + margin: "0 auto", + justifyContent: 'center', + alignItems: 'flex-end', + width: 35, + height: 35, + }, +}) + + diff --git a/client/src/lib/views/privacy.js b/client/src/lib/views/privacy.js new file mode 100644 index 0000000..eb243a3 --- /dev/null +++ b/client/src/lib/views/privacy.js @@ -0,0 +1,106 @@ +import React, { Component } from 'react'; +import { + StyleSheet, + View, +} from 'react-native'; +import HTMLView from 'react-native-htmlview' + +import ScrollableContainer from '../components/scrollableContainer' +import ClearText from '../components/text' + +export default class Privacy extends Component { + constructor(props) { + super() + } + render() { + let body = this.props.content.body + return ( + <ScrollableContainer heading='Privacy Policy'> + <View style={styles.body}> + <HTMLView value={body} stylesheet={htmlStyles} renderNode={renderNode} onLinkPress={this.props.onLinkPress} /> + </View> + </ScrollableContainer> + ) + } +} + +function renderNode (node, index) { + if (node.name === 'hr') { + return ( <View key={index} style={styles.hr} /> ) + } + // console.warn(node.name) +} + +const styles = StyleSheet.create({ + body: { + width: '90%', + maxWidth: '650px', + paddingBottom: 200, + }, + hr: { + width: 50, + height: 2, + backgroundColor: '#fff', + marginBottom: 20, + }, +}) + +const htmlStyles = StyleSheet.create({ + p: { + color: 'white', + fontFamily: 'Futura-Medium', + textAlign: 'justify', + fontSize: 16, + lineHeight: 30, + margin: 0, + }, + li: { + color: 'white', + fontFamily: 'Futura-Medium', + textAlign: 'justify', + fontSize: 16, + lineHeight: 30, + margin: 0, + }, + b: { + fontFamily: 'Futura-MediumItalic', + color: 'white', + fontSize: 16, + lineHeight: 30, + }, + i: { + fontFamily: 'Futura-MediumItalic', + color: 'white', + fontSize: 16, + lineHeight: 30, + }, + a: { + color: 'white', + fontFamily: 'Futura-Medium', + textDecorationLine: 'underline', + fontSize: 16, + lineHeight: 30, + }, + h1: { + color: 'white', + fontFamily: 'Futura-Medium', + fontSize: 24, + lineHeight: 30, + margin: 0, + }, + h2: { + color: 'white', + fontFamily: 'Futura-Medium', + fontSize: 20, + lineHeight: 32, + margin: 0, + }, + h3: { + color: 'white', + fontFamily: 'Futura-Medium', + fontSize: 16, + lineHeight: 30, + fontWeight: 'bold', + margin: 0, + }, +}) |
