upload, download, compile and reset blocks
This commit is contained in:
		
							parent
							
								
									8060927cc6
								
							
						
					
					
						commit
						4550977471
					
				| @ -13,6 +13,7 @@ | |||||||
|     "@testing-library/react": "^9.5.0", |     "@testing-library/react": "^9.5.0", | ||||||
|     "@testing-library/user-event": "^7.2.1", |     "@testing-library/user-event": "^7.2.1", | ||||||
|     "blockly": "^3.20200625.2", |     "blockly": "^3.20200625.2", | ||||||
|  |     "file-saver": "^2.0.2", | ||||||
|     "prismjs": "^1.20.0", |     "prismjs": "^1.20.0", | ||||||
|     "react": "^16.13.1", |     "react": "^16.13.1", | ||||||
|     "react-dom": "^16.13.1", |     "react-dom": "^16.13.1", | ||||||
|  | |||||||
| @ -1,46 +0,0 @@ | |||||||
| import React, {Component} from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| import { clearStats, onChangeCode } from '../actions/workspaceActions'; |  | ||||||
| import { initialXml } from './Blockly/initialXml.js'; |  | ||||||
| 
 |  | ||||||
| import * as Blockly from 'blockly/core'; |  | ||||||
| 
 |  | ||||||
| import ListItem from '@material-ui/core/ListItem'; |  | ||||||
| import ListItemIcon from '@material-ui/core/ListItemIcon'; |  | ||||||
| import ListItemText from '@material-ui/core/ListItemText'; |  | ||||||
| 
 |  | ||||||
| import { faTrashRestore } from "@fortawesome/free-solid-svg-icons"; |  | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; |  | ||||||
| 
 |  | ||||||
| class ClearWorkspace extends Component { |  | ||||||
| 
 |  | ||||||
|   clearWorkspace = () => { |  | ||||||
|     const workspace = Blockly.getMainWorkspace(); |  | ||||||
|     Blockly.Events.disable(); // https://groups.google.com/forum/#!topic/blockly/m7e3g0TC75Y
 |  | ||||||
|     // if events are disabled, then the workspace will be cleared AND the blocks are not in the trashcan
 |  | ||||||
|     const xmlDom = Blockly.Xml.textToDom(initialXml) |  | ||||||
|     Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace); |  | ||||||
|     Blockly.Events.enable(); |  | ||||||
|     workspace.options.maxBlocks = Infinity; |  | ||||||
|     this.props.onChangeCode(); |  | ||||||
|     this.props.clearStats(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render() { |  | ||||||
|     return ( |  | ||||||
|       <ListItem button onClick={() => {this.clearWorkspace(); this.props.onClick();}}> |  | ||||||
|         <ListItemIcon><FontAwesomeIcon icon={faTrashRestore} /></ListItemIcon> |  | ||||||
|         <ListItemText primary='Zurücksetzen' /> |  | ||||||
|       </ListItem> |  | ||||||
|     ); |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| ClearWorkspace.propTypes = { |  | ||||||
|   clearStats: PropTypes.func.isRequired, |  | ||||||
|   onChangeCode: PropTypes.func.isRequired |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| export default connect(null, { clearStats, onChangeCode })(ClearWorkspace); |  | ||||||
| @ -10,11 +10,26 @@ import DialogTitle from '@material-ui/core/DialogTitle'; | |||||||
| import DialogContent from '@material-ui/core/DialogContent'; | import DialogContent from '@material-ui/core/DialogContent'; | ||||||
| import DialogActions from '@material-ui/core/DialogActions'; | import DialogActions from '@material-ui/core/DialogActions'; | ||||||
| import Dialog from '@material-ui/core/Dialog'; | import Dialog from '@material-ui/core/Dialog'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | 
 | ||||||
|  | import { faPlay } from "@fortawesome/free-solid-svg-icons"; | ||||||
|  | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| 
 | 
 | ||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
|   backdrop: { |   backdrop: { | ||||||
|     zIndex: theme.zIndex.drawer + 1, |     zIndex: theme.zIndex.drawer + 1, | ||||||
|     color: '#fff', |     color: '#fff', | ||||||
|  |   }, | ||||||
|  |   button: { | ||||||
|  |     backgroundColor: theme.palette.primary.main, | ||||||
|  |     color: theme.palette.primary.contrastText, | ||||||
|  |     width: '40px', | ||||||
|  |     height: '40px', | ||||||
|  |     '&:hover': { | ||||||
|  |       backgroundColor: theme.palette.primary.main, | ||||||
|  |       color: theme.palette.primary.contrastText, | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -60,10 +75,21 @@ class Compile extends Component { | |||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div style={{display: 'inline'}}> |       <div style={{}}> | ||||||
|  |         {this.props.iconButton ? | ||||||
|  |           <Tooltip title='Blöcke kompilieren' arrow style={{marginRight: '5px'}}> | ||||||
|  |             <IconButton | ||||||
|  |               className={this.props.classes.button} | ||||||
|  |               onClick={() => this.compile()} | ||||||
|  |             > | ||||||
|  |               <FontAwesomeIcon icon={faPlay} size="xs"/> | ||||||
|  |             </IconButton> | ||||||
|  |           </Tooltip> | ||||||
|  |          : | ||||||
|           <Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}> |           <Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}> | ||||||
|             Kompilieren |             Kompilieren | ||||||
|           </Button> |           </Button> | ||||||
|  |         } | ||||||
|         <Backdrop className={this.props.classes.backdrop} open={this.state.progress}> |         <Backdrop className={this.props.classes.backdrop} open={this.state.progress}> | ||||||
|           <CircularProgress color="inherit" /> |           <CircularProgress color="inherit" /> | ||||||
|         </Backdrop> |         </Backdrop> | ||||||
|  | |||||||
| @ -72,7 +72,8 @@ class Home extends Component { | |||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <WorkspaceStats /> |         <div style={{float: 'left'}}><WorkspaceStats /></div> | ||||||
|  |         <div style={{float: 'right'}}><WorkspaceFunc /></div> | ||||||
|         <Grid container spacing={2}> |         <Grid container spacing={2}> | ||||||
|           <Grid item xs={12} md={this.state.codeOn ? 6 : 12} style={{ position: 'relative' }}> |           <Grid item xs={12} md={this.state.codeOn ? 6 : 12} style={{ position: 'relative' }}> | ||||||
|             <Tooltip title={this.state.codeOn ? 'Code ausblenden' : 'Code anzeigen'} > |             <Tooltip title={this.state.codeOn ? 'Code ausblenden' : 'Code anzeigen'} > | ||||||
| @ -93,7 +94,6 @@ class Home extends Component { | |||||||
|             </Grid> |             </Grid> | ||||||
|             : null} |             : null} | ||||||
|         </Grid> |         </Grid> | ||||||
|         <WorkspaceFunc /> |  | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| import ClearWorkspace from './ClearWorkspace'; |  | ||||||
| import senseboxLogo from './sensebox_logo.svg'; | import senseboxLogo from './sensebox_logo.svg'; | ||||||
| 
 | 
 | ||||||
| import { withRouter } from 'react-router-dom'; | import { withRouter } from 'react-router-dom'; | ||||||
| @ -105,7 +104,6 @@ class Navbar extends Component { | |||||||
|                 </ListItem> |                 </ListItem> | ||||||
|               </Link> |               </Link> | ||||||
|             ))} |             ))} | ||||||
|             <ClearWorkspace onClick={this.toggleDrawer}/> |  | ||||||
|           </List> |           </List> | ||||||
|           <Divider classes={{root: this.props.classes.appBarColor}} style={{marginTop: 'auto'}}/> |           <Divider classes={{root: this.props.classes.appBarColor}} style={{marginTop: 'auto'}}/> | ||||||
|           <List> |           <List> | ||||||
|  | |||||||
| @ -1,39 +1,143 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
|  | import { clearStats, onChangeCode } from '../actions/workspaceActions'; | ||||||
|  | 
 | ||||||
|  | import * as Blockly from 'blockly/core'; | ||||||
|  | 
 | ||||||
|  | import { saveAs } from 'file-saver'; | ||||||
|  | 
 | ||||||
|  | import { initialXml } from './Blockly/initialXml.js'; | ||||||
| 
 | 
 | ||||||
| import MaxBlocks from './MaxBlocks'; |  | ||||||
| import Compile from './Compile'; | import Compile from './Compile'; | ||||||
| 
 | 
 | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import Button from '@material-ui/core/Button'; | import Button from '@material-ui/core/Button'; | ||||||
| import DialogTitle from '@material-ui/core/DialogTitle'; | import DialogTitle from '@material-ui/core/DialogTitle'; | ||||||
| import DialogContent from '@material-ui/core/DialogContent'; | import DialogContent from '@material-ui/core/DialogContent'; | ||||||
| import DialogActions from '@material-ui/core/DialogActions'; | import DialogActions from '@material-ui/core/DialogActions'; | ||||||
| import Dialog from '@material-ui/core/Dialog'; | import Dialog from '@material-ui/core/Dialog'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | 
 | ||||||
|  | import { faSave, faUpload, faShare } from "@fortawesome/free-solid-svg-icons"; | ||||||
|  | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   button: { | ||||||
|  |     backgroundColor: theme.palette.primary.main, | ||||||
|  |     color: theme.palette.primary.contrastText, | ||||||
|  |     width: '40px', | ||||||
|  |     height: '40px', | ||||||
|  |     '&:hover': { | ||||||
|  |       backgroundColor: theme.palette.primary.main, | ||||||
|  |       color: theme.palette.primary.contrastText, | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class WorkspaceFunc extends Component { | class WorkspaceFunc extends Component { | ||||||
| 
 | 
 | ||||||
|   state = { |   constructor(props){ | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|       title: '', |       title: '', | ||||||
|       content: '', |       content: '', | ||||||
|       open: false |       open: false | ||||||
|   } |     }; | ||||||
| 
 |  | ||||||
|   getArduinoCode = () => { |  | ||||||
|     this.setState({ title: 'Adurino Code', content: this.props.arduino, open: true }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   getXMLCode = () => { |  | ||||||
|     this.setState({ title: 'XML Code', content: this.props.xml, open: true }); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toggleDialog = () => { |   toggleDialog = () => { | ||||||
|     this.setState({ open: !this.state }); |     this.setState({ open: !this.state }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   saveXmlFile = (code) => { | ||||||
|  |     // saveTextFileAs
 | ||||||
|  |     var fileName = 'todo.xml' | ||||||
|  |     var blob = new Blob([code], { type: 'text/xml' }); | ||||||
|  |     saveAs(blob, fileName); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uploadXmlFile = (xmlFile) => { | ||||||
|  |     console.log(xmlFile); | ||||||
|  |     if(xmlFile.type !== 'text/xml'){ | ||||||
|  |       this.setState({ open: true, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entsprach nicht dem geforderten Format. Es sind nur XML-Dateien zulässig.' }); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       var reader = new FileReader(); | ||||||
|  |       reader.readAsText(xmlFile); | ||||||
|  |       reader.onloadend = () => { | ||||||
|  |         var xmlDom = null; | ||||||
|  |         try { | ||||||
|  |           xmlDom = Blockly.Xml.textToDom(reader.result); | ||||||
|  |           const workspace = Blockly.getMainWorkspace(); | ||||||
|  |           var xmlBefore = this.props.xml; | ||||||
|  |           workspace.clear(); | ||||||
|  |           this.props.clearStats(); | ||||||
|  |           Blockly.Xml.domToWorkspace(xmlDom, workspace); | ||||||
|  |           if(workspace.getAllBlocks().length < 1){ | ||||||
|  |             Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xmlBefore), workspace) | ||||||
|  |             this.setState({ open: true, title: 'Keine Blöcke', content: 'Es wurden keine Blöcke detektiert. Bitte überprüfe den XML-Code und versuche es erneut.' }); | ||||||
|  |           } | ||||||
|  |         } catch(err){ | ||||||
|  |           this.setState({ open: true, title: 'Ungültige XML', content: 'Die XML-Datei konnte nicht in Blöcke zerlegt werden. Bitte überprüfe den XML-Code und versuche es erneut.' }); | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   resetWorkspace = () => { | ||||||
|  |     const workspace = Blockly.getMainWorkspace(); | ||||||
|  |     Blockly.Events.disable(); // https://groups.google.com/forum/#!topic/blockly/m7e3g0TC75Y
 | ||||||
|  |     // if events are disabled, then the workspace will be cleared AND the blocks are not in the trashcan
 | ||||||
|  |     const xmlDom = Blockly.Xml.textToDom(initialXml) | ||||||
|  |     Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace); | ||||||
|  |     Blockly.Events.enable(); | ||||||
|  |     workspace.options.maxBlocks = Infinity; | ||||||
|  |     this.props.onChangeCode(); | ||||||
|  |     this.props.clearStats(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ marginTop: '20px' }}> |       <div style={{ marginBottom: '20px', width: 'max-content', display: 'flex'}}> | ||||||
|  |         <Compile iconButton /> | ||||||
|  |         <Tooltip title='Blöcke speichern' arrow style={{marginRight: '5px'}}> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.saveXmlFile(this.props.xml)} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faSave} size="xs"/> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  |         <div ref={this.inputRef} style={{width: 'max-content', height: '40px', marginRight: '5px'}}> | ||||||
|  |           <input | ||||||
|  |             style={{display: 'none'}} | ||||||
|  |             accept="text/xml" | ||||||
|  |             onChange={(e) => {this.uploadXmlFile(e.target.files[0])}} | ||||||
|  |             id="open-blocks" | ||||||
|  |             type="file" | ||||||
|  |           /> | ||||||
|  |           <label htmlFor="open-blocks"> | ||||||
|  |             <Tooltip title='Blöcke öffnen' arrow style={{marginRight: '5px'}}> | ||||||
|  |               <div className={this.props.classes.button} style={{borderRadius: '50%', cursor: 'pointer', display: 'table-cell', | ||||||
|  |               verticalAlign: 'middle', | ||||||
|  |               textAlign: 'center'}}> | ||||||
|  |                 <FontAwesomeIcon icon={faUpload} style={{width: '18px', height: '18px'}}/> | ||||||
|  |               </div> | ||||||
|  |             </Tooltip> | ||||||
|  |           </label> | ||||||
|  |         </div> | ||||||
|  |         <Tooltip title='Workspace zurücksetzen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.resetWorkspace()} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faShare} size="xs" flip='horizontal'/> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|         <Dialog onClose={this.toggleDialog} open={this.state.open}> |         <Dialog onClose={this.toggleDialog} open={this.state.open}> | ||||||
|           <DialogTitle>{this.state.title}</DialogTitle> |           <DialogTitle>{this.state.title}</DialogTitle> | ||||||
|           <DialogContent dividers> |           <DialogContent dividers> | ||||||
| @ -45,14 +149,6 @@ class WorkspaceFunc extends Component { | |||||||
|             </Button> |             </Button> | ||||||
|           </DialogActions> |           </DialogActions> | ||||||
|         </Dialog> |         </Dialog> | ||||||
|         <Button style={{ marginRight: '10px', color: 'white' }} variant="contained" color="primary" onClick={() => this.getArduinoCode()}> |  | ||||||
|           Get Adurino Code |  | ||||||
|         </Button> |  | ||||||
|         <Button style={{ marginRight: '10px', color: 'white' }} variant="contained" color="primary" onClick={() => this.getXMLCode()}> |  | ||||||
|           Get XML Code |  | ||||||
|         </Button> |  | ||||||
|         <MaxBlocks /> |  | ||||||
|         <Compile /> |  | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| @ -60,7 +156,9 @@ class WorkspaceFunc extends Component { | |||||||
| 
 | 
 | ||||||
| WorkspaceFunc.propTypes = { | WorkspaceFunc.propTypes = { | ||||||
|   arduino: PropTypes.string.isRequired, |   arduino: PropTypes.string.isRequired, | ||||||
|   xml: PropTypes.string.isRequired |   xml: PropTypes.string.isRequired, | ||||||
|  |   clearStats: PropTypes.func.isRequired, | ||||||
|  |   onChangeCode: PropTypes.func.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
| @ -68,4 +166,4 @@ const mapStateToProps = state => ({ | |||||||
|   xml: state.workspace.code.xml |   xml: state.workspace.code.xml | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(WorkspaceFunc); | export default connect(mapStateToProps, { clearStats, onChangeCode })(withStyles(styles, {withTheme: true})(WorkspaceFunc)); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user