summaryrefslogtreecommitdiff
path: root/client/src/lib/timeline
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/timeline
parentb694bd511ceccd00d4a4c98f36f910d5fc5f79c4 (diff)
react-native-web port of fmf app
Diffstat (limited to 'client/src/lib/timeline')
-rw-r--r--client/src/lib/timeline/index.js164
-rw-r--r--client/src/lib/timeline/tickMarks.js192
-rw-r--r--client/src/lib/timeline/timelineEvent.js102
-rw-r--r--client/src/lib/timeline/timelineFilter.js127
-rw-r--r--client/src/lib/timeline/timelineFull.js227
-rw-r--r--client/src/lib/timeline/timelineHeader.js111
6 files changed, 923 insertions, 0 deletions
diff --git a/client/src/lib/timeline/index.js b/client/src/lib/timeline/index.js
new file mode 100644
index 0000000..3fde6df
--- /dev/null
+++ b/client/src/lib/timeline/index.js
@@ -0,0 +1,164 @@
+import React, { Component } from 'react';
+import {
+ FlatList,
+ StyleSheet,
+ Text,
+ TouchableOpacity,
+ View,
+ ScrollView,
+} from 'react-native';
+
+import ScrollableContainer from '../components/scrollableContainer'
+import Modal from '../components/modal'
+import Container from '../components/container'
+import ClearText from '../components/text'
+import TimelineEvent from './timelineEvent'
+import TimelineFull from './timelineFull'
+import TimelineHeader from './timelineHeader'
+import TimelineFilter from './timelineFilter'
+import TickMarks from './tickMarks'
+
+export default class Timeline extends Component {
+ constructor(props) {
+ super()
+ this.state = {
+ events: props.events,
+ tag: props.tag,
+ introVisible: props.firstTime,
+ modalVisible: false,
+ filterVisible: false,
+ item: null,
+
+ introVisible: false,
+// modalVisible: true,
+// item: props.events[2],
+ }
+ this.onPress = this.onPress.bind(this)
+ this.onFilter = this.onFilter.bind(this)
+ this.scrollToIndex = this.scrollToIndex.bind(this)
+ this.items = []
+ }
+ onPress(item) {
+ this.setState({
+ modalVisible: true,
+ item: item,
+ })
+ }
+ onFilter(tag) {
+ let events;
+ if (!! tag && tag !== this.state.tag) {
+ events = this.props.events.filter((e) => e && e.keywords.includes(tag))
+ }
+ else {
+ events = this.props.events
+ }
+ this.setState({ events, tag, modalVisible: false, filterVisible: false })
+ }
+ scrollToIndex(index) {
+ // this.flatList._listRef.scrollToIndex(index)
+ // this.scrollable.scrollToIndex({index: index})
+ const offset = this.items[index].ref.parentNode.offsetTop
+ this.scrollable.scrollView.scrollTo({
+ y: offset,
+ x: 0,
+ animated: true,
+ })
+ }
+ render() {
+ const heading = this.state.tag || 'History of Surveillance'
+ const items = this.state.events.map((item, i) => (
+ <TimelineEvent ref={(ref) => this.items[i] = ref} key={item.id} onPress={this.onPress} item={item} />
+ ))
+
+ return (
+ <View>
+ <ScrollableContainer ref={(ref) => this.scrollable = ref} heading={heading} headingOnPress={() => this.onFilter("")}>
+ {items}
+ </ScrollableContainer>
+ <TickMarks events={this.state.events} scrollToIndex={this.scrollToIndex} />
+ <Modal isVisible={this.state.introVisible} style={[styles.modal, styles.headerModal]}>
+ <TimelineHeader
+ content={this.props.content}
+ onLinkPress={this.props.onLinkPress}
+ onClose={() => this.setState({ introVisible: false })}
+ />
+ </Modal>
+ <Modal isVisible={this.state.modalVisible} style={styles.modal}>
+ <TimelineFull
+ item={this.state.item}
+ onLinkPress={this.props.onLinkPress}
+ onFilter={this.onFilter}
+ onClose={() => this.setState({ modalVisible: false })}
+ />
+ </Modal>
+ </View>
+ )
+ }
+}
+
+
+
+// <View style={styles.body}>
+// <View style={styles.sidebar}>
+// <TouchableOpacity onPress={() => this.setState({ filterVisible: true })}>
+// <ClearText style={[styles.filterText, !! this.state.tag ? styles.filterActive : styles.filterInactive ]}>
+// { !! this.state.tag ? 'FILTER' : '' }
+// </ClearText>
+// </TouchableOpacity>
+// </View>
+// </View>
+
+// <Modal isVisible={this.state.filterVisible} style={styles.modal}>
+// <TimelineFilter
+// events={this.props.events}
+// tag={this.state.tag}
+// onFilter={this.onFilter}
+// onClose={() => this.setState({ filterVisible: false })}
+// />
+// </Modal>
+
+const styles = StyleSheet.create({
+ wrapper: {
+ },
+ modal: {
+ margin: 0,
+ padding: 0,
+ },
+ headerModal: {
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ backgroundColor: 'rgb(0,0,0)',
+ margin: 0,
+ padding: 0,
+ },
+ body: {
+ flexDirection: 'row',
+ height: 840,
+ },
+ flatList: {
+ },
+ container: {
+ flex: 1,
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ },
+ sidebar: {
+ flex: 0,
+ },
+ filterText: {
+ color: '#bbb',
+ textAlign: 'left',
+ fontSize: 12,
+ marginLeft: 4,
+ },
+ filterActive: {
+ color: '#fff',
+ textDecorationLine: 'underline',
+ },
+ filterInactive: {
+ color: '#bbb',
+ },
+ tickMarks: {
+ flex: 0,
+ },
+})
diff --git a/client/src/lib/timeline/tickMarks.js b/client/src/lib/timeline/tickMarks.js
new file mode 100644
index 0000000..c8bda29
--- /dev/null
+++ b/client/src/lib/timeline/tickMarks.js
@@ -0,0 +1,192 @@
+import React, { Component } from 'react'
+
+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
+
+function getFirstTouch(f) {
+ return (e) => f(e.touches[0])
+}
+
+export default class TickMarks extends Component {
+ constructor(props) {
+ super()
+
+ this.width = 45
+ this.height = window.innerHeight - 160
+ this.tag = ""
+
+ this.onTouchStart = this.onTouchStart.bind(this)
+ this.onTouchMove = this.onTouchMove.bind(this)
+ this.onTouchEnd = this.onTouchEnd.bind(this)
+ }
+
+ buildMarkers() {
+ this.yearTicks = []
+ this.years = []
+
+ const height = this.height
+ const width = this.width
+
+ const yPadding = fontStyle.fontSize
+ const heightRange = height - yPadding * 2
+
+ const startYear = 1800
+ const endYear = 2018
+ const yearStep = 10
+ const yearMarkerStep = 50
+ const yearDiff = endYear - startYear
+
+ for (let year = startYear; year <= endYear; year += yearStep) {
+ let y = (year - startYear) / yearDiff * heightRange + yPadding
+ let isMarker = (year % yearMarkerStep) == 0
+ let style = isMarker ? tickStyles.marker : tickStyles.year
+ this.yearTicks.push(
+ <line
+ key={'tick_' + year}
+ x1={width}
+ y1={y}
+ x2={width - style.width}
+ y2={y}
+ stroke={style.stroke}
+ strokeWidth={style.strokeWidth}
+ />
+ )
+ if (isMarker) {
+ this.years.push(
+ <text
+ key={'label_' + year}
+ fontFamily="Futura-Medium"
+ fill={fontStyle.fill}
+ fontSize={fontStyle.fontSize}
+ x={1}
+ y={y - fontStyle.yOffset}
+ textAnchor="start"
+ >{year}</text>
+ )
+ }
+ }
+ this.yearsByOffset = []
+ this.eventTicks = this.props.events.map((event, index) => {
+ if (event.date.match(/\D/)) {
+ return null
+ }
+ const year = parseInt(event.date)
+ const y = (year - startYear) / yearDiff * heightRange + yPadding
+ const style = tickStyles.event
+ if (year > 1800) {
+ this.yearsByOffset.push([y, Math.max(index-1, 0)])
+ }
+ return (
+ <line
+ key={'event_' + event.id.replace(/-/, '_')}
+ x1={width}
+ y1={y+1}
+ x2={width - style.width}
+ y2={y+1}
+ stroke={style.stroke}
+ strokeWidth={style.strokeWidth}
+ />
+ )
+ }).filter(e => !!e)
+ }
+
+ scrollToYPosition(y) {
+ let index;
+ y -= this.svg.getBoundingClientRect().top
+ var foundOffset = this.yearsByOffset.some((pair) => {
+ if (y < pair[0]) {
+ index = pair[1]
+ return true
+ }
+ return false
+ })
+ if (foundOffset) {
+ this.props.scrollToIndex(index)
+ }
+ }
+
+ render() {
+ this.buildMarkers()
+ this.tag = this.props.tag
+ return (
+ <svg
+ style={styles.svg}
+ height={this.height}
+ width={this.width}
+ ref={(ref) => this.svg = ref}
+ >
+ {this.eventTicks}
+ {this.yearTicks}
+ {this.years}
+ </svg>
+ )
+ }
+
+ componentDidMount() {
+ if (isMobile) {
+ this.svg.addEventListener("touchstart", getFirstTouch(this.onTouchStart))
+ this.svg.addEventListener("touchmove", getFirstTouch(this.onTouchMove))
+ window.addEventListener("touchend", getFirstTouch(this.onTouchEnd))
+ }
+ else {
+ this.svg.addEventListener("mousedown", this.onTouchStart)
+ this.svg.addEventListener("mousemove", this.onTouchMove)
+ window.addEventListener("mouseup", this.onTouchEnd)
+ }
+ }
+
+ onTouchStart(e) {
+ this.dragging = true
+ this.scrollToYPosition(e.pageY)
+ }
+ onTouchMove(e) {
+ if (this.dragging || isMobile) {
+ this.scrollToYPosition(e.pageY)
+ }
+ }
+ onTouchEnd(e) {
+ this.dragging = false
+ }
+
+}
+
+
+const styles = {
+ svg: {
+ position: 'fixed',
+ top: 105,
+ left: '2%',
+ zIndex: 2,
+ },
+}
+
+const tickStyles = {
+ year: {
+ width: 4,
+ stroke: '#bbb',
+ strokeWidth: 1,
+ },
+ marker: {
+ width: 10,
+ stroke: '#888',
+ strokeWidth: 1
+ },
+ event: {
+ width: 10,
+ stroke: 'white',
+ strokeWidth: 2
+ },
+}
+const eventColors = {
+ 'Surveillance': 'rgb(0,64,255)',
+ 'Drones': 'rgb(255,0,0)',
+ 'Facial Recognition': 'rgb(0,255,0)',
+}
+const fontStyle = {
+ fontSize: 12,
+ yOffset: -3,
+ fill: '#bbb',
+}
diff --git a/client/src/lib/timeline/timelineEvent.js b/client/src/lib/timeline/timelineEvent.js
new file mode 100644
index 0000000..903ceed
--- /dev/null
+++ b/client/src/lib/timeline/timelineEvent.js
@@ -0,0 +1,102 @@
+import React, { Component } from 'react';
+import {
+ TouchableOpacity,
+ StyleSheet,
+ Image,
+ View
+} from 'react-native';
+
+import ClearText from '../components/text'
+
+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 imageHeight = isMobile ? 70 : 100
+const imageWidth = isMobile ? 100 : 150
+
+export default class TimelineEvent extends Component {
+ constructor() {
+ super()
+ }
+ render() {
+ const item = this.props.item
+ let image;
+ if (item.image && item.image.uri) {
+ const originalWidth = Number(item.image.width)
+ const originalHeight = Number(item.image.height)
+ let height = originalHeight > imageHeight ? imageHeight : originalHeight
+ let width = originalWidth * height / originalHeight
+ if (width > imageWidth) {
+ width = imageWidth
+ height = originalHeight * imageWidth / originalWidth
+ }
+ if (isNaN(width) || isNaN(height)) {
+ console.log(width, height, item.image.uri)
+ }
+ image = <img
+ src={item.image.uri}
+ style={{
+ width: width,
+ height: height,
+ }} />
+ } else {
+ image = <View></View>
+ }
+ return (
+ <TouchableOpacity style={styles.item} activeOpacity={0.8} onPress={() => this.props.onPress(this.props.item) }>
+ <div ref={(ref) => this.ref = ref} style={{flex: 0}}></div>
+ <View style={styles.item}>
+ <View style={styles.dateContainer}>
+ <ClearText style={styles.date}>{item.date}</ClearText>
+ </View>
+ <View style={styles.imageContainer}>{image}</View>
+ <View style={styles.titleContainer}>
+ <ClearText style={styles.title}>{item.title}</ClearText>
+ </View>
+ </View>
+ </TouchableOpacity>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ item: {
+ flex: 1,
+ width: '80%',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ flexDirection: 'row',
+ padding: 10,
+ marginBottom: 10,
+ minHeight: 100,
+ },
+ dateContainer: {
+ width: '30%',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ paddingRight: 10,
+ paddingLeft: 30,
+ },
+ imageContainer: {
+ width: '40%',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ marginRight: 10,
+ },
+ titleContainer: {
+ width: '30%',
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ paddingLeft: 10,
+ },
+ title: {
+ textAlign: 'center',
+ fontWeight: 'bold',
+ },
+ date: {
+ textAlign: 'center',
+ },
+})
diff --git a/client/src/lib/timeline/timelineFilter.js b/client/src/lib/timeline/timelineFilter.js
new file mode 100644
index 0000000..c6da98a
--- /dev/null
+++ b/client/src/lib/timeline/timelineFilter.js
@@ -0,0 +1,127 @@
+import React, { Component } from 'react';
+import {
+ ScrollView,
+ StyleSheet,
+ TouchableOpacity,
+ Image,
+ View,
+ Text,
+ RefreshControl,
+} from 'react-native';
+
+import ClearText from '../components/text'
+import Heading from '../components/heading'
+import Definition from '../components/definition'
+import Close from '../components/close'
+
+export default class TimelineFilter extends Component {
+ constructor(props) {
+ super()
+ this.onRefresh = this.onRefresh.bind(this)
+ this.buildCounts(props.events)
+ }
+ buildCounts(events){
+ const lookup = {}
+ events.forEach((event) => {
+ event.keywords.forEach((t) => {
+ lookup[t] = lookup[t] || 0
+ lookup[t] += 1
+ })
+ })
+
+ this.tags = Object.keys(lookup)
+ .map((t) => [t, lookup[t]])
+ .sort((a,b) => a[1]<b[1]?-1:a[1]==b[1]?0:1)
+ .filter((t) => t[1] > 1)
+ .reverse()
+ }
+ buildTagItems() {
+ const maxFontSize = 22
+ const minFontSize = 12
+ const fontSizeDiff = maxFontSize - minFontSize
+ const maxCount = this.tags[2][1]
+ const selectedTag = this.props.tag
+
+ return this.tags.map((pair, i) => {
+ const tag = pair[0]
+ const count = pair[1]
+ const fontSize = Math.min(maxCount, count) / maxCount * (fontSizeDiff) + minFontSize
+ const textDecorationLine = tag === selectedTag ? 'underline' : 'none'
+ const color = (!! selectedTag && tag !== selectedTag) ? '#bbb' : '#fff'
+
+ return (
+ <ClearText
+ key={'tag_' + i}
+ style={[styles.link, {fontSize, padding: 10, textDecorationLine, color}]}
+ onPress={() => this.props.onFilter(tag)}
+ >
+ {tag}
+ </ClearText>
+ )
+ })
+
+ }
+ onRefresh() {
+ this.props.onClose()
+ }
+ render() {
+ const tagItems = this.buildTagItems()
+ const refreshControl = (
+ <RefreshControl
+ refreshing={false}
+ onRefresh={this.onRefresh}
+ tintColor={'rgba(0,0,0,0)'}
+ />
+ )
+
+ return (
+ <View style={styles.container}>
+ <ScrollView
+ style={styles.scrollView}
+ contentContainerStyle={styles.body}
+ horizontal={false}
+ showsHorizontalScrollIndicator={false}
+ refreshControl={refreshControl}
+ >
+ <Heading style={styles.heading}>CATEGORIES</Heading>
+ <View style={styles.tagItems}>{tagItems}</View>
+ </ScrollView>
+ <Close onPress={this.props.onClose} />
+ </View>
+ )
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'flex-start',
+ alignItems: 'flex-start',
+ },
+ heading: {
+ marginLeft: 0,
+ },
+ scrollView: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ },
+ body: {
+ backgroundColor: 'black',
+ height: '100%',
+ },
+ tagItems: {
+ flexWrap: 'wrap',
+ justifyContent: 'center',
+ alignItems: 'center',
+ flexDirection: 'row',
+ padding: 5,
+ },
+ link: {
+ marginHorizontal: 2,
+ marginVertical: 2,
+ lineHeight: 16,
+ },
+})
diff --git a/client/src/lib/timeline/timelineFull.js b/client/src/lib/timeline/timelineFull.js
new file mode 100644
index 0000000..a004987
--- /dev/null
+++ b/client/src/lib/timeline/timelineFull.js
@@ -0,0 +1,227 @@
+import React, { Component } from 'react';
+import {
+ ScrollView,
+ StyleSheet,
+ TouchableOpacity,
+ Image,
+ View,
+ RefreshControl,
+} from 'react-native';
+
+import HTMLView from 'react-native-htmlview'
+import htmlStyles from '../components/htmlStyles'
+
+import ClearText from '../components/text'
+import Heading from '../components/heading'
+import Definition from '../components/definition'
+import Close from '../components/close'
+
+export default class TimelineFull extends Component {
+ constructor() {
+ super()
+ this.onRefresh = this.onRefresh.bind(this)
+ this.onPickTag = this.onPickTag.bind(this)
+ }
+ onRefresh() {
+ this.props.onClose()
+ }
+ onPickTag(tag) {
+ this.props.onFilter(tag)
+ }
+ render() {
+ const item = this.props.item
+ let image, links;
+ if (! item) {
+ return ( <View></View> )
+ }
+
+ if (item.image) {
+ const caption = item.credit ? (
+ <ClearText style={styles.caption}>{item.credit}</ClearText>
+ ) : (
+ <View></View>
+ )
+ const originalWidth = Number(item.image.width)
+ const originalHeight = Number(item.image.height)
+ const height = originalHeight > 450 ? 450 : originalHeight
+ const width = originalWidth * height / originalHeight
+ image = (
+ <View style={styles.imageContainer}>
+ <View style={styles.imageWrapper}>
+ <img src={item.image.uri}
+ style={[styles.image, {
+ width: width,
+ height: height,
+ }]} />
+ </View>
+ {caption}
+ </View>
+ )
+ } else {
+ image = ( <View></View> )
+ }
+
+ if (item.links.length) {
+ const linkItems = item.links.map((link, i) => {
+ const url = link.uri
+ let name = link.text
+ if (! name || name.match(/Link Text/i)) {
+ name = linkTextFromUrl(url)
+ }
+ return (
+ <TouchableOpacity key={'link_' + i} onPress={() => this.props.onLinkPress(url)}>
+ <ClearText style={styles.link}>{name}</ClearText>
+ </TouchableOpacity>
+ )
+ })
+ links = (
+ <Definition label='Links' contentIsView={true}>{linkItems}</Definition>
+ )
+ }
+
+ const tags = item.keywords.map((tag, i) => {
+ return `<a href='${tag}'>${tag}</a>`
+ }).join(', ')
+
+ const description = '<p>' + item.description + '</p>'
+
+ return (
+ <View style={styles.container}>
+ <ScrollView
+ contentContainerStyle={styles.item}
+ horizontal={false}
+ showsHorizontalScrollIndicator={false}
+ >
+ {image}
+ <Heading style={styles.title}>{item.title}</Heading>
+ <View style={styles.contentContainer}>
+ <View style={styles.bodyContainer}>
+ <HTMLView value={description} style={styles.description} stylesheet={htmlStyles} onLinkPress={this.props.onLinkPress} />
+ </View>
+ <View style={styles.metadataContainer}>
+ <Definition label='Date'>{item.date}</Definition>
+ <Definition label='Medium'>{item.medium}</Definition>
+ <Definition label='Category' contentIsView={true}>
+ <TouchableOpacity onPress={() => this.onPickTag(item.category)}>
+ <ClearText style={styles.link}>{item.category}</ClearText>
+ </TouchableOpacity>
+ </Definition>
+ <Definition label='Tags' contentIsView={true}>
+ <HTMLView value={'<p>' + tags + '</p>'} stylesheet={tagHTMLStyles} onLinkPress={this.onPickTag} />
+ </Definition>
+ {links}
+ </View>
+ </View>
+ </ScrollView>
+ <Close onPress={this.props.onClose} />
+ </View>
+ )
+ }
+}
+
+
+function linkTextFromUrl (url) {
+ const url_parts = url.split('/')
+ const domain = url_parts[2]
+ const terms = domain.split('.')
+ const len = terms.length
+ let term = (len > 2 && terms[len-1].length == 2) ? terms[len-3] : terms[len-2]
+ if (term == 'wikipedia') {
+ term += url_parts[4].replace(/\#.*/,'').replace('_', ' ')
+ }
+ return capitalize(term)
+}
+function capitalize (s){
+ return s.charAt(0).toUpperCase() + s.slice(1)
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ scrollView: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'black',
+ },
+ item: {
+ maxWidth: 1000,
+ padding: 40,
+ },
+ imageContainer: {
+ width: '100%',
+ marginBottom: 10,
+ alignItems: 'center',
+ },
+ imageWrapper: {
+ width: '100%',
+ marginBottom: 5,
+ alignItems: 'center',
+ },
+ caption: {
+ fontSize: 12,
+ },
+
+ bodyContainer: {
+ left: '0%',
+ width: '60%',
+ paddingRight: 50,
+ },
+ metadataContainer: {
+ position: 'absolute',
+ left: '60%',
+ width: '40%',
+ },
+ title: {
+ textAlign: 'left',
+ fontWeight: 'bold',
+ fontSize: 18,
+ },
+ date: {
+ textAlign: 'left',
+ },
+ description: {
+ flex: 1,
+ flexDirection: 'column',
+ },
+ link: {
+ textDecorationLine: 'underline',
+ textAlign: 'left',
+ },
+ tag: {
+ marginRight: 5,
+ },
+})
+
+const tagHTMLStyles = 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/timeline/timelineHeader.js b/client/src/lib/timeline/timelineHeader.js
new file mode 100644
index 0000000..559ace9
--- /dev/null
+++ b/client/src/lib/timeline/timelineHeader.js
@@ -0,0 +1,111 @@
+import React, { Component } from 'react';
+import {
+ StyleSheet,
+ TouchableOpacity,
+ Text,
+ View,
+ ScrollView,
+} from 'react-native';
+import HTMLView from 'react-native-htmlview'
+
+import htmlStyles from '../components/htmlStyles'
+import Heading from '../components/heading'
+import ClearText from '../components/text'
+import Button from '../components/button'
+import Close from '../components/close'
+
+export default class TimelineHeader extends Component {
+ constructor(props) {
+ super()
+ }
+ render() {
+ if (!! this.props.tag) {
+ return (
+ <View style={styles.tagContainer}></View>
+ )
+ }
+ else {
+ const body = '<p>' + this.props.content.body + '</p>'
+ return (
+ <ScrollView contentContainerStyle={styles.container}>
+ <View style={styles.body}>
+ <Heading>HISTORY OF SURVEILLANCE</Heading>
+ <HTMLView value={body} stylesheet={timelineHTMLStyles} onLinkPress={this.props.onLinkPress} />
+ <Button buttonStyle={styles.buttonStyle} onPress={this.props.onClose} label={'VIEW THE TIMELINE'} />
+ </View>
+ </ScrollView>
+ )
+ }
+ }
+}
+
+const styles = StyleSheet.create({
+ container: {
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ },
+ body: {
+ marginTop: 20,
+ maxWidth: 650,
+ marginBottom: 10,
+ backgroundColor: 'black',
+ padding: 40,
+ },
+ buttonStyle: {
+ marginTop: 40,
+ marginLeft: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ },
+})
+
+const timelineHTMLStyles = 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: {
+ color: 'white',
+ fontFamily: 'Futura-MediumItalic',
+ fontSize: 16,
+ lineHeight: 30,
+ },
+ a: {
+ color: 'white',
+ fontFamily: 'Futura-Medium',
+ textDecorationLine: 'underline',
+ fontSize: 16,
+ lineHeight: 30,
+ },
+ red: {
+ color: '#f00',
+ fontWeight: 'bold',
+ fontFamily: 'Futura-MediumItalic',
+ fontSize: 16,
+ lineHeight: 24,
+ },
+ green: {
+ color: '#0f0',
+ fontWeight: 'bold',
+ fontFamily: 'Futura-MediumItalic',
+ fontSize: 16,
+ lineHeight: 24,
+ },
+ blue: {
+ color: '#08f',
+ fontWeight: 'bold',
+ fontFamily: 'Futura-MediumItalic',
+ fontSize: 16,
+ lineHeight: 24,
+ },
+})
+