Skip to content

Commit

Permalink
[0.5.0] PDF Exports (#9)
Browse files Browse the repository at this point in the history
* Changed order of Graphs

* Moved Graphs to their own container

* Allow you to add an optional profile name

* Started work on PDF generation

* draft PDF

* Move from react-pdf -> jspdf

* Cleanup

* More cleanup

* Table bg fix

* Fix graph legends breaking in dark mode pdf export

* Change pdf table head color

* Clean up App Bar

* Cleanup

* Disable pdf button if no units

* Add Reddit button to footer

* Add beta tag for PDF

* Better contrast

* Bump package.json version
  • Loading branch information
damonhook authored Dec 19, 2019
1 parent e038478 commit 6cf9323
Show file tree
Hide file tree
Showing 48 changed files with 1,277 additions and 292 deletions.
5 changes: 4 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "statshammer",
"version": "0.4.0",
"version": "0.5.0",
"private": true,
"proxy": "http://localhost:5000/",
"engines": {
Expand Down Expand Up @@ -31,6 +31,9 @@
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.16.0",
"format-unicorn": "^1.1.1",
"html2canvas": "^1.0.0-rc.5",
"jspdf": "^1.5.3",
"jspdf-autotable": "^3.2.11",
"lodash": "^4.17.15",
"lorem-ipsum": "^2.0.3",
"nanoid": "^2.1.7",
Expand Down
96 changes: 16 additions & 80 deletions client/src/components/AppBar/AppMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,32 @@ import React, { useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import {
Menu, MenuItem, IconButton, Typography, Button, useMediaQuery,
Menu, IconButton, Button, useMediaQuery,
} from '@material-ui/core';
import {
MoreVert, BarChart, ImportExport, BrightnessMedium, Delete,
} from '@material-ui/icons';
import { clearAllUnits, addUnit } from 'actions/units.action';
import { toggleDarkMode, toggleDesktopGraphList } from 'actions/config.action';
import { MoreVert } from '@material-ui/icons';
import { clearAllUnits } from 'actions/units.action';
import { connect } from 'react-redux';
import ConfirmationDialog from 'components/ConfirmationDialog';
import { useHistory, Route } from 'react-router-dom';
import { Route } from 'react-router-dom';
import { addNotification } from 'actions/notifications.action';
import Uploader from 'components/Uploader';
import { addUnitEnabled } from 'utils/unitHelpers';

import PdfDownloadItem from './PdfDownloadItem';
import ToggleDarkModeItem from './ToggleDarkModeItem';
import ClearUnitsItem from './ClearUnitsItem';
import ToggleGraphListItem from './ToggleGraphListItem';
import ImportUnitItem from './ImportUnitItem';

const useStyles = makeStyles((theme) => ({
menu: {},
icon: {
color: theme.palette.primary.contrastText,
},
caption: {
paddingBottom: theme.spacing(1),
},
menuItemIcon: {
marginRight: theme.spacing(1),
},
}));

/**
* A menu list containing various actions that can be performed
*/
const AppMenu = ({
clearAllUnits, addNotification, toggleDarkMode, addUnit, toggleDesktopGraphList,
}) => {
const AppMenu = ({ clearAllUnits, addNotification }) => {
const classes = useStyles();
const history = useHistory();
const theme = useTheme();
const mobile = useMediaQuery(theme.breakpoints.down('sm'));

Expand Down Expand Up @@ -66,16 +56,6 @@ const AppMenu = ({
handleMenuClose();
}, [handleMenuClose]);

/**
* Change the URL bar to a new location. This is used to display dialog boxes that
* retains proper navigation
* @param {string} newloc the new URL to set
*/
const setLocation = useCallback((newloc) => {
handleMenuClose();
history.push(newloc);
}, [handleMenuClose, history]);

/**
* Handle the case when the confirm option is selected from the clear all units dialog
*/
Expand All @@ -84,20 +64,6 @@ const AppMenu = ({
addNotification({ message: 'All units cleared', variant: 'info' });
}, [addNotification, clearAllUnits, menuItemClick]);

/** Is the upload menu item disabled or not */
const isUploadDisabled = !addUnitEnabled();

/** The function to call when a file upload happens.
* In this case that would be importing the uploaded unit data
* @param {object} data the JSON from the uploaded unit
* */
const onUnitUpload = useCallback((data) => {
if (data && data.name && data.weapon_profiles) {
addNotification({ message: 'Successfully imported unit', variant: 'success' });
addUnit(data.name, data.weapon_profiles);
}
}, [addNotification, addUnit]);

return (
<div className={classes.menu}>
{mobile
Expand All @@ -123,33 +89,11 @@ const AppMenu = ({
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={() => setLocation(confirmPath)}>
<Delete className={classes.menuItemIcon} />
Clear Units
</MenuItem>
<MenuItem onClick={() => menuItemClick(toggleDarkMode)}>
<BrightnessMedium className={classes.menuItemIcon} />
<span>Toggle Dark Mode&nbsp;</span>
<Typography variant="caption" color="secondary" className={classes.caption}>
Beta
</Typography>
</MenuItem>
{!mobile && (
<MenuItem onClick={() => menuItemClick(toggleDesktopGraphList)}>
<BarChart className={classes.menuItemIcon} />
Toggle Graph List/Tabs
</MenuItem>
)}
<Uploader
onUpload={(data) => menuItemClick(() => onUnitUpload(data))}
disabled={isUploadDisabled}
component={(
<MenuItem disabled={isUploadDisabled}>
<ImportExport className={classes.menuItemIcon} />
Import Unit
</MenuItem>
)}
/>
<ClearUnitsItem onClick={handleMenuClose} />
<ToggleDarkModeItem onClick={handleMenuClose} />
{!mobile && <ToggleGraphListItem onClick={handleMenuClose} />}
<ImportUnitItem onClick={handleMenuClose} />
<PdfDownloadItem onClick={handleMenuClose} />
</Menu>
<Route path={confirmPath}>
<ConfirmationDialog
Expand All @@ -168,14 +112,6 @@ AppMenu.propTypes = {
clearAllUnits: PropTypes.func.isRequired,
/** A function to call to add a notification to the stack */
addNotification: PropTypes.func.isRequired,
/** A function to call to toggle dark/light themes */
toggleDarkMode: PropTypes.func.isRequired,
/** A function to call to add a new unit */
addUnit: PropTypes.func.isRequired,
/** A function to call to toggle the desktop graph list */
toggleDesktopGraphList: PropTypes.func.isRequired,
};

export default connect(null, {
clearAllUnits, addNotification, toggleDarkMode, addUnit, toggleDesktopGraphList,
})(AppMenu);
export default connect(null, { clearAllUnits, addNotification })(AppMenu);
43 changes: 43 additions & 0 deletions client/src/components/AppBar/ClearUnitsItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import { MenuItem } from '@material-ui/core';
import { Delete } from '@material-ui/icons';
import { Link } from 'react-router-dom';

const useStyles = makeStyles((theme) => ({
menuItemIcon: {
marginRight: theme.spacing(1),
},
item: {
color: theme.palette.getContrastText(theme.palette.background.paper),
},
link: {
color: theme.palette.getContrastText(theme.palette.background.paper),
textDecoration: 'none',
},
}));

const ClearUnitsItem = ({ onClick }) => {
const classes = useStyles();

const handleClick = () => {
onClick();
};

return (
<Link onClick={handleClick} to="/units/confirm" className={classes.link}>
<MenuItem className={classes.item}>
<Delete className={classes.menuItemIcon} />
Clear All Units
</MenuItem>
</Link>
);
};

ClearUnitsItem.propTypes = {
/** A callback function to call when the menu item is clicked */
onClick: PropTypes.func.isRequired,
};

export default ClearUnitsItem;
76 changes: 76 additions & 0 deletions client/src/components/AppBar/ImportUnitItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import { MenuItem } from '@material-ui/core';
import { ImportExport } from '@material-ui/icons';
import { addUnit } from 'actions/units.action';
import { connect } from 'react-redux';
import { addNotification } from 'actions/notifications.action';
import Uploader from 'components/Uploader';
import { addUnitEnabled } from 'utils/unitHelpers';

const useStyles = makeStyles((theme) => ({
menu: {},
icon: {
color: theme.palette.primary.contrastText,
},
caption: {
paddingBottom: theme.spacing(1),
},
menuItemIcon: {
marginRight: theme.spacing(1),
},
}));


const ImportUnitItem = ({
// eslint-disable-next-line no-unused-vars
numUnits, onClick, addNotification, addUnit,
}) => {
const classes = useStyles();

/** Is the upload menu item disabled or not */
const isUploadDisabled = !addUnitEnabled();

/** The function to call when a file upload happens.
* In this case that would be importing the uploaded unit data
* @param {object} data the JSON from the uploaded unit
* */
const onUnitUpload = useCallback((data) => {
if (data && data.name && data.weapon_profiles) {
onClick();
addNotification({ message: 'Successfully imported unit', variant: 'success' });
addUnit(data.name, data.weapon_profiles);
}
}, [addNotification, addUnit, onClick]);

return (
<Uploader
onUpload={onUnitUpload}
disabled={isUploadDisabled}
component={(
<MenuItem disabled={isUploadDisabled}>
<ImportExport className={classes.menuItemIcon} />
Import Unit
</MenuItem>
)}
/>
);
};

ImportUnitItem.propTypes = {
/** The current number of units. Used to ensure that the item is disabled when limit is reached */
numUnits: PropTypes.number.isRequired,
/** A callback function to call when the menu item is clicked */
onClick: PropTypes.func.isRequired,
/** A function to call to add a notification to the stack */
addNotification: PropTypes.func.isRequired,
/** A function to call to add a new unit */
addUnit: PropTypes.func.isRequired,
};

const mapStateToProps = (state) => ({
numUnits: state.units.length,
});

export default connect(mapStateToProps, { addNotification, addUnit })(ImportUnitItem);
55 changes: 55 additions & 0 deletions client/src/components/AppBar/PdfDownloadItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import { MenuItem } from '@material-ui/core';
import { GetApp } from '@material-ui/icons';
import { Link } from 'react-router-dom';
import BetaTag from 'components/BetaTag';

const useStyles = makeStyles((theme) => ({
menuItemIcon: {
marginRight: theme.spacing(1),
},
item: {
color: theme.palette.getContrastText(theme.palette.background.paper),
},
link: {
color: theme.palette.getContrastText(theme.palette.background.paper),
textDecoration: 'none',
},
}));

const PdfDownloadItem = ({ onClick, numUnits }) => {
const classes = useStyles();

const handleClick = () => {
onClick();
};

const disabled = numUnits <= 0;

return (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<Link onClick={handleClick} to={disabled ? '' : '/pdf'} className={classes.link}>
<MenuItem className={classes.item} disabled={disabled}>
<GetApp className={classes.menuItemIcon} />
Download PDF
<BetaTag />
</MenuItem>
</Link>
);
};

PdfDownloadItem.propTypes = {
/** A callback function to call when the menu item is clicked */
onClick: PropTypes.func.isRequired,
/** The current number of units. Used to disable the button */
numUnits: PropTypes.number.isRequired,
};

const mapStateToProps = (state) => ({
numUnits: state.units.length,
});

export default connect(mapStateToProps)(PdfDownloadItem);
38 changes: 38 additions & 0 deletions client/src/components/AppBar/ToggleDarkModeItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import PropTypes from 'prop-types';
import { makeStyles } from '@material-ui/core/styles';
import { MenuItem, Typography } from '@material-ui/core';
import { BrightnessMedium } from '@material-ui/icons';
import { toggleDarkMode } from 'actions/config.action';
import { connect } from 'react-redux';

const useStyles = makeStyles((theme) => ({
menuItemIcon: {
marginRight: theme.spacing(1),
},
}));

const ToggleDarkModeItem = ({ onClick, toggleDarkMode }) => {
const classes = useStyles();

const handleClick = () => {
onClick();
toggleDarkMode();
};

return (
<MenuItem onClick={handleClick}>
<BrightnessMedium className={classes.menuItemIcon} />
<span>Toggle Dark Mode&nbsp;</span>
</MenuItem>
);
};

ToggleDarkModeItem.propTypes = {
/** A callback function to call when the menu item is clicked */
onClick: PropTypes.func.isRequired,
/** A function to call to toggle dark/light themes */
toggleDarkMode: PropTypes.func.isRequired,
};

export default connect(null, { toggleDarkMode })(ToggleDarkModeItem);
Loading

0 comments on commit 6cf9323

Please sign in to comment.