initial search bar feature added on new branch

This commit is contained in:
Sol
2020-08-04 19:01:15 +01:00
parent dfeebce4b5
commit 9ca0a8fddc
7 changed files with 240 additions and 1 deletions

View File

@@ -334,6 +334,14 @@ export function toggleCover () {
}
}
export const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY'
export function updateSearchQuery (searchQuery) {
return {
type: UPDATE_SEARCH_QUERY,
searchQuery
}
}
// ERRORS
export const FETCH_SOURCE_ERROR = 'FETCH_SOURCE_ERROR'

View File

@@ -21,6 +21,7 @@ import TemplateCover from './TemplateCover'
import colors from '../common/global'
import { binarySearch, insetSourceFrom, findDescriptionInFilterTree } from '../common/utilities'
import { isMobile } from 'react-device-detect'
import Search from './Search.jsx'
class Dashboard extends React.Component {
constructor (props) {
@@ -35,6 +36,11 @@ class Dashboard extends React.Component {
this.getCategoryColor = this.getCategoryColor.bind(this)
this.findEventIdx = this.findEventIdx.bind(this)
this.onKeyDown = this.onKeyDown.bind(this)
<<<<<<< HEAD
=======
this.selectNarrativeStep = this.selectNarrativeStep.bind(this)
this.updateSearchQuery = this.updateSearchQuery.bind(this)
>>>>>>> 575b8f5... initial search bar feature added
}
componentDidMount () {
@@ -226,6 +232,11 @@ class Dashboard extends React.Component {
}
}
}
updateSearchQuery (e) {
let queryString = e.target.value;
this.props.actions.updateSearchQuery(queryString)
}
render () {
const { actions, app, domain, ui, features } = this.props
@@ -312,6 +323,12 @@ class Dashboard extends React.Component {
notifications={domain.notifications}
onToggle={actions.markNotificationsRead}
/>
<Search
narrative={app.narrative}
queryString={app.searchQuery}
onQueryUpdate={this.updateSearchQuery}
events={domain.events}
/>
{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') })

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

@@ -0,0 +1,67 @@
import React from 'react'
import '../scss/search.scss'
import SearchRow from './SearchRow.jsx'
class Search extends React.Component {
constructor(props) {
super(props)
this.state = {
isFolded : true,
searchResults: [],
queryString: ''
}
this.onButtonClick = this.onButtonClick.bind(this)
this.updateSearchQueryResults = this.updateSearchQueryResults.bind(this)
}
componentDidUpdate (prevProps, prevState) {
if (prevProps.queryString !== this.props.queryString) {
this.updateSearchQueryResults (this.props.queryString)
}
}
onButtonClick () {
this.setState(prevState => {
return { isFolded : !prevState.isFolded }
})
}
updateSearchQueryResults (queryString) {
let searchResults
if (queryString === '') {
searchResults = []
} else {
searchResults = this.props.events.filter(event =>
event.description.toLowerCase().includes(queryString.toLowerCase()) || event.location.includes(queryString) || event.category.includes(queryString)
)
}
this.setState({
searchResults: searchResults
})
}
render () {
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' value={this.props.queryString} onChange={this.props.onQueryUpdate} type='text' />
<i id='close-search-overlay' className='material-icons' onClick={this.onButtonClick} >close</i>
</div>
<div class='search-results'>
{this.state.searchResults.map(result => {
return <SearchRow query={this.props.queryString} category={result.category} location={result.location} date={result.date} description={result.description} />
})}
</div>
</div>
</div>
)
}
}
export default Search;

View File

@@ -0,0 +1,39 @@
import React from 'react'
const SearchRow = ({ description, category, location, date, query }) => {
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'>
<div className='location-date-container'>
<div className='location-container'>
<i className='material-icons'>location_on</i>
<p>{getHighlightedText(location, query)}</p>
</div>
<div className='date-container'>
<i className='material-icons'>event</i>
<p>{getHighlightedText(date, query)}</p>
</div>
</div>
<p>{getShortDescription(description, query).map(match => {
return <span>{getHighlightedText(match, query)}...<br></br></span>
})}</p>
</div>
)
}
export default SearchRow

View File

@@ -22,7 +22,8 @@ import {
FETCH_ERROR,
FETCH_SOURCE_ERROR,
SET_LOADING,
SET_NOT_LOADING
SET_NOT_LOADING,
UPDATE_SEARCH_QUERY
} from '../actions'
function updateHighlighted (appState, action) {
@@ -229,6 +230,13 @@ function setNotLoading (appState) {
}
}
function updateSearchQuery (appState, action) {
return {
...appState,
searchQuery: action.searchQuery
}
}
function app (appState = initial.app, action) {
switch (action.type) {
case UPDATE_HIGHLIGHTED:
@@ -275,6 +283,8 @@ function app (appState = initial.app, action) {
return setLoading(appState)
case SET_NOT_LOADING:
return setNotLoading(appState)
case UPDATE_SEARCH_QUERY:
return updateSearchQuery(appState, action)
default:
return appState
}

97
src/scss/search.scss Normal file
View File

@@ -0,0 +1,97 @@
#search-bar-icon-container {
position: absolute;
background-color: black;
color: #a0a0a0;
border: #a0a0a0 solid 0.1px;
top: 10px;
margin-left: 10px;
height: 24px;
padding: 10px;
&:hover {
cursor: pointer;
color: white;
}
}
.search-bar-overlay {
background-color: black;
height: 100vh;
width: 400px;
position: absolute;
transition: 0.2s ease;
}
.search-bar-input {
width: 300px;
margin: 20px;
line-height: 40px;
font-size: 15px;
color: gray;
padding-left: 15px;
background: black;
border: 1px solid #a0a0a0;
&:focus {
outline: none;
}
}
#close-search-overlay {
color: #a0a0a0;
vertical-align: middle;
font-size: 30px;
transition: 0.2s ease;
&:hover {
color: white;
cursor: pointer;
}
}
.folded {
left: -400px;
transition: 0.2s ease;
}
.search-outer-container {
position: absolute;
left: 110px;
&.narrative-mode {
left: 0;
}
}
.search-row {
color: white;
padding-left: 10px;
padding-right: 10px;
padding-top: 10px;
padding-bottom: 10px;
background-color: grey;
border-bottom: 1px white solid;
border-top: 1px white solid;
}
.search-row > p {
margin: 0;
}
.search-results {
height: calc(100% - 332px);
overflow: auto;
}
div.location-date-container {
margin-top: 10px;
margin-bottom: 10px;
}
div.location-date-container > div {
width: 50%;
display: inline-block;
vertical-align: top;
}
div.location-date-container > div > p {
display: inline;
line-height: 24px;
vertical-align: top;
}