Merge pull request #24 from status-im/feat/project-list-style-switcher

Add list view and favorites
This commit is contained in:
Iuri Matias 2019-04-28 19:07:31 -04:00 committed by GitHub
commit 1b207e4021
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 220 additions and 70 deletions

View File

@ -76,6 +76,7 @@
"case-sensitive-paths-webpack-plugin": "2.2.0", "case-sensitive-paths-webpack-plugin": "2.2.0",
"chai": "^4.1.0", "chai": "^4.1.0",
"chart.js": "^2.7.3", "chart.js": "^2.7.3",
"classnames": "^2.2.6",
"cryptocurrency-icons": "^0.9.3", "cryptocurrency-icons": "^0.9.3",
"css-loader": "1.0.0", "css-loader": "1.0.0",
"cytoscape": "^3.3.0", "cytoscape": "^3.3.0",
@ -190,8 +191,18 @@
"react-app" "react-app"
], ],
"plugins": [ "plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }], [
["@babel/plugin-proposal-class-properties", { "loose": true }], "@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
],
[ [
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime",
{ {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -21,6 +21,12 @@ import InputAdornment from '@material-ui/core/InputAdornment';
import SearchIcon from '@material-ui/icons/Search'; import SearchIcon from '@material-ui/icons/Search';
import ListIcon from '@material-ui/icons/Reorder'; import ListIcon from '@material-ui/icons/Reorder';
import DashboardIcon from '@material-ui/icons/Dashboard'; import DashboardIcon from '@material-ui/icons/Dashboard';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import classnames from 'classnames';
import defaultProjectImage from '../../images/default-project-img.png'; import defaultProjectImage from '../../images/default-project-img.png';
import newProjectImage from '../../images/new-project.png'; import newProjectImage from '../../images/new-project.png';
@ -30,50 +36,91 @@ const SORT_TYPES = {
name: 'name' name: 'name'
} }
const styles = theme => ({ const cardAbsolutesDistance = 26;
root: {
margin: '1.75rem 4.5rem', const styles = theme => {
...theme.typography.body1 return ({
}, root: {
filters: { margin: '1.75rem 4.5rem',
float: 'right' ...theme.typography.body1
}, },
search: { filters: {
height: 36 float: 'right'
}, },
title: { search: {
fontSize: '20px', height: 36
textAlign: 'center', },
fontWeight: 500 formatBtn: {
}, cursor: 'pointer',
media: { '&:hover, &.active': {
height: 235, color: 'black'
position: 'relative' }
}, },
avatar: { title: {
position: 'absolute', fontSize: '20px',
top: 26, textAlign: 'center',
left: 26 fontWeight: 500
}, },
'card-title': { media: {
fontSize: '20px' height: 235,
}, position: 'relative'
'card-content': { },
fontSize: '16px' avatarGrid: {
}, position: 'absolute',
'card-actions': { top: cardAbsolutesDistance,
float: 'right' left: cardAbsolutesDistance
}, },
progress: { 'card-title': {
height: 10 fontSize: '20px'
}, },
'new-project-img': { 'card-content': {
textAlign: 'center', fontSize: '16px'
margin: 0, },
paddingTop: 115, 'card-actions': {
paddingBottom: 43 float: 'right'
} },
}) progress: {
height: 10
},
'new-project-img': {
textAlign: 'center',
margin: 0,
paddingTop: 115,
paddingBottom: 43
},
nameCell: {
fontSize: 18
},
darkRow: {
backgroundColor: theme.palette.common.grey
},
addProjectRow: {
borderTop: '0.25px solid ' + theme.palette.text.grey,
cursor: 'pointer',
'& td': {
paddingTop: 35
}
},
favorite: {
display: 'inline-block',
backgroundImage: 'url("/images/favorite-sprite.png")',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'bottom',
width: 30,
height: 27,
cursor: 'pointer',
'&:hover, &.isFavorite': {
backgroundPosition: 'top'
}
},
cardFavorite: {
position: 'absolute',
top: cardAbsolutesDistance,
right: cardAbsolutesDistance,
zIndex: 9000
}
})
}
function sortByTitle(a, b) { function sortByTitle(a, b) {
if (!a.manifest || !b.manifest) { if (!a.manifest || !b.manifest) {
@ -90,9 +137,14 @@ function sortByDate(a, b) {
return 1; return 1;
} }
function ProjectCard({classes, project}) { function Favorite({classes, setFavorites, favorites, projectId, className}) {
return (<span className={classnames(className, classes.favorite, {isFavorite: favorites[projectId]})}
onClick={() => setFavorites({...favorites, [projectId]: !favorites[projectId]})}/>);
}
function ProjectCard({classes, project, favorites, setFavorites}) {
return (<Card className={classes.card}> return (<Card className={classes.card}>
<CardActionArea href={`/#/project/${project.projectId}`}> <CardActionArea href={`/#/project/${project.projectId}`} onClick={e => { if (e.target.className.indexOf(classes.favorite) > -1) { e.preventDefault() } }}>
<CardMedia <CardMedia
className={classes.media} className={classes.media}
image={defaultProjectImage} image={defaultProjectImage}
@ -111,7 +163,8 @@ function ProjectCard({classes, project}) {
<Typography component="p" className={classes['card-content']} color="textSecondary"> <Typography component="p" className={classes['card-content']} color="textSecondary">
Delegate: {project.manifest.creator} {/*TODO check if that really is the delegate*/} Delegate: {project.manifest.creator} {/*TODO check if that really is the delegate*/}
</Typography> </Typography>
{project.manifest.avatar && <img className={classes.avatar} alt="avatar" src={project.manifest.avatar} width={40} height={40}/>} {project.manifest.avatar && <img className={classes.avatarGrid} alt="avatar" src={project.manifest.avatar} width={40} height={40}/>}
<Favorite className={classes.cardFavorite} classes={classes} favorites={favorites} projectId={project.projectId} setFavorites={setFavorites}/>
</CardContent> </CardContent>
</CardActionArea> </CardActionArea>
<CardActions className={classes['card-actions']}> <CardActions className={classes['card-actions']}>
@ -122,8 +175,98 @@ function ProjectCard({classes, project}) {
</Card>) </Card>)
} }
function Projects({projectAddedEvents, classes}) { function GridView({classes, projects, favorites, setFavorites}) {
return (<Grid container spacing={40}>
{projects.map((project, index) => {
if (!project.manifest) {
return ''
}
return (<Grid key={'project-' + index} item xs={12} sm={6} md={4} lg={3} className={classes.card}>
<ProjectCard project={project} classes={classes} favorites={favorites} setFavorites={setFavorites}/>
</Grid>);
})}
<Grid item xs={12} sm={6} md={4} lg={3} className="project-list-item">
<Card className={classes.card}>
<CardActionArea href="/#/create-project/" style={{height: 460}}>
<p className={classes['new-project-img']}><img alt="new project" src={newProjectImage}/></p>
<Typography align="center" className={classes['card-content']}>Add your own project</Typography>
</CardActionArea>
</Card>
</Grid>
</Grid>)
}
const CustomTableCell = withStyles(theme => ({
head: {
color: theme.palette.text.grey,
fontSize: 15,
border: 0
},
body: {
fontSize: 16,
border: 0,
height: 80
}
}))(TableCell);
function ListView({classes, projects, history, favorites, setFavorites}) {
let rowCounter = -1;
return (<Table className={classes.table}>
<TableHead>
<TableRow>
<CustomTableCell>&nbsp;</CustomTableCell>
<CustomTableCell>Project name</CustomTableCell>
<CustomTableCell>Description</CustomTableCell>
<CustomTableCell>Funding details</CustomTableCell>
<CustomTableCell>Delegate</CustomTableCell>
<CustomTableCell>&nbsp;</CustomTableCell>
<CustomTableCell>&nbsp;</CustomTableCell>
</TableRow>
</TableHead>
<TableBody>
{projects.map((project, index) => {
rowCounter++;
if (!project.manifest) {
return ''
}
return (
<TableRow className={classnames(classes.row, {[classes.darkRow]: rowCounter%2})} key={'project-' + index}>
<CustomTableCell>
{project.manifest.avatar &&
<img className={classes.avatar} alt="avatar" src={project.manifest.avatar} width={40} height={40}/>}
</CustomTableCell>
<CustomTableCell className={classes.nameCell}>{project.manifest.title}</CustomTableCell>
<CustomTableCell>{project.manifest.description}</CustomTableCell>
<CustomTableCell>76% of 2.055 ETH<br/>3 funders</CustomTableCell>
<CustomTableCell>{project.manifest.creator}</CustomTableCell>
<CustomTableCell>
<Favorite classes={classes} favorites={favorites} projectId={project.projectId} setFavorites={setFavorites}/>
</CustomTableCell>
<CustomTableCell>
<Button size="small" color="primary" href={`/#/project/${project.projectId}`}>Read more</Button>
</CustomTableCell>
</TableRow>
)
})}
<TableRow className={classnames(classes.row, classes.addProjectRow)} hover onClick={() => history.push('/create-project/')}>
<CustomTableCell>
<img alt="add project" src={newProjectImage} width={40} height={40}/>
</CustomTableCell>
<CustomTableCell>Add your own project</CustomTableCell>
<CustomTableCell/>
<CustomTableCell/>
<CustomTableCell/>
<CustomTableCell/>
<CustomTableCell/>
</TableRow>
</TableBody>
</Table>)
}
function Projects({projectAddedEvents, classes, history}) {
const [favorites, setFavorites] = useState({});
const [sortType, _setSortType] = useState(SORT_TYPES.date); const [sortType, _setSortType] = useState(SORT_TYPES.date);
const [isGridView, setIsGridView] = useState(true);
const projects = projectAddedEvents.map(event => { const projects = projectAddedEvents.map(event => {
return Object.assign({projectId: event.returnValues.idProject}, useProjectData(event.returnValues.idProject, '', projectAddedEvents)); return Object.assign({projectId: event.returnValues.idProject}, useProjectData(event.returnValues.idProject, '', projectAddedEvents));
@ -134,7 +277,9 @@ function Projects({projectAddedEvents, classes}) {
return (<div className={classes.root}> return (<div className={classes.root}>
<Typography className={classes.title} component="h2" gutterBottom>All projects</Typography> <Typography className={classes.title} component="h2" gutterBottom>All projects</Typography>
{projects.length === 0 && <Loading/>} {projects.length === 0 && <Loading/>}
{projects.length > 0 && {projects.length > 0 &&
<Fragment> <Fragment>
<div className={classes.filters}> <div className={classes.filters}>
@ -148,27 +293,13 @@ function Projects({projectAddedEvents, classes}) {
}} }}
/> />
</FormControl> </FormControl>
<DashboardIcon color="disabled" fontSize="large"/>
<ListIcon color="disabled" fontSize="large"/> <DashboardIcon className={classnames(classes.formatBtn, {'active': isGridView})} color="disabled" fontSize="large" onClick={() => setIsGridView(true)}/>
<ListIcon className={classnames(classes.formatBtn, {'active': !isGridView})} color="disabled" fontSize="large" onClick={() => setIsGridView(false)}/>
</div> </div>
<Grid container spacing={40}>
{projects.map((project, index) => { {!isGridView && ListView({classes, projects, history, favorites, setFavorites})}
if (!project.manifest) { {isGridView && GridView({classes, projects, favorites, setFavorites})}
return ''
}
return (<Grid key={'project-' + index} item xs={12} sm={6} md={4} lg={3} className={classes.card}>
<ProjectCard project={project} classes={classes}/>
</Grid>);
})}
<Grid item xs={12} sm={6} md={4} lg={3} className="project-list-item">
<Card className={classes.card}>
<CardActionArea href="/#/create-project/" style={{height: 460}}>
<p className={classes['new-project-img']}><img alt="new project" src={newProjectImage}/></p>
<Typography align="center" className={classes['card-content']}>Add your own project</Typography>
</CardActionArea>
</Card>
</Grid>
</Grid>
</Fragment> </Fragment>
} }
</div>) </div>)

View File

@ -12,6 +12,14 @@ const theme = createMuiTheme({
useNextVariants: true, useNextVariants: true,
fontFamily: ['Inter', '-apple-system', 'BlinkMacSystemFont', "Segoe UI", 'Roboto', "Helvetica Neue", 'Arial', "Noto Sans", 'sans-serif', "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"].join(','), fontFamily: ['Inter', '-apple-system', 'BlinkMacSystemFont', "Segoe UI", 'Roboto', "Helvetica Neue", 'Arial', "Noto Sans", 'sans-serif', "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"].join(','),
}, },
palette: {
common: {
grey: '#F5F7F8'
},
text: {
grey: '#939BA1'
}
}
}); });
render( render(