Skip to content

Commit

Permalink
Presentation Layer for NPM Register (#92)
Browse files Browse the repository at this point in the history
* Adding webpack and React Hello World

* adding react files, webpack, and package.json, ignoring compiled files

* Adding updated gitignore and package.json

* Adding material UI assets and basic layout

* Breaking out webpack config adding typefaces and loaders for fonts and css

* Breaking header out into a separate component

* Modifying webpack config to resolve jsx

* Fixing sigterm exit on ctrl-c crashing build by adding --success flag

* Adding standard linting to build

* Adjusting dev server config

* Outputting full url to console for easier loading.

* Adding support for windows in the npm scripts

* Adding cross-env to handle environment variables on windows and unix platforms

* Adding start of API

* Adding method to get all packages and their info in a request

* Adding current version and author

* Removing erroneous files for now

* Adding Axios http library

* Restructuring as ES6 class

* Tables added to render api, updated api to include tarballs

* Fixing bug where if a package did not have a name it would break rendering

* Adding production minification of UI assets

* Adding favicon and logos to the header

* Styling loader and hiding table while request is loading

* Renaming api to client

* Adding S3 client api and drying up json response in base storage class

* Readme Feature

Adding readme to the API response

* Adding modal component

* Prevent pushing of empty details

* null didn't work...

* adding readme modal, still need markdown rendering.

* Adding markdown rendering and tarball downloading

* Adding a darker header to the readme modal

* moving dev dependencies to fix heroku

* adding babel to dependencies for heroku

* Adding responsive styles for the table

* Loading .env files in development

* Updating packages for dependencies

* Adding default readme content and restructuring variable declarations.

* fixing indentations per standard style

* Fixing indentations to match standard style

* Refactoring to fix code climate issues. Render function was too big...

* Invoking react template partials

* Fixing API changes for material ui components

* Fixing refresh after refactor

* Updating webpack and adding hot module reloading.

* Fixing mode typo

* Upgrading to latest version of material ui
  • Loading branch information
dgautsch committed Aug 31, 2018
1 parent 362309d commit bf5ca63
Show file tree
Hide file tree
Showing 23 changed files with 15,647 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -4,3 +4,5 @@ fs
node_modules
tmp
npm-debug.log
public/app.js
.env
172 changes: 172 additions & 0 deletions client/App.jsx
@@ -0,0 +1,172 @@
import http from 'axios'
import Button from '@material-ui/core/Button'
import CircularProgress from '@material-ui/core/CircularProgress'
import Grid from '@material-ui/core/Grid'
import Icon from '@material-ui/core/Icon'
import IconButton from '@material-ui/core/IconButton'
import Paper from '@material-ui/core/Paper'
import PropTypes from 'prop-types'
import React from 'react'
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 { withStyles } from '@material-ui/core/styles'

import Header from './components/Header'
import Modal from './components/Modal'

const styles = theme => ({
root: {
marginTop: 30,
'font-family': ['Roboto', 'Arial', 'sans-serif']
},
button: {
margin: theme.spacing.unit
},
anchor: {
'text-decoration': 'none'
},
paper: {
padding: 16,
margin: 16,
textAlign: 'center',
color: theme.palette.text.secondary,
width: '100%',
marginTop: theme.spacing.unit * 3,
overflowX: 'auto'
},
table: {
overflow: 'auto',
'word=break': 'keep-all'
},
loader: {
color: '#282828'
}
})

class App extends React.Component {
constructor (props) {
super(props)
this.classes = props.classes
this.state = {
packages: [],
loading: false,
readmeOpen: false,
readmeContent: null
}
this.toggleReadMeModal = this.toggleReadMeModal.bind(this)
this.refreshPackages = this.refreshPackages.bind(this)
}

getPackages () {
this.setState({
loading: true
})
return http.get('/-/api/v1/packages')
}

toggleReadMeModal (readmeContent) {
return () => {
this.setState({
readmeOpen: !this.state.readmeOpen,
readmeContent
})
}
}

refreshPackages () {
this.getPackages().then((response) => {
if (response && response.data) {
this.setState({
packages: response.data,
loading: false
})
}
})
}

componentDidMount () {
this.refreshPackages()
}

tableHeader () {
return (
<TableHead>
<TableRow>
<TableCell>Package Name</TableCell>
<TableCell>Author(s)</TableCell>
<TableCell>Description</TableCell>
<TableCell>Latest Version</TableCell>
<TableCell>Readme</TableCell>
<TableCell>Tarball</TableCell>
</TableRow>
</TableHead>
)
};

tableBody () {
return (
<TableBody>
{this.state.packages.map((item, idx) => (
<TableRow key={idx} hover>
<TableCell>{item.name}</TableCell>
<TableCell>{item.author.name}</TableCell>
<TableCell>{item.description}</TableCell>
<TableCell>{item.currentVersion}</TableCell>
<TableCell>
<IconButton color='default' aria-label='Open read me' onClick={this.toggleReadMeModal(item.readme)}>
<Icon>class</Icon>
</IconButton>
</TableCell>
<TableCell>
<a href={item.tarball.tarball} className={this.classes.anchor}>
<IconButton color='default' aria-label='Download Tarball'><Icon>get_app</Icon></IconButton>
</a>
</TableCell>
</TableRow>
))}
</TableBody>
)
}

refreshButton () {
return (
<Grid item xs={12} sm={12}>
<Button variant='raised' className={this.classes.button} onClick={this.refreshPackages}>
Refresh Packages
</Button>
</Grid>
)
}

render () {
return (
<div className={this.classes.root}>
<Header />
<Grid container spacing={16}>
<Paper className={this.classes.paper}>
<Grid item xs={12} sm={12}>
{this.state.loading && <CircularProgress size={120} className={this.classes.loader} />}
{!this.state.loading &&
<Table className={this.classes.table}>
{this.tableHeader()}
{this.tableBody()}
</Table>
}
</Grid>
{this.refreshButton()}
</Paper>
</Grid>
<Modal show={this.state.readmeOpen} onClose={this.toggleReadMeModal()} readme={this.state.readmeContent} />
</div>
)
}
}

App.propTypes = {
classes: PropTypes.object.isRequired
}

export default withStyles(styles)(App)
42 changes: 42 additions & 0 deletions client/components/Header.jsx
@@ -0,0 +1,42 @@
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import Typography from '@material-ui/core/Typography'

const styles = {
root: {
marginBottom: 30,
width: '100%'
},
appbar: {
background: '#cc0000',
color: '#FFF'
},
type: {
marginLeft: 10
}
}

function SimpleAppBar (props) {
const classes = props.classes
return (
<div className={classes.root}>
<AppBar position='static' color='inherit' className={classes.appbar}>
<Toolbar>
<img src='/images/logo-small.png' alt='NPM Register' width='30px' height='30px' />
<Typography type='headline' color='inherit' className={classes.type}>
NPM Register
</Typography>
</Toolbar>
</AppBar>
</div>
)
}

SimpleAppBar.propTypes = {
classes: PropTypes.object.isRequired
}

export default withStyles(styles)(SimpleAppBar)
77 changes: 77 additions & 0 deletions client/components/Modal.jsx
@@ -0,0 +1,77 @@
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Dialog from '@material-ui/core/Dialog'
import Slide from '@material-ui/core/Slide'
import Button from '@material-ui/core/Button'
import AppBar from '@material-ui/core/AppBar'
import Toolbar from '@material-ui/core/Toolbar'
import IconButton from '@material-ui/core/IconButton'
import Typography from '@material-ui/core/Typography'
import CloseIcon from '@material-ui/icons/Close'
import md from 'marked'

const styles = {
appBar: {
position: 'relative',
background: '#282828',
color: '#FFF'
},
flex: {
flex: 1
},
readme: {
overflow: 'auto',
padding: 16
}
}

class Modal extends React.Component {
constructor (props) {
super(props)
this.classes = props.classes
this.readme = props.readme
this.show = props.show
this.onClose = props.onClose
}

Transition (props) {
return <Slide direction='up' {...props} />
}

render () {
let readme = md.parse(this.props.readme || '', {sanitize: true})
return (
<Dialog
fullScreen
open={this.props.show}
onClose={this.props.onClose}
TransitionComponent={this.Transition}
>
<AppBar color='inherit' className={this.classes.appBar}>
<Toolbar>
<IconButton color='inherit' onClick={this.props.onClose} aria-label='Close'>
<CloseIcon />
</IconButton>
<Typography type='title' color='inherit' className={this.classes.flex}>
README
</Typography>
<Button color='inherit' onClick={this.props.onClose}>
Close
</Button>
</Toolbar>
</AppBar>
<Typography type='body1' component='div' dangerouslySetInnerHTML={{__html: readme}} className={this.classes.readme} />
</Dialog>
)
}
}

Modal.propTypes = {
classes: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
readme: PropTypes.string,
show: PropTypes.bool
}

export default withStyles(styles)(Modal)
16 changes: 16 additions & 0 deletions client/index.jsx
@@ -0,0 +1,16 @@
import React from 'react'
import { render } from 'react-dom'
import App from './App'

const renderApp = () => {
render(
<App />,
document.getElementById('app')
)
}

renderApp()

if (module.hot) {
module.hot.accept('./App', renderApp)
}
62 changes: 62 additions & 0 deletions config/webpack.config.js
@@ -0,0 +1,62 @@
const path = require('path')
const merge = require('webpack-merge')
const devConfig = require('./webpack.dev')
const prodConfig = require('./webpack.prod')
const PATHS = {
app: path.join(__dirname, '../client'),
public: path.join(__dirname, '../public')
}

const baseConfig = {
entry: {
app: PATHS.app + '/index.jsx'
},
output: {
path: PATHS.public,
filename: 'app.js'
},
resolve: {
extensions: ['.webpack.js', '.web.js', '.js', '.json', '.jsx']
},
module: {
rules: [{
test: /\.jsx$/,
exclude: /(node_modules|lib)/,
use: {
loader: 'babel-loader',
options: {
presets: ['react']
}
}
}, {
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192
}
}]
}, {
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'url-loader',
options: {
limit: 50000,
mimetype: 'application/font-woff',
name: './fonts/[name].[ext]'
}
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
}]
}
}

module.exports = () => {
if (process.env.NODE_ENV === 'production') {
console.log('Loading production config...')
return merge(baseConfig, prodConfig)
}

console.log('Loading development config...')
return merge(baseConfig, devConfig)
}

0 comments on commit bf5ca63

Please sign in to comment.