Merge branch 'develop' of https://github.com/forensic-architecture/timemap into feature/refactor-filters-and-narratives-to-associations

This commit is contained in:
efarooqui
2020-09-22 10:26:10 -07:00
7 changed files with 254 additions and 1 deletions

View File

@@ -22,6 +22,7 @@ import TemplateCover from './TemplateCover'
import colors from '../common/global'
import { binarySearch, insetSourceFrom } from '../common/utilities'
import { isMobile } from 'react-device-detect'
import Search from './Search.jsx'
class Dashboard extends React.Component {
constructor (props) {
@@ -242,6 +243,7 @@ class Dashboard extends React.Component {
}
}
}
render () {
const { actions, app, domain, ui, features } = this.props
@@ -327,6 +329,12 @@ class Dashboard extends React.Component {
notifications={domain.notifications}
onToggle={actions.markNotificationsRead}
/>
<Search
narrative={app.narrative}
queryString={app.searchQuery}
events={domain.events}
onSearchRowClick={this.handleSelect}
/>
{app.source ? (
<MediaOverlay
source={app.source}

View File

@@ -92,6 +92,7 @@ class Map extends React.Component {
firstLayer.addTo(map)
map.keyboard.disable()
map.zoomControl.remove()
map.on('move zoomend viewreset moveend', () => this.alignLayers())
map.on('zoomstart', () => { if (this.svgRef.current !== null) this.svgRef.current.classList.add('hide') })

76
src/components/Search.jsx Normal file
View File

@@ -0,0 +1,76 @@
import React from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as actions from '../actions'
import '../scss/search.scss'
import SearchRow from './SearchRow.jsx'
class Search extends React.Component {
constructor (props) {
super(props)
this.state = {
isFolded: true
}
this.onButtonClick = this.onButtonClick.bind(this)
this.updateSearchQuery = this.updateSearchQuery.bind(this)
}
onButtonClick () {
this.setState(prevState => {
return { isFolded: !prevState.isFolded }
})
}
updateSearchQuery (e) {
let queryString = e.target.value
this.props.actions.updateSearchQuery(queryString)
}
render () {
let searchResults
const searchAttributes = ['description', 'location', 'category', 'date']
if (!this.props.queryString) {
searchResults = []
} else {
searchResults = this.props.events.filter(event =>
searchAttributes.some(attribute => event[attribute].toLowerCase().includes(this.props.queryString.toLowerCase()))
)
}
return (
<div class={'search-outer-container' + (this.props.narrative ? ' narrative-mode ' : '')}>
<div id='search-bar-icon-container' onClick={this.onButtonClick}>
<i className='material-icons'>search</i>
</div>
<div class={'search-bar-overlay' + (this.state.isFolded ? ' folded' : '')}>
<div class='search-input-container'>
<input class='search-bar-input' onChange={this.updateSearchQuery} type='text' />
<i id='close-search-overlay' className='material-icons' onClick={this.onButtonClick} >close</i>
</div>
<div class='search-results'>
{searchResults.map(result => {
return <SearchRow onSearchRowClick={this.props.onSearchRowClick} eventObj={result} query={this.props.queryString} />
})}
</div>
</div>
</div>
)
}
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(
state => state,
mapDispatchToProps
)(Search)

View File

@@ -0,0 +1,40 @@
import React from 'react'
const SearchRow = ({ query, eventObj, onSearchRowClick }) => {
const { description, location, date } = eventObj
function getHighlightedText (text, highlight) {
// Split text on highlight term, include term itself into parts, ignore case
const parts = text.split(new RegExp(`(${highlight})`, 'gi'))
return <span>{ parts.map(part => part.toLowerCase() === highlight.toLowerCase() ? <span style={{ backgroundColor: 'yellow', color: 'black' }}>{part}</span> : part) }</span>
}
function getShortDescription (text, searchQuery) {
var regexp = new RegExp(`(([^ ]* ){0,6}[a-zA-Z]*${searchQuery.toLowerCase()}[a-zA-Z]*( [^ ]*){0,5})`, 'gm')
let parts = text.toLowerCase().match(regexp)
for (var x = 0; x < (parts ? parts.length : 0); x++) {
parts[x] = '...' + parts[x]
}
const firstLine = [text.match('(([^ ]* ){0,10})', 'm')[0]]
return parts || firstLine
}
return (
<div className='search-row' onClick={() => onSearchRowClick([eventObj])}>
<div className='location-date-container'>
<div className='date-container'>
<i className='material-icons'>event</i>
<p>{getHighlightedText(date, query)}</p>
</div>
<div className='location-container'>
<i className='material-icons'>location_on</i>
<p>{getHighlightedText(location, query)}</p>
</div>
</div>
<p>{getShortDescription(description, query).map(match => {
return <span>{getHighlightedText(match, query)}...<br /></span>
})}</p>
</div>
)
}
export default SearchRow