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:
@@ -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>
|
||||||
}
|
{ 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;
|
||||||
@@ -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',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user