summaryrefslogtreecommitdiff
path: root/client/src/lib/components
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2017-06-01 19:47:08 -0400
committerJules Laplace <julescarbon@gmail.com>2017-06-01 19:47:08 -0400
commit3e72bfa56c860826429a842f6c128d78d4a930db (patch)
tree3cecd31c92d53fae32e9761b80802c82f3dcb7fa /client/src/lib/components
parentb694bd511ceccd00d4a4c98f36f910d5fc5f79c4 (diff)
react-native-web port of fmf app
Diffstat (limited to 'client/src/lib/components')
-rw-r--r--client/src/lib/components/button.js55
-rw-r--r--client/src/lib/components/checkbox.js35
-rw-r--r--client/src/lib/components/close.js33
-rw-r--r--client/src/lib/components/container.js50
-rw-r--r--client/src/lib/components/definition.js52
-rw-r--r--client/src/lib/components/footer.js77
-rw-r--r--client/src/lib/components/header.js52
-rw-r--r--client/src/lib/components/heading.js34
-rw-r--r--client/src/lib/components/htmlStyles.js32
-rw-r--r--client/src/lib/components/mapStyle.js287
-rw-r--r--client/src/lib/components/modal.js41
-rw-r--r--client/src/lib/components/pushNotifications.js38
-rw-r--r--client/src/lib/components/scrollableContainer.js51
-rw-r--r--client/src/lib/components/text.js30
-rw-r--r--client/src/lib/components/webViewModal.js56
-rw-r--r--client/src/lib/components/youtube.js91
16 files changed, 1014 insertions, 0 deletions
diff --git a/client/src/lib/components/button.js b/client/src/lib/components/button.js
new file mode 100644
index 0000000..a5cf8ea
--- /dev/null
+++ b/client/src/lib/components/button.js
@@ -0,0 +1,55 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View
+} from 'react-native';
+import DeviceInfo from 'react-native-device-info'
+
+export default class Button extends Component {
+ render() {
+ let { buttonStyle, textStyle, ...others } = this.props
+ let activeOpacity = 0.5
+ let viewElementStyle = [ styles.button, buttonStyle ]
+ let textElementStyle = [ styles.text, textStyle ]
+ if (this.props.disabled) {
+ viewElementStyle.push( styles.disabledButton )
+ textElementStyle.push( styles.disabledText )
+ activeOpacity = 1.0
+ }
+ return (
+ <TouchableOpacity {...others} onPress={() => { if (! this.props.disabled) { this.props.onPress() }}}>
+ <View style={viewElementStyle}>
+ <Text style={textElementStyle}>
+ {this.props.label}
+ </Text>
+ </View>
+ </TouchableOpacity>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ text: {
+ color: '#000',
+ fontFamily: 'Futura-Medium',
+ textAlign: 'center',
+ padding: 0,
+ },
+ button: {
+ padding: 10,
+ margin: 10,
+ borderRadius: 3,
+ backgroundColor: '#fff',
+ borderBottomColor: '#bbb',
+ borderBottomWidth: 2,
+ },
+ disabledButton: {
+ backgroundColor: '#bbb',
+ borderBottomColor: '#888',
+ },
+ disabledText: {
+ color: '#333',
+ },
+})
diff --git a/client/src/lib/components/checkbox.js b/client/src/lib/components/checkbox.js
new file mode 100644
index 0000000..ea4ab7e
--- /dev/null
+++ b/client/src/lib/components/checkbox.js
@@ -0,0 +1,35 @@
+import React, { Component } from 'react'
+import {
+ View,
+ Image,
+ StyleSheet,
+} from 'react-native'
+
+import ClearText from './text'
+
+export default class CheckBox extends Component {
+ render() {
+ const image = this.props.checked ? (
+ <Image source={require('../../img/checkbox-on.png')} style={styles.image} />
+ ) : (
+ <Image source={require('../../img/checkbox-off.png')} style={styles.image} />
+ )
+ return (
+ <View style={[styles.row, this.props.containerStyle]} onClick={() => this.props.onChange(this.props.checked)}>
+ {image}
+ <ClearText style={this.props.labelStyle}>{this.props.label}</ClearText>
+ </View>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ },
+ image: {
+ width: 30,
+ height: 30,
+ marginRight: 5,
+ }
+})
diff --git a/client/src/lib/components/close.js b/client/src/lib/components/close.js
new file mode 100644
index 0000000..12b4a0a
--- /dev/null
+++ b/client/src/lib/components/close.js
@@ -0,0 +1,33 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ TouchableOpacity,
+ Image
+} from 'react-native';
+import DeviceInfo from 'react-native-device-info'
+
+export default class Close extends Component {
+ render() {
+ return (
+ <TouchableOpacity onPress={this.props.onPress} style={styles.close}>
+ <Image key={'image_close'} source={require('../../img/close.png')} style={styles.closeImage} />
+ </TouchableOpacity>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ close: {
+ position: 'absolute',
+ top: 10,
+ right: 10,
+ width: 40,
+ height: 40,
+ padding: 5,
+ backgroundColor: 'black'
+ },
+ closeImage: {
+ width: 30,
+ height: 30,
+ },
+})
diff --git a/client/src/lib/components/container.js b/client/src/lib/components/container.js
new file mode 100644
index 0000000..fe6fd5e
--- /dev/null
+++ b/client/src/lib/components/container.js
@@ -0,0 +1,50 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ View
+} from 'react-native';
+
+import Heading from '../components/heading'
+
+export default class Container extends Component {
+ constructor() {
+ super()
+ }
+ render() {
+ const { heading, style, bodyStyle, headingOnPress, ...props } = this.props
+ let headingEl;
+ if (heading) {
+ headingEl = (
+ <Heading style={styles.heading} onPress={headingOnPress}>
+ {heading.toUpperCase()}
+ </Heading>
+ )
+ }
+ else {
+ headingEl = null
+ }
+ return (
+ <View style={[styles.container, style]} {...props}>
+ {headingEl}
+ <View style={[styles.body, bodyStyle]}>
+ {this.props.children}
+ </View>
+ </View>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'flex-start',
+ },
+ heading: {
+ },
+ body: {
+ flex: 1,
+ justifyContent: 'flex-start',
+ alignItems: 'flex-start',
+ paddingLeft: 10,
+ }
+})
diff --git a/client/src/lib/components/definition.js b/client/src/lib/components/definition.js
new file mode 100644
index 0000000..b6b78f5
--- /dev/null
+++ b/client/src/lib/components/definition.js
@@ -0,0 +1,52 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ View,
+} from 'react-native';
+
+import ClearText from './text'
+
+export default class Definition extends Component {
+ render() {
+ let { labelStyle, contentStyle, label, children, contentIsView, ...others } = this.props
+ let content;
+ if (contentIsView) {
+ content = (
+ <View style={[styles.contentView, contentStyle]}>{children}</View>
+ )
+ }
+ else {
+ content = (
+ <ClearText style={[styles.content, contentStyle]}>{children}</ClearText>
+ )
+ }
+ return (
+ <View style={styles.item} {...others}>
+ <ClearText style={[styles.label, labelStyle]}>{label}</ClearText>
+ {content}
+ </View>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ item: {
+ justifyContent: 'flex-start',
+ flexDirection: 'row',
+ width: '100%',
+ paddingBottom: 0,
+ },
+ label: {
+ width: 80,
+ minWidth: 80,
+ textAlign: 'left',
+ color: '#bbb',
+ },
+ content: {
+ flex: 1,
+ textAlign: 'left'
+ },
+ contentView: {
+ flex: 1,
+ },
+})
diff --git a/client/src/lib/components/footer.js b/client/src/lib/components/footer.js
new file mode 100644
index 0000000..b389245
--- /dev/null
+++ b/client/src/lib/components/footer.js
@@ -0,0 +1,77 @@
+import React, { Component } from 'react'
+import {
+ Image,
+ StyleSheet,
+ TouchableOpacity,
+ Text,
+ View,
+} from 'react-native'
+import { Link } from 'react-router-dom'
+
+export default class ClearText extends Component {
+ render() {
+ return (
+ <View style={styles.footer}>
+ <Link to='/page/privacy'>
+ <View style={styles.footerItem}>
+ <Text style={styles.footerText}>Privacy</Text>
+ </View>
+ </Link>
+ <View style={styles.footerItem}>
+ <TouchableOpacity onPress={() => this.props.onLinkPress('http://armoryonpark.org/')}>
+ <Image source={require('../../img/armory.png')} resizeMode='contain' style={styles.footerLogo} />
+ </TouchableOpacity>
+ </View>
+ <Link to='/page/credits'>
+ <View style={styles.footerItem}>
+ <Text style={styles.footerText}>Credits</Text>
+ </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({
+ footer: {
+ position: 'fixed',
+ bottom: 0,
+ left: 0,
+ width: '100%',
+ height: isMobile ? 40 : 70,
+ backgroundColor: 'black',
+ paddingTop: isMobile ? 5 : 20,
+ paddingBottom: isMobile ? 5 : 20,
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ },
+ footerLogo: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ alignItems: 'center',
+ maxWidth: 140,
+ maxHeight: 40,
+ width: 260,
+ height: 39,
+ },
+ footerItem: {
+ height: 35,
+ flex: 1,
+ alignItems: 'center',
+ },
+ footerText: {
+ fontFamily: 'Futura-Medium',
+ color: 'white',
+ fontSize: 10,
+ flex: 1,
+ padding: 10,
+ marginTop: 4,
+ }
+})
diff --git a/client/src/lib/components/header.js b/client/src/lib/components/header.js
new file mode 100644
index 0000000..4cd264a
--- /dev/null
+++ b/client/src/lib/components/header.js
@@ -0,0 +1,52 @@
+import React, { Component } from 'react'
+import {
+ Image,
+ StyleSheet,
+ TouchableOpacity,
+ Text,
+ View,
+} from 'react-native'
+import { Link } from 'react-router-dom'
+
+import Nav from '../views/nav'
+
+export default class ClearText extends Component {
+ render() {
+ return (
+ <View style={styles.header}>
+ <Nav location={this.props.location} min={0} max={3} />
+ <Link to='/'>
+ <Image source={require('../../img/logo.png')} resizeMode='contain' style={styles.logo} />
+ </Link>
+ <Nav location={this.props.location} min={3} max={6} />
+ </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({
+ header: {
+ flex: 1,
+ width: '100%',
+ backgroundColor: 'black',
+ paddingTop: isMobile ? 5 : 10,
+ paddingBottom: isMobile ? 5 : 10,
+ height: isMobile ? 30 : 70,
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ },
+
+ logo: {
+ flex: 2,
+ marginTop: 0,
+ height: isMobile ? 30 : 55,
+ marginBottom: isMobile ? 5 : 10,
+ },
+})
diff --git a/client/src/lib/components/heading.js b/client/src/lib/components/heading.js
new file mode 100644
index 0000000..4ec19fb
--- /dev/null
+++ b/client/src/lib/components/heading.js
@@ -0,0 +1,34 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet
+} from 'react-native';
+import DeviceInfo from 'react-native-device-info'
+
+import ClearText from './text'
+
+export default class Heading extends Component {
+ render() {
+ const {style, ...props} = this.props
+ return (
+ <ClearText style={[styles.heading, style]} {...props} />
+ )
+ }
+}
+
+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({
+ heading: {
+ marginLeft: 0,
+ paddingTop: isMobile ? 5 : 10,
+ paddingLeft: 0,
+ paddingRight: 0,
+ paddingBottom: isMobile ? 10 : 30,
+ fontSize: isMobile ? 20 : 25,
+ textAlign: 'center',
+ }
+})
diff --git a/client/src/lib/components/htmlStyles.js b/client/src/lib/components/htmlStyles.js
new file mode 100644
index 0000000..4037f9d
--- /dev/null
+++ b/client/src/lib/components/htmlStyles.js
@@ -0,0 +1,32 @@
+import {
+ StyleSheet,
+} from 'react-native'
+
+export default StyleSheet.create({
+ p: {
+ color: 'white',
+ fontFamily: 'Futura-Medium',
+ textAlign: 'justify',
+ fontSize: 16,
+ lineHeight: 30,
+ },
+ 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,
+ },
+})
diff --git a/client/src/lib/components/mapStyle.js b/client/src/lib/components/mapStyle.js
new file mode 100644
index 0000000..1b72668
--- /dev/null
+++ b/client/src/lib/components/mapStyle.js
@@ -0,0 +1,287 @@
+export default [
+ {
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#212121"
+ }
+ ]
+ },
+ {
+ "elementType": "labels.icon",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#757575"
+ }
+ ]
+ },
+ {
+ "elementType": "labels.text.stroke",
+ "stylers": [
+ {
+ "color": "#212121"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative",
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#757575"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative.country",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#9e9e9e"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative.land_parcel",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative.locality",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative.locality",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#bdbdbd"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative.neighborhood",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "administrative.province",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "poi",
+ "elementType": "labels.text",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "poi",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#757575"
+ }
+ ]
+ },
+ {
+ "featureType": "poi.business",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "poi.park",
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#181818"
+ }
+ ]
+ },
+ {
+ "featureType": "poi.park",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#616161"
+ }
+ ]
+ },
+ {
+ "featureType": "poi.park",
+ "elementType": "labels.text.stroke",
+ "stylers": [
+ {
+ "color": "#1b1b1b"
+ }
+ ]
+ },
+ {
+ "featureType": "road",
+ "elementType": "geometry.fill",
+ "stylers": [
+ {
+ "color": "#2c2c2c"
+ }
+ ]
+ },
+ {
+ "featureType": "road",
+ "elementType": "labels",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "road",
+ "elementType": "labels.icon",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "road",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#8a8a8a"
+ }
+ ]
+ },
+ {
+ "featureType": "road.arterial",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "road.arterial",
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#373737"
+ }
+ ]
+ },
+ {
+ "featureType": "road.highway",
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#3c3c3c"
+ }
+ ]
+ },
+ {
+ "featureType": "road.highway",
+ "elementType": "labels",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "road.highway.controlled_access",
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#4e4e4e"
+ }
+ ]
+ },
+ {
+ "featureType": "road.local",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "road.local",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#616161"
+ }
+ ]
+ },
+ {
+ "featureType": "transit",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "transit",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#757575"
+ }
+ ]
+ },
+ {
+ "featureType": "water",
+ "elementType": "geometry",
+ "stylers": [
+ {
+ "color": "#000000"
+ }
+ ]
+ },
+ {
+ "featureType": "water",
+ "elementType": "labels.text",
+ "stylers": [
+ {
+ "visibility": "off"
+ }
+ ]
+ },
+ {
+ "featureType": "water",
+ "elementType": "labels.text.fill",
+ "stylers": [
+ {
+ "color": "#3d3d3d"
+ }
+ ]
+ }
+] \ No newline at end of file
diff --git a/client/src/lib/components/modal.js b/client/src/lib/components/modal.js
new file mode 100644
index 0000000..38869ce
--- /dev/null
+++ b/client/src/lib/components/modal.js
@@ -0,0 +1,41 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ View,
+} from 'react-native';
+
+export default class Modal extends Component {
+ render() {
+ let style = [ styles.modal ]
+ if (this.props.isVisible) {
+ style.push(styles.visible)
+ }
+ style.push( this.props.style )
+ return (
+ <View style={style} className='modal'>
+ {this.props.children}
+ </View>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ modal: {
+ position: 'fixed',
+ top: '100%',
+ left: 0,
+ width: '100%',
+ height: '100%',
+ zIndex: 2,
+ backgroundColor: 'black',
+ flex: 1,
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ opacity: 0,
+ },
+ visible: {
+ top: '0%',
+ opacity: 1,
+ },
+})
diff --git a/client/src/lib/components/pushNotifications.js b/client/src/lib/components/pushNotifications.js
new file mode 100644
index 0000000..832f45b
--- /dev/null
+++ b/client/src/lib/components/pushNotifications.js
@@ -0,0 +1,38 @@
+import PushNotification from 'react-native-push-notification'
+
+function init(cb) {
+ PushNotification.configure({
+ onRegister: (token) => {
+ fetch("https://hanselgretelarmory.com/_services/push/add", {
+ method: "POST",
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ registrationId: token.token,
+ channel: 'feed',
+ platform: token.os,
+ }),
+ }).then(() => {
+ // console.warn('pushed token')
+ }).catch((err) => {
+ // console.warn('got error')
+ })
+ },
+ onNotification: (notification) => {
+ cb(notification)
+ },
+ permissions: {
+ alert: true,
+ badge: false,
+ sound: false
+ },
+ popInitialNotification: true,
+ requestPermissions: true,
+ })
+}
+
+export default {
+ init: init,
+}
diff --git a/client/src/lib/components/scrollableContainer.js b/client/src/lib/components/scrollableContainer.js
new file mode 100644
index 0000000..b0beb63
--- /dev/null
+++ b/client/src/lib/components/scrollableContainer.js
@@ -0,0 +1,51 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ View,
+ ScrollView
+} from 'react-native';
+
+import Heading from '../components/heading'
+
+export default class ScrollableContainer extends Component {
+ constructor(props) {
+ super()
+ }
+ render() {
+ const { heading, bodyStyle, headingOnPress, ...props } = this.props
+ let headingEl;
+ if (heading) {
+ headingEl = (
+ <Heading style={styles.heading} onPress={headingOnPress}>
+ {heading.toUpperCase()}
+ </Heading>
+ )
+ }
+ else {
+ headingEl = null
+ }
+ return (
+ <View style={styles.container} {...props}>
+ <ScrollView ref={(ref) => this.scrollView = ref} contentContainerStyle={[styles.body, bodyStyle]}>
+ {headingEl}
+ {this.props.children}
+ <div style={{height: 40}}>footer</div>
+ </ScrollView>
+ </View>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ width: '100vw',
+ flex: 1,
+ justifyContent: 'flex-start',
+ },
+ heading: {
+ },
+ body: {
+ alignItems: 'center',
+ },
+})
+
diff --git a/client/src/lib/components/text.js b/client/src/lib/components/text.js
new file mode 100644
index 0000000..17e3cb0
--- /dev/null
+++ b/client/src/lib/components/text.js
@@ -0,0 +1,30 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ Text
+} from 'react-native';
+
+export default class ClearText extends Component {
+ render() {
+ let { style, ...others } = this.props
+ if (style) {
+ style = [ styles.text, style ]
+ }
+ else {
+ style = styles.text
+ }
+ return (
+ <Text style={style} children={this.props.children} {...others} />
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ text: {
+ color: '#ffffff',
+ fontFamily: 'Futura-Medium',
+ textAlign: 'center',
+ fontSize: 16,
+ lineHeight: 30,
+ }
+})
diff --git a/client/src/lib/components/webViewModal.js b/client/src/lib/components/webViewModal.js
new file mode 100644
index 0000000..0796470
--- /dev/null
+++ b/client/src/lib/components/webViewModal.js
@@ -0,0 +1,56 @@
+import React, { Component } from 'react'
+import {
+ StyleSheet,
+ WebView,
+ View,
+ ActivityIndicator,
+} from 'react-native'
+import Modal from 'react-native-modal'
+
+import Close from './close'
+
+export default class WebViewModal extends Component {
+ render() {
+ return (
+ <Modal isVisible={!!this.props.url} style={styles.modal}>
+ <WebView
+ source={{uri: this.props.url || 'about:blank'}}
+ style={styles.webview}
+ startInLoadingState={true}
+ renderLoading={() => {
+ return (
+ <View style={styles.loadingContainer}>
+ <ActivityIndicator size="large" color="white" />
+ </View>
+ )
+ }}
+ />
+ <Close onPress={this.props.onClose} />
+ </Modal>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ modal: {
+ marginLeft: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginTop: 0,
+ paddingTop: 60,
+ backgroundColor: 'black',
+ },
+ webview: {
+ flex: 1,
+ justifyContent: 'flex-start',
+ alignItems: 'flex-start',
+ },
+ loadingContainer: {
+ backgroundColor: 'black',
+ flex: 1,
+ height: '100%',
+ width: '100%',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+})
diff --git a/client/src/lib/components/youtube.js b/client/src/lib/components/youtube.js
new file mode 100644
index 0000000..ab5f8aa
--- /dev/null
+++ b/client/src/lib/components/youtube.js
@@ -0,0 +1,91 @@
+import React, { Component } from 'react';
+import {
+} from 'react-native';
+
+export default class Youtube extends Component {
+ constructor(props) {
+ super()
+ this.container = null
+ this.buildPlayer = this.buildPlayer.bind(this)
+ }
+ render() {
+ return (
+ <div ref={(ref) => this.container = ref} />
+ )
+ }
+ componentDidMount() {
+ if (YT_READY) {
+ this.buildPlayer()
+ }
+ else {
+ setTimeout(this.buildPlayer, 250)
+ }
+ }
+ buildPlayer() {
+ this.player = new YT.Player(this.container, {
+ videoId: this.props.ytid,
+ width: "100%",
+ height: 400,
+ playerVars: {
+ 'autohide': 1,
+ 'autoplay': 0,
+ 'disablekb': 0,
+ 'cc_load_policy': 3,
+ 'controls': 0,
+ 'enablejsapi': 1,
+ 'fs': 0,
+ 'modestbranding': 1,
+ 'iv_load_policy': 3,
+ 'loop': 0,
+ 'showinfo': 0,
+ 'rel': 0,
+ 'wmode': 'transparent',
+ 'hd': 1
+ },
+ events: {
+ onReady: function(e){
+ e.target.playVideo()
+ },
+ onStateChange: function(e){
+ switch(e.data){
+ case -1:
+ // unstarted
+ break
+ case 0:
+ // finished
+ break
+ case 1:
+ // play
+ break
+ case 2:
+ // pause
+ break
+ case 3:
+ // buffering
+ break
+ case 5:
+ // cued
+ break
+ default:
+ break
+ }
+ }
+ }
+ })
+ }
+ componentDidUpdate(prevProps, prevState) {
+ if (! this.player) {
+ return
+ }
+ else if (prevProps.ytid !== this.props.ytid) {
+ this.player.loadVideoById( this.props.ytid )
+ }
+ }
+}
+
+let YT_READY = false
+
+global.onYouTubeIframeAPIReady = function(){
+ console.log('youtube api loaded')
+ YT_READY = true
+}