materialuiupgraded
Version:
Material-UI's workspace package
286 lines (268 loc) • 8.83 kB
JavaScript
import React from 'react';
import LZString from 'lz-string';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import copy from 'clipboard-copy';
import { withStyles } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import Collapse from '@material-ui/core/Collapse';
import EditIcon from '@material-ui/icons/Edit';
import CodeIcon from '@material-ui/icons/Code';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import Tooltip from '@material-ui/core/Tooltip';
import Github from '@material-ui/docs/svgIcons/GitHub';
import MarkdownElement from '@material-ui/docs/MarkdownElement';
import { getDependencies } from 'docs/src/modules/utils/helpers';
function compress(object) {
return LZString.compressToBase64(JSON.stringify(object))
.replace(/\+/g, '-') // Convert '+' to '-'
.replace(/\//g, '_') // Convert '/' to '_'
.replace(/=+$/, ''); // Remove ending '='
}
function addHiddenInput(form, name, value) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
function getDemo(props) {
return {
title: 'Material demo',
description: props.githubLocation,
dependencies: getDependencies(props.raw),
files: {
'demo.js': props.raw,
'index.js': `
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './demo';
ReactDOM.render(<Demo />, document.querySelector('#root'));
`,
'index.html': `
<body>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<div id="root"></div>
</body>
`,
},
};
}
const styles = theme => ({
root: {
position: 'relative',
marginBottom: 40,
marginLeft: -theme.spacing.unit * 2,
marginRight: -theme.spacing.unit * 2,
[theme.breakpoints.up('sm')]: {
padding: `0 ${theme.spacing.unit}px`,
marginLeft: 0,
marginRight: 0,
},
},
demo: theme.mixins.gutters({
borderRadius: theme.shape.borderRadius,
backgroundColor:
theme.palette.type === 'light' ? theme.palette.grey[200] : theme.palette.grey[900],
display: 'flex',
justifyContent: 'center',
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
[theme.breakpoints.up('sm')]: {
paddingLeft: theme.spacing.unit * 3,
paddingRight: theme.spacing.unit * 3,
paddingTop: theme.spacing.unit * 6,
paddingBottom: theme.spacing.unit * 3,
},
}),
demoHiddenHeader: {
paddingTop: theme.spacing.unit * 2,
[theme.breakpoints.up('sm')]: {
paddingTop: theme.spacing.unit * 3,
},
},
header: {
display: 'none',
[theme.breakpoints.up('sm')]: {
display: 'flex',
flip: false,
position: 'absolute',
top: 0,
right: theme.spacing.unit,
},
},
code: {
display: 'none',
padding: 0,
margin: 0,
[theme.breakpoints.up('sm')]: {
display: 'block',
},
'& pre': {
overflow: 'auto',
margin: '0px !important',
borderRadius: '0px !important',
},
},
});
class Demo extends React.Component {
state = {
anchorEl: null,
codeOpen: false,
};
handleClickMore = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleCloseMore = () => {
this.setState({ anchorEl: null });
};
handleClickCodeOpen = () => {
this.setState(state => ({
codeOpen: !state.codeOpen,
}));
};
handleClickCodeSandbox = () => {
const demo = getDemo(this.props);
const parameters = compress({
files: {
'package.json': {
content: {
title: demo.title,
description: demo.description,
dependencies: demo.dependencies,
},
},
'demo.js': {
content: demo.files['demo.js'],
},
'index.js': {
content: demo.files['index.js'],
},
'index.html': {
content: demo.files['index.html'],
},
},
});
const form = document.createElement('form');
form.method = 'POST';
form.target = '_blank';
form.action = 'https://codeSandbox.io/api/v1/sandboxes/define';
addHiddenInput(form, 'parameters', parameters);
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
};
handleClickCopy = async () => {
try {
await copy(this.props.raw);
} finally {
this.handleCloseMore();
}
};
handleClickStackBlitz = () => {
const demo = getDemo(this.props);
const form = document.createElement('form');
form.method = 'POST';
form.target = '_blank';
form.action = 'https://stackblitz.com/run';
addHiddenInput(form, 'project[template]', 'javascript');
addHiddenInput(form, 'project[title]', demo.title);
addHiddenInput(form, 'project[description]', demo.description);
addHiddenInput(form, 'project[dependencies]', JSON.stringify(demo.dependencies));
Object.entries(demo.files).forEach(([key, value]) => {
addHiddenInput(form, `project[files][${key}]`, value);
});
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
this.handleCloseMore();
};
render() {
const { classes, demoOptions, githubLocation, index, js: DemoComponent, raw } = this.props;
const { anchorEl, codeOpen } = this.state;
return (
<div className={classes.root}>
{demoOptions.hideHeader ? null : (
<div>
<div className={classes.header}>
<Tooltip title="See the source on GitHub" placement="top">
<IconButton href={githubLocation} target="_blank" aria-label="GitHub">
<Github />
</IconButton>
</Tooltip>
{demoOptions.hideEditButton ? null : (
<Tooltip title="Edit in CodeSandbox" placement="top">
<IconButton onClick={this.handleClickCodeSandbox} aria-label="CodeSandbox">
<EditIcon />
</IconButton>
</Tooltip>
)}
<Tooltip title={codeOpen ? 'Hide the source' : 'Show the source'} placement="top">
<IconButton
onClick={this.handleClickCodeOpen}
aria-label={`Source of demo n°${index}`}
>
<CodeIcon />
</IconButton>
</Tooltip>
<IconButton
onClick={this.handleClickMore}
aria-owns={anchorEl ? 'demo-menu-more' : null}
aria-haspopup="true"
aria-label="See more"
>
<MoreVertIcon />
</IconButton>
<Menu
id="demo-menu-more"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={this.handleCloseMore}
getContentAnchorEl={null}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<MenuItem onClick={this.handleClickCopy}>Copy the source</MenuItem>
{demoOptions.hideEditButton ? null : (
<MenuItem onClick={this.handleClickStackBlitz}>Edit in StackBlitz</MenuItem>
)}
</Menu>
</div>
<Collapse in={codeOpen} unmountOnExit>
<MarkdownElement
dir="ltr"
className={classes.code}
text={`\`\`\`jsx\n${raw}\n\`\`\``}
/>
</Collapse>
</div>
)}
<div
className={classNames(classes.demo, {
[classes.demoHiddenHeader]: demoOptions.hideHeader,
})}
>
<DemoComponent />
</div>
</div>
);
}
}
Demo.propTypes = {
classes: PropTypes.object.isRequired,
demoOptions: PropTypes.object.isRequired,
githubLocation: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
js: PropTypes.func.isRequired,
raw: PropTypes.string.isRequired,
};
export default withStyles(styles)(Demo);