Completely re-wrote our sidebar. Now we can filter by search and category on top and a list of all elements are displayed below in a scrollable list. Each element has a indicator showing if the item is requested, downloading or downloaded. There are several generator functions that are to be pulled out in their seperate components.

This commit is contained in:
2018-01-09 16:25:52 +01:00
parent e64d88bfdf
commit 262093d196
2 changed files with 321 additions and 183 deletions

View File

@@ -7,207 +7,242 @@ import sidebarCSS from '../styles/adminSidebar.jsx'
class SidebarComponent extends Component { class SidebarComponent extends Component {
constructor(props){ constructor(props){
super(props) super(props)
// Constructor with states holding the search query and the element of reponse. // Constructor with states holding the search query and the element of reponse.
this.state = { this.state = {
filterValue: '', filterValue: '',
filterQuery: '', filterQuery: '',
requestItemsToBeDisplayed: [], requestItemsToBeDisplayed: [],
listItemSelected: '', listItemSelected: '',
} height: '0',
} }
// Where we wait for api response to be delivered from parent through props this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
componentWillReceiveProps(props) { }
this.state.listItemSelected = props.listItemSelected;
this.displayRequestedElementsInfo(props.requested_objects);
}
// Inputs a date and returns a text string that matches how long it was since // Where we wait for api response to be delivered from parent through props
convertDateToDaysSince(date) { componentWillReceiveProps(props) {
var oneDay = 24*60*60*1000; this.state.listItemSelected = props.listItemSelected;
var firstDate = new Date(date); this.displayRequestedElementsInfo(props.requested_objects);
var secondDate = new Date(); }
var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay)); componentDidMount() {
this.updateWindowDimensions();
switch (diffDays) { window.addEventListener('resize', this.updateWindowDimensions);
case 0: }
return 'Today';
case 1:
return '1 day ago'
default:
return diffDays + ' days ago'
}
}
// Called from our dropdown, receives a filter string and checks it with status field componentWillUnmount() {
// of our request objects. window.removeEventListener('resize', this.updateWindowDimensions);
filterItems(filterValue) { }
let filteredRequestElements = this.props.requested_objects.map((item, index) => {
if (item.status === filterValue || filterValue === 'all')
return this.generateListElements(index, item);
})
this.setState({ updateWindowDimensions() {
requestItemsToBeDisplayed: filteredRequestElements, this.setState({ height: window.innerHeight });
filterValue: filterValue, }
})
} // Inputs a date and returns a text string that matches how long it was since
convertDateToDaysSince(date) {
var oneDay = 24*60*60*1000;
var firstDate = new Date(date);
var secondDate = new Date();
var diffDays = Math.round(Math.abs((firstDate.getTime() - secondDate.getTime()) / oneDay));
switch (diffDays) {
case 0:
return 'Today';
case 1:
return '1 day ago'
default:
return diffDays + ' days ago'
}
}
// Called from our dropdown, receives a filter string and checks it with status field
// of our request objects.
filterItems(filterValue) {
let filteredRequestElements = this.props.requested_objects.map((item, index) => {
if (item.status === filterValue || filterValue === 'all')
return this.generateListElements(index, item);
})
this.setState({
requestItemsToBeDisplayed: filteredRequestElements,
filterValue: filterValue,
})
}
// Updates the internal state of the query filter and updates the list to only // Updates the internal state of the query filter and updates the list to only
// display names matching the query. This is real-time filtering. // display names matching the query. This is real-time filtering.
updateFilterQuery(event) { updateFilterQuery(event) {
const query = event.target.value; const query = event.target.value;
let filteredByQuery = this.props.requested_objects.map((item, index) => { let filteredByQuery = this.props.requested_objects.map((item, index) => {
if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1) if (item.name.toLowerCase().indexOf(query.toLowerCase()) != -1)
return this.generateListElements(index, item); return this.generateListElements(index, item);
}) })
this.setState({ console.log(filteredByQuery)
requestItemsToBeDisplayed: filteredByQuery, this.setState({
filterQuery: query, requestItemsToBeDisplayed: filteredByQuery,
}); filterQuery: query,
} });
}
generateFilterDropdown() { generateFilterSearch() {
return ( return (
<select onChange={ event => this.filterItems(event.target.value) } value={this.state.filterValue}> <div style={sidebarCSS.searchSidebar}>
<option value='all'>All</option> <div style={sidebarCSS.searchInner}>
<option value='requested'>Requested</option> <input
<option value='downloading'>Downloading</option> type="text"
<option value='downloaded'>Downloaded</option> id="search"
</select> style={sidebarCSS.searchTextField}
) placeholder="Search requested items"
} onChange={event => this.updateFilterQuery(event)}
value={this.state.filterQuery}/>
<span>
<svg id="icon-search" style={sidebarCSS.searchIcon} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15">
<g id="search">
<circle style={sidebarCSS.searchSVGIcon} cx="6.055" cy="5.805" r="5.305"></circle>
<path style={sidebarCSS.searchSVGIcon} d="M9.847 9.727l4.166 4.773"></path>
</g>
</svg>
</span>
</div>
</div>
)
}
generateFilterSearchbar() { generateNav() {
return ( let filterValue = this.state.filterValue;
<input
type="text"
id="search"
placeholder="Filter by name..."
onChange={event => this.updateFilterQuery(event)}
value={this.state.filterQuery}/>
)
}
// A colored bar indicating the status of a item by color. return (
generateRequestIndicator(status) { <nav style={sidebarCSS.sidebar_navbar_underline}>
let statusColor; <ul style={sidebarCSS.ulFilterSelectors}>
<li>
<span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('all') }>All</span>
{ (filterValue === 'all' || filterValue === '') && <span style={sidebarCSS.spanFilterSelectors}></span> }
</li>
switch (status) { <li>
case 'requested': <span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('requested') }>Requested</span>
// Yellow { filterValue === 'requested' && <span style={sidebarCSS.spanFilterSelectors}></span> }
statusColor = '#ffe14d'; </li>
break;
case 'downloading':
// Blue
statusColor = '#3fc3f3';
break;
case 'downloaded':
// Green
statusColor = '#6be682';
break;
default:
statusColor = 'grey';
}
const indicatorCSS = { <li>
width: '100%', <span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('downloading') }>Downloading</span>
height: '4px', { filterValue === 'downloading' && <span style={sidebarCSS.spanFilterSelectors}></span> }
marginTop: '3px', </li>
backgroundColor: statusColor,
}
return ( <li>
<div style={indicatorCSS}></div> <span style={sidebarCSS.aFilterSelectors} onClick = { event => this.filterItems('downloaded') }>Downloaded</span>
) { filterValue === 'downloaded' && <span style={sidebarCSS.spanFilterSelectors}></span> }
} </li>
</ul>
</nav>
)
}
generateBody(cards) {
let style = sidebarCSS.ulCard;
style.maxHeight = this.state.height - 160;
return (
<ul style={style}>
{ cards }
</ul>
)
}
generateListElements(index, item) { generateListElements(index, item) {
if (index == this.state.listItemSelected) { let statusBar;
return (
<div style={sidebarCSS.parentElement_selected}>
<div style={sidebarCSS.contentContainer}>
<span style={sidebarCSS.title}> {item.name } </span>
<div style={sidebarCSS.rightContainer}>
<span>{ this.convertDateToDaysSince(item.requested_date) }</span>
</div>
</div>
<span>Status: { item.status }</span>
<br/>
<span>Matches found: 0</span>
{ this.generateRequestIndicator(item.status) }
</div>
)
}
else
return (
<Link style={sidebarCSS.link} to={{ pathname: '/admin/'+String(index)}}>
<Interactive
key={index}
style={sidebarCSS.parentElement}
as='div'
hover={sidebarCSS.parentElement_hover}
focus={sidebarCSS.parentElement_hover}
active={sidebarCSS.parentElement_active}>
<span style={sidebarCSS.title}> {item.name } </span> switch (item.status) {
<div style={sidebarCSS.rightContainer}> case 'requested':
<span>{ this.convertDateToDaysSince(item.requested_date) }</span> // Yellow
</div> statusBar = { background: 'linear-gradient(to right, rgb(63, 195, 243) 0, rgb(63, 195, 243) 4px, #fff 4px, #fff 100%) no-repeat' }
<br/> break;
{ this.generateRequestIndicator(item.status) } case 'downloading':
</Interactive> // Blue
</Link> statusBar = { background: 'linear-gradient(to right, rgb(255, 225, 77) 0, rgb(255, 225, 77) 4px, #fff 4px, #fff 100%) no-repeat' }
) break;
} case 'downloaded':
// Green
statusBar = { background: 'linear-gradient(to right, #39aa56 0, #39aa56 4px, #fff 4px, #fff 100%) no-repeat' }
break;
default:
statusBar = { background: 'linear-gradient(to right, grey 0, grey 4px, #fff 4px, #fff 100%) no-repeat' }
}
// This is our main loader that gets called when we receive api response through props from parent statusBar.listStyleType = 'none';
displayRequestedElementsInfo(requested_objects) {
let requestedElement = requested_objects.map((item, index) => {
if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) {
if (item.status === this.state.filterValue){
return this.generateListElements(index, item);
}
}
else if (this.state.filterQuery !== '') { return (
if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1) <Link style={sidebarCSS.link} to={{ pathname: '/admin/'+String(index)}} key={index}>
return this.generateListElements(index, item); <li style={statusBar}>
} <Interactive
as='div'
style={ (index != this.state.listItemSelected) ? sidebarCSS.card : sidebarCSS.cardSelected }
hover={sidebarCSS.cardSelected}
focus={sidebarCSS.cardSelected}
active={sidebarCSS.cardSelected}>
else <h2 style={sidebarCSS.titleCard}>
return this.generateListElements(index, item); <span>{ item.name }</span>
}) </h2>
this.setState({ <p style={sidebarCSS.pCard}>
requestItemsToBeDisplayed: requestedElement, <span>Requested:
}) <time>
} &nbsp;{ this.convertDateToDaysSince(item.requested_date) }
</time>
</span>
</p>
</Interactive>
</li>
</Link>
)
}
render() { // This is our main loader that gets called when we receive api response through props from parent
let bodyCSS = sidebarCSS.body; displayRequestedElementsInfo(requested_objects) {
if (typeof InstallTrigger !== 'undefined') let requestedElement = requested_objects.map((item, index) => {
bodyCSS.width = '-moz-min-content'; if (['requested', 'downloading', 'downloaded'].indexOf(this.state.filterValue) != -1) {
if (item.status === this.state.filterValue){
return this.generateListElements(index, item);
}
}
else if (this.state.filterQuery !== '') {
if (item.name.toLowerCase().indexOf(this.state.filterQuery.toLowerCase()) != -1)
return this.generateListElements(index, item);
}
else
return this.generateListElements(index, item);
})
return ( this.setState({
<div> requestItemsToBeDisplayed: this.generateBody(requestedElement)
<h1>Hello from the sidebar: </h1> })
{ this.generateFilterDropdown() } }
{ this.generateFilterSearchbar() }
<div key='requestedTable' style={bodyCSS}> render() {
{ this.state.requestItemsToBeDisplayed } // if (typeof InstallTrigger !== 'undefined')
</div> // bodyCSS.width = '-moz-min-content';
</div>
); return (
} <div>
<h1 style={sidebarCSS.header}>Requested items</h1>
{ this.generateFilterSearch() }
{ this.generateNav() }
<div key='requestedTable' style={sidebarCSS.body}>
{ this.state.requestItemsToBeDisplayed }
</div>
</div>
);
}
} }
export default SidebarComponent; export default SidebarComponent;

View File

@@ -1,13 +1,15 @@
export default { export default {
header: {
textAlign: 'center',
},
body: { body: {
backgroundColor: 'white', backgroundColor: 'white',
width: 'min-content',
}, },
parentElement: { parentElement: {
display: 'inline-block', display: 'inline-block',
width: '300px', width: '100%',
border: '1px solid black', border: '1px solid grey',
borderRadius: '2px', borderRadius: '2px',
padding: '4px', padding: '4px',
margin: '4px', margin: '4px',
@@ -16,7 +18,8 @@ export default {
}, },
parentElement_hover: { parentElement_hover: {
marginLeft: '10px', backgroundColor: '#f8f8f8',
pointer: 'hand',
}, },
parentElement_active: { parentElement_active: {
@@ -25,8 +28,8 @@ export default {
parentElement_selected: { parentElement_selected: {
display: 'inline-block', display: 'inline-block',
width: '300px', width: '100%',
border: '1px solid black', border: '1px solid grey',
borderRadius: '2px', borderRadius: '2px',
padding: '4px', padding: '4px',
margin: '4px 0px 4px 4px', margin: '4px 0px 4px 4px',
@@ -35,7 +38,7 @@ export default {
}, },
title: { title: {
maxWidth: '70%', maxWidth: '65%',
display: 'inline-flex', display: 'inline-flex',
}, },
@@ -47,4 +50,104 @@ export default {
rightContainer: { rightContainer: {
float: 'right', float: 'right',
}, },
searchSidebar: {
height: '4em',
},
searchInner: {
top: '0',
right: '0',
left: '0',
bottom: '0',
margin: 'auto',
width: '90%',
minWidth: '280px',
height: '30px',
border: '1px solid #d0d0d0',
borderRadius: '4px',
overflow: 'hidden'
},
searchTextField: {
display: 'inline-block',
width: '90%',
padding: '.3em',
verticalAlign: 'middle',
border: 'none',
background: '#fff',
fontSize: '14px',
marginTop: '-7px',
},
searchIcon: {
width: '15px',
height: '16px',
marginRight: '4px',
marginTop: '7px',
},
searchSVGIcon: {
fill: 'none',
stroke: '#9d9d9d',
strokeLinecap: 'round',
strokeLinejoin: 'round',
strokeMiterlimit: '10',
},
ulFilterSelectors: {
borderBottom: '2px solid #f1f1f1',
display: 'flex',
padding: '0',
margin: '0',
listStyle: 'none',
justifyContent: 'space-evenly',
},
aFilterSelectors: {
color: '#3eaaaf',
fontSize: '16px',
cursor: 'pointer',
},
spanFilterSelectors: {
content: '""',
bottom: '-2px',
display: 'block',
width: '100%',
height: '2px',
backgroundColor: '#3eaaaa',
},
ulCard: {
margin: '1em 0 0 0',
padding: '0',
listStyle: 'none',
borderBottom: '.46rem solid #f1f1f',
backgroundColor: '#f1f1f1',
overflow: 'scroll',
},
card: {
padding: '.1em .5em .8em 1.5em',
marginBottom: '.26rem',
height: 'auto',
cursor: 'pointer',
},
cardSelected: {
padding: '.1em .5em .8em 1.5em',
marginBottom: '.26rem',
height: 'auto',
cursor: 'pointer',
backgroundColor: '#f9f9f9',
},
titleCard: {
fontSize: '15px',
fontWeight: '400',
whiteSpace: 'no-wrap',
textDecoration: 'none',
},
pCard: {
margin: '0',
},
} }