Merge branch 'master' into new-blocks
| @ -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", | ||||||
|  | |||||||
| Before Width: | Height: | Size: 43 B After Width: | Height: | Size: 43 B | 
| Before Width: | Height: | Size: 569 B After Width: | Height: | Size: 569 B | 
| Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B | 
| Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B | 
| Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B | 
| Before Width: | Height: | Size: 1010 B After Width: | Height: | Size: 1010 B | 
| Before Width: | Height: | Size: 771 B After Width: | Height: | Size: 771 B | 
| Before Width: | Height: | Size: 738 B After Width: | Height: | Size: 738 B | 
| Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB | 
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
| @ -5,6 +5,7 @@ export const MOVE_BLOCK = 'MOVE_BLOCK'; | |||||||
| export const CHANGE_BLOCK = 'CHANGE_BLOCK'; | export const CHANGE_BLOCK = 'CHANGE_BLOCK'; | ||||||
| export const DELETE_BLOCK = 'DELETE_BLOCK'; | export const DELETE_BLOCK = 'DELETE_BLOCK'; | ||||||
| export const CLEAR_STATS = 'CLEAR_STATS'; | export const CLEAR_STATS = 'CLEAR_STATS'; | ||||||
|  | export const NAME = 'NAME'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export const TUTORIAL_SUCCESS = 'TUTORIAL_SUCCESS'; | export const TUTORIAL_SUCCESS = 'TUTORIAL_SUCCESS'; | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { NEW_CODE, CHANGE_WORKSPACE, CREATE_BLOCK, MOVE_BLOCK, CHANGE_BLOCK, DELETE_BLOCK, CLEAR_STATS } from './types'; | import { NEW_CODE, CHANGE_WORKSPACE, CREATE_BLOCK, MOVE_BLOCK, CHANGE_BLOCK, DELETE_BLOCK, CLEAR_STATS, NAME } from './types'; | ||||||
| 
 | 
 | ||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from 'blockly/core'; | ||||||
| 
 | 
 | ||||||
| @ -72,3 +72,10 @@ export const clearStats = () => (dispatch) => { | |||||||
|     payload: stats |     payload: stats | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | export const workspaceName = (name) => (dispatch) => { | ||||||
|  |   dispatch({ | ||||||
|  |     type: NAME, | ||||||
|  |     payload: name | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | |||||||
| @ -63,8 +63,8 @@ class BlocklyWindow extends Component { | |||||||
|             length: 1, |             length: 1, | ||||||
|             colour: '#4EAF47', // senseBox-green
 |             colour: '#4EAF47', // senseBox-green
 | ||||||
|             snap: false |             snap: false | ||||||
|           }} |         }} | ||||||
|         media={'/media/'} |         media={'/media/blockly/'} | ||||||
|         move={this.props.move !== undefined && !this.props.move ? {} : |         move={this.props.move !== undefined && !this.props.move ? {} : | ||||||
|           { // https://developers.google.com/blockly/guides/configure/web/move
 |           { // https://developers.google.com/blockly/guides/configure/web/move
 | ||||||
|             scrollbars: true, |             scrollbars: true, | ||||||
|  | |||||||
| @ -1,27 +1,49 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| 
 | 
 | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
|  | import clsx from 'clsx'; | ||||||
| 
 | 
 | ||||||
| import Breadcrumbs from '@material-ui/core/Breadcrumbs'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import MaterialUIBreadcrumbs from '@material-ui/core/Breadcrumbs'; | ||||||
| import Typography from '@material-ui/core/Typography'; | import Typography from '@material-ui/core/Typography'; | ||||||
| 
 | 
 | ||||||
| class MyBreadcrumbs extends Component { | import { faHome } from "@fortawesome/free-solid-svg-icons"; | ||||||
|  | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   home: { | ||||||
|  |     color: theme.palette.secondary.main, | ||||||
|  |     width: '20px !important', | ||||||
|  |     height: '20px', | ||||||
|  |     marginTop: '2px' | ||||||
|  |   }, | ||||||
|  |   hover: { | ||||||
|  |     '&:hover': { | ||||||
|  |       color: theme.palette.primary.main | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class Breadcrumbs extends Component { | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       this.props.content && this.props.content.length > 1 ? |       this.props.content && this.props.content.length > 0 ? | ||||||
|         <Breadcrumbs separator="›" style={{marginBottom: '20px'}}> |         <MaterialUIBreadcrumbs separator="›" style={{marginBottom: '20px'}}> | ||||||
|  |           <Link to={'/'} style={{textDecoration: 'none'}}> | ||||||
|  |             <FontAwesomeIcon className={clsx(this.props.classes.home, this.props.classes.hover)} icon={faHome} size="xs"/> | ||||||
|  |           </Link> | ||||||
|           {this.props.content.splice(0, this.props.content.length-1).map((content, i) => ( |           {this.props.content.splice(0, this.props.content.length-1).map((content, i) => ( | ||||||
|             <Link to={content.link} style={{textDecoration: 'none'}} key={i}> |             <Link to={content.link} style={{textDecoration: 'none'}} key={i}> | ||||||
|               <Typography color="secondary">{content.title}</Typography> |               <Typography className={this.props.classes.hover} color="secondary">{content.title}</Typography> | ||||||
|             </Link> |             </Link> | ||||||
|           ))} |           ))} | ||||||
|           <Typography color="textPrimary" style={{overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '300px'}}> |           <Typography color="textPrimary" style={{overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '300px'}}> | ||||||
|             {this.props.content.slice(-1)[0].title} |             {this.props.content.slice(-1)[0].title} | ||||||
|           </Typography> |           </Typography> | ||||||
|         </Breadcrumbs> |         </MaterialUIBreadcrumbs> | ||||||
|       : null |       : null | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default MyBreadcrumbs; | export default withStyles(styles, {withTheme: true})(Breadcrumbs); | ||||||
|  | |||||||
| @ -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); |  | ||||||
| @ -7,6 +7,7 @@ import "prismjs/themes/prism.css"; | |||||||
| import "prismjs/plugins/line-numbers/prism-line-numbers"; | import "prismjs/plugins/line-numbers/prism-line-numbers"; | ||||||
| import "prismjs/plugins/line-numbers/prism-line-numbers.css"; | import "prismjs/plugins/line-numbers/prism-line-numbers.css"; | ||||||
| 
 | 
 | ||||||
|  | import withWidth from '@material-ui/core/withWidth'; | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import MuiAccordion from '@material-ui/core/Accordion'; | import MuiAccordion from '@material-ui/core/Accordion'; | ||||||
| import MuiAccordionSummary from '@material-ui/core/AccordionSummary'; | import MuiAccordionSummary from '@material-ui/core/AccordionSummary'; | ||||||
| @ -136,4 +137,4 @@ const mapStateToProps = state => ({ | |||||||
|   xml: state.workspace.code.xml |   xml: state.workspace.code.xml | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(CodeViewer); | export default connect(mapStateToProps, null)(withWidth()(CodeViewer)); | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| 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 { workspaceName } from '../actions/workspaceActions'; | ||||||
|  | 
 | ||||||
|  | import { detectWhitespacesAndReturnReadableResult } from '../helpers/whitespace'; | ||||||
| 
 | 
 | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import Button from '@material-ui/core/Button'; | import Button from '@material-ui/core/Button'; | ||||||
| @ -10,71 +13,136 @@ 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 TextField from '@material-ui/core/TextField'; | ||||||
|  | 
 | ||||||
|  | import { faCogs } 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, | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Compile extends Component { | class Compile extends Component { | ||||||
| 
 | 
 | ||||||
|   state = { |   constructor(props){ | ||||||
|     progress: false, |     super(props); | ||||||
|     open: false |     this.state = { | ||||||
|  |       progress: false, | ||||||
|  |       open: false, | ||||||
|  |       file: false, | ||||||
|  |       title: '', | ||||||
|  |       content: '', | ||||||
|  |       name: props.name | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   componentDidUpdate(props){ | ||||||
|  |     if(props.name !== this.props.name){ | ||||||
|  |       this.setState({name: this.props.name}); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|   compile = () => { |   compile = () => { | ||||||
|  |     this.setState({ progress: true }); | ||||||
|     const data = { |     const data = { | ||||||
|       "board": process.env.REACT_APP_BOARD, |       "board": process.env.REACT_APP_BOARD, | ||||||
|       "sketch": this.props.arduino |       "sketch": this.props.arduino | ||||||
|     }; |     }; | ||||||
|     this.setState({ progress: true }); |  | ||||||
|     fetch(`${process.env.REACT_APP_COMPILER_URL}/compile`, { |     fetch(`${process.env.REACT_APP_COMPILER_URL}/compile`, { | ||||||
|       method: "POST", |       method: "POST", | ||||||
|       headers: { 'Content-Type': 'application/json' }, |       headers: { 'Content-Type': 'application/json' }, | ||||||
|       body: JSON.stringify(data) |       body: JSON.stringify(data) | ||||||
|     }) |     }) | ||||||
|       .then(response => response.json()) |     .then(response => response.json()) | ||||||
|       .then(data => { |     .then(data => { | ||||||
|         console.log(data) |       console.log(data); | ||||||
|         this.download(data.data.id) |       this.setState({id: data.data.id}, () => { | ||||||
|       }) |         this.createFileName(); | ||||||
|       .catch(err => { |  | ||||||
|         console.log(err); |  | ||||||
|         this.setState({ progress: false, open: true }); |  | ||||||
|       }); |       }); | ||||||
|  |     }) | ||||||
|  |     .catch(err => { | ||||||
|  |       console.log(err); | ||||||
|  |       this.setState({ progress: false, file: false, open: true, title: 'Fehler', content: 'Etwas ist beim Kompilieren schief gelaufen. Versuche es nochmal.' }); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   download = (id) => { |   download = () => { | ||||||
|     const filename = 'sketch' |     const id = this.state.id; | ||||||
|  |     const filename = detectWhitespacesAndReturnReadableResult(this.state.name); | ||||||
|  |     this.toggleDialog(); | ||||||
|  |     this.props.workspaceName(this.state.name); | ||||||
|     window.open(`${process.env.REACT_APP_COMPILER_URL}/download?id=${id}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`, '_self'); |     window.open(`${process.env.REACT_APP_COMPILER_URL}/download?id=${id}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`, '_self'); | ||||||
|     this.setState({ progress: false }); |     this.setState({ progress: false }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toggleDialog = () => { |   toggleDialog = () => { | ||||||
|     this.setState({ open: !this.state }); |     this.setState({ open: !this.state, progress: false }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createFileName = () => { | ||||||
|  |     if(this.state.name){ | ||||||
|  |       this.download(); | ||||||
|  |     } | ||||||
|  |     else{ | ||||||
|  |       this.setState({ file: true, open: true, title: 'Blöcke kompilieren', content: 'Bitte gib einen Namen für die Bennenung des zu kompilierenden Programms ein und bestätige diesen mit einem Klick auf \'Eingabe\'.' }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setFileName = (e) => { | ||||||
|  |     this.setState({name: e.target.value}); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ display: 'inline' }}> |       <div style={{}}> | ||||||
|         <Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}> |         {this.props.iconButton ? | ||||||
|           Kompilieren |           <Tooltip title='Blöcke kompilieren' arrow style={{marginRight: '5px'}}> | ||||||
|         </Button> |             <IconButton | ||||||
|  |               className={this.props.classes.button} | ||||||
|  |               onClick={() => this.compile()} | ||||||
|  |             > | ||||||
|  |               <FontAwesomeIcon icon={faCogs} size="xs"/> | ||||||
|  |             </IconButton> | ||||||
|  |           </Tooltip> | ||||||
|  |          : | ||||||
|  |           <Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}> | ||||||
|  |             <FontAwesomeIcon icon={faCogs} style={{marginRight: '5px'}}/> Kompilieren | ||||||
|  |           </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> | ||||||
|         <Dialog onClose={this.toggleDialog} open={this.state.open}> |         <Dialog onClose={this.toggleDialog} open={this.state.open}> | ||||||
|           <DialogTitle>Fehler</DialogTitle> |           <DialogTitle>{this.state.title}</DialogTitle> | ||||||
|           <DialogContent dividers> |           <DialogContent dividers> | ||||||
|             Etwas ist beim Kompilieren schief gelaufen. Versuche es nochmal. |             {this.state.content} | ||||||
|  |             {this.state.file ? | ||||||
|  |               <div style={{marginTop: '10px'}}> | ||||||
|  |                 <TextField autoFocus placeholder='Dateiname' value={this.state.name} onChange={this.setFileName} style={{marginRight: '10px'}}/> | ||||||
|  |                 <Button disabled={!this.state.name} variant='contained' color='primary' onClick={() => this.download()}>Eingabe</Button> | ||||||
|  |               </div> | ||||||
|  |             : null} | ||||||
|           </DialogContent> |           </DialogContent> | ||||||
|           <DialogActions> |           <DialogActions> | ||||||
|             <Button onClick={this.toggleDialog} color="primary"> |             <Button onClick={this.state.file ? () => {this.toggleDialog(); this.setState({name: this.props.name})} : this.toggleDialog} color="primary"> | ||||||
|               Schließen |               {this.state.file ? 'Abbrechen' : 'Schließen'} | ||||||
|             </Button> |             </Button> | ||||||
|           </DialogActions> |           </DialogActions> | ||||||
|         </Dialog> |         </Dialog> | ||||||
| @ -84,11 +152,15 @@ class Compile extends Component { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Compile.propTypes = { | Compile.propTypes = { | ||||||
|   arduino: PropTypes.string.isRequired |   arduino: PropTypes.string.isRequired, | ||||||
|  |   name: PropTypes.string, | ||||||
|  |   workspaceName: PropTypes.func.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   arduino: state.workspace.code.arduino |   arduino: state.workspace.code.arduino, | ||||||
|  |   name: state.workspace.name | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(Compile)); | 
 | ||||||
|  | export default connect(mapStateToProps, { workspaceName })(withStyles(styles, {withTheme: true})(Compile)); | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| 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 } from '../actions/workspaceActions'; | import { clearStats, workspaceName } from '../actions/workspaceActions'; | ||||||
| 
 | 
 | ||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from 'blockly/core'; | ||||||
| 
 | 
 | ||||||
| @ -58,6 +58,7 @@ class Home extends Component { | |||||||
| 
 | 
 | ||||||
|   componentWillUnmount(){ |   componentWillUnmount(){ | ||||||
|     this.props.clearStats(); |     this.props.clearStats(); | ||||||
|  |     this.props.workspaceName(null); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onChange = () => { |   onChange = () => { | ||||||
| @ -72,7 +73,8 @@ class Home extends Component { | |||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <WorkspaceStats /> |         <div style={{float: 'right', height: '40px', marginBottom: '20px'}}><WorkspaceFunc /></div> | ||||||
|  |         <div style={{float: 'left', height: '40px', position: 'relative'}}><WorkspaceStats /></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,15 +95,15 @@ class Home extends Component { | |||||||
|             </Grid> |             </Grid> | ||||||
|             : null} |             : null} | ||||||
|         </Grid> |         </Grid> | ||||||
|         <WorkspaceFunc /> |  | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Home.propTypes = { | Home.propTypes = { | ||||||
|   clearStats: PropTypes.func.isRequired |   clearStats: PropTypes.func.isRequired, | ||||||
|  |   workspaceName: PropTypes.func.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default connect(null, { clearStats })(withStyles(styles, { withTheme: true })(Home)); | export default connect(null, { clearStats, workspaceName })(withStyles(styles, { withTheme: true })(Home)); | ||||||
|  | |||||||
| @ -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> | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ class NotFound extends Component { | |||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <Breadcrumbs content={[{link: '/', title: 'Home'}, {link: this.props.location.pathname, title: 'Error'}]}/> |         <Breadcrumbs content={[{link: this.props.location.pathname, title: 'Error'}]}/> | ||||||
|         <Typography variant='h4' style={{marginBottom: '5px'}}>Die von Ihnen angeforderte Seite kann nicht gefunden werden.</Typography> |         <Typography variant='h4' style={{marginBottom: '5px'}}>Die von Ihnen angeforderte Seite kann nicht gefunden werden.</Typography> | ||||||
|         <Typography variant='body1'>Die gesuchte Seite wurde möglicherweise entfernt, ihr Name wurde geändert oder sie ist vorübergehend nicht verfügbar.</Typography> |         <Typography variant='body1'>Die gesuchte Seite wurde möglicherweise entfernt, ihr Name wurde geändert oder sie ist vorübergehend nicht verfügbar.</Typography> | ||||||
|         {this.props.button ? |         {this.props.button ? | ||||||
|  | |||||||
| @ -1,17 +1,32 @@ | |||||||
| 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 { workspaceName } from '../../actions/workspaceActions'; | ||||||
| 
 | 
 | ||||||
| import BlocklyWindow from '../Blockly/BlocklyWindow'; | import BlocklyWindow from '../Blockly/BlocklyWindow'; | ||||||
| import SolutionCheck from './SolutionCheck'; | import SolutionCheck from './SolutionCheck'; | ||||||
| import CodeViewer from '../CodeViewer'; | import CodeViewer from '../CodeViewer'; | ||||||
|  | import WorkspaceFunc from '../WorkspaceFunc'; | ||||||
| 
 | 
 | ||||||
|  | import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | ||||||
| import Grid from '@material-ui/core/Grid'; | import Grid from '@material-ui/core/Grid'; | ||||||
| import Card from '@material-ui/core/Card'; | import Card from '@material-ui/core/Card'; | ||||||
| import Typography from '@material-ui/core/Typography'; | import Typography from '@material-ui/core/Typography'; | ||||||
| 
 | 
 | ||||||
| class Assessment extends Component { | class Assessment extends Component { | ||||||
| 
 | 
 | ||||||
|  |   componentDidMount(){ | ||||||
|  |     // alert(this.props.name);
 | ||||||
|  |     this.props.workspaceName(this.props.name); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentDidUpdate(props){ | ||||||
|  |     if(props.name !== this.props.name){ | ||||||
|  |       // alert(this.props.name);
 | ||||||
|  |       this.props.workspaceName(this.props.name); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   render() { |   render() { | ||||||
|     var tutorialId = this.props.currentTutorialId; |     var tutorialId = this.props.currentTutorialId; | ||||||
|     var currentTask = this.props.step; |     var currentTask = this.props.step; | ||||||
| @ -21,18 +36,18 @@ class Assessment extends Component { | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div style={{width: '100%'}}> |       <div style={{width: '100%'}}> | ||||||
|         <Typography variant='h4' style={{marginBottom: '5px'}}>{currentTask.headline}</Typography> |         <Typography variant='h4' style={{float: 'left', marginBottom: '5px', height: '40px', display: 'table'}}>{currentTask.headline}</Typography> | ||||||
|  |         <div style={{float: 'right', height: '40px'}}><WorkspaceFunc solutionCheck/></div> | ||||||
|         <Grid container spacing={2} style={{marginBottom: '5px'}}> |         <Grid container spacing={2} style={{marginBottom: '5px'}}> | ||||||
|           <Grid item xs={12} md={6} lg={8} style={{ position: 'relative' }}> |           <Grid item xs={12} md={6} lg={8}> | ||||||
|             <SolutionCheck /> |  | ||||||
|             <BlocklyWindow initialXml={statusTask ? statusTask.xml ? statusTask.xml : null : null}/> |             <BlocklyWindow initialXml={statusTask ? statusTask.xml ? statusTask.xml : null : null}/> | ||||||
|           </Grid> |           </Grid> | ||||||
|           <Grid item xs={12} md={6} lg={4}> |           <Grid item xs={12} md={6} lg={4} style={isWidthDown('sm', this.props.width) ? {height: 'max-content'} : {}}> | ||||||
|             <Card style={{height: 'calc(50% - 30px)', padding: '10px', marginBottom: '10px'}}> |             <Card style={{height: 'calc(50% - 30px)', padding: '10px', marginBottom: '10px'}}> | ||||||
|               <Typography variant='h5'>Arbeitsauftrag</Typography> |               <Typography variant='h5'>Arbeitsauftrag</Typography> | ||||||
|               <Typography>{currentTask.text1}</Typography> |               <Typography>{currentTask.text1}</Typography> | ||||||
|             </Card> |             </Card> | ||||||
|             <div style={{height: '50%'}}> |             <div style={isWidthDown('sm', this.props.width) ? {height: '500px'} : {height: '50%'}}> | ||||||
|               <CodeViewer /> |               <CodeViewer /> | ||||||
|             </div> |             </div> | ||||||
|           </Grid> |           </Grid> | ||||||
| @ -45,7 +60,8 @@ class Assessment extends Component { | |||||||
| Assessment.propTypes = { | Assessment.propTypes = { | ||||||
|   currentTutorialId: PropTypes.number, |   currentTutorialId: PropTypes.number, | ||||||
|   status: PropTypes.array.isRequired, |   status: PropTypes.array.isRequired, | ||||||
|   change: PropTypes.number.isRequired |   change: PropTypes.number.isRequired, | ||||||
|  |   workspaceName: PropTypes.func.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
| @ -54,4 +70,4 @@ const mapStateToProps = state => ({ | |||||||
|   currentTutorialId: state.tutorial.currentId |   currentTutorialId: state.tutorial.currentId | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(Assessment); | export default connect(mapStateToProps, { workspaceName })(withWidth()(Assessment)); | ||||||
|  | |||||||
| @ -24,15 +24,15 @@ const styles = theme => ({ | |||||||
|     color: fade(theme.palette.secondary.main, 0.6) |     color: fade(theme.palette.secondary.main, 0.6) | ||||||
|   }, |   }, | ||||||
|   outerDivError: { |   outerDivError: { | ||||||
|     stroke: fade(theme.palette.error.dark, 0.2), |     stroke: fade(theme.palette.error.dark, 0.6), | ||||||
|     color: fade(theme.palette.error.dark, 0.2) |     color: fade(theme.palette.error.dark, 0.6) | ||||||
|   }, |   }, | ||||||
|   outerDivSuccess: { |   outerDivSuccess: { | ||||||
|     stroke: fade(theme.palette.primary.main, 0.2), |     stroke: fade(theme.palette.primary.main, 0.6), | ||||||
|     color: fade(theme.palette.primary.main, 0.2) |     color: fade(theme.palette.primary.main, 0.6) | ||||||
|   }, |   }, | ||||||
|   outerDivOther: { |   outerDivOther: { | ||||||
|     stroke: fade(theme.palette.secondary.main, 0.2) |     stroke: fade(theme.palette.secondary.main, 0.6) | ||||||
|   }, |   }, | ||||||
|   innerDiv: { |   innerDiv: { | ||||||
|     width: 'inherit', |     width: 'inherit', | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ import { withRouter } from 'react-router-dom'; | |||||||
| import Compile from '../Compile'; | import Compile from '../Compile'; | ||||||
| 
 | 
 | ||||||
| import tutorials from './tutorials.json'; | import tutorials from './tutorials.json'; | ||||||
| import { checkXml } from './compareXml'; | import { checkXml } from '../../helpers/compareXml'; | ||||||
| 
 | 
 | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import IconButton from '@material-ui/core/IconButton'; | import IconButton from '@material-ui/core/IconButton'; | ||||||
| @ -60,10 +60,10 @@ class SolutionCheck extends Component { | |||||||
|     const steps = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps; |     const steps = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps; | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <Tooltip title='Lösung kontrollieren'> |         <Tooltip title='Lösung kontrollieren' arrow> | ||||||
|           <IconButton |           <IconButton | ||||||
|             className={this.props.classes.compile} |             className={this.props.classes.compile} | ||||||
|             style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }} |             style={{width: '40px', height: '40px', marginRight: '5px'}} | ||||||
|             onClick={() => this.check()} |             onClick={() => this.check()} | ||||||
|           > |           > | ||||||
|             <FontAwesomeIcon icon={faPlay} size="xs"/> |             <FontAwesomeIcon icon={faPlay} size="xs"/> | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| 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 { workspaceName } from '../../actions/workspaceActions'; | ||||||
| import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; | import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; | ||||||
| 
 | 
 | ||||||
| import Breadcrumbs from '../Breadcrumbs'; | import Breadcrumbs from '../Breadcrumbs'; | ||||||
| @ -10,6 +11,8 @@ import Instruction from './Instruction'; | |||||||
| import Assessment from './Assessment'; | import Assessment from './Assessment'; | ||||||
| import NotFound from '../NotFound'; | import NotFound from '../NotFound'; | ||||||
| 
 | 
 | ||||||
|  | import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace'; | ||||||
|  | 
 | ||||||
| import tutorials from './tutorials.json'; | import tutorials from './tutorials.json'; | ||||||
| 
 | 
 | ||||||
| import Card from '@material-ui/core/Card'; | import Card from '@material-ui/core/Card'; | ||||||
| @ -29,6 +32,7 @@ class Tutorial extends Component { | |||||||
| 
 | 
 | ||||||
|   componentWillUnmount(){ |   componentWillUnmount(){ | ||||||
|     this.props.tutorialId(null); |     this.props.tutorialId(null); | ||||||
|  |     this.props.workspaceName(null); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
| @ -36,12 +40,13 @@ class Tutorial extends Component { | |||||||
|     var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0]; |     var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0]; | ||||||
|     var steps = tutorial ? tutorial.steps : null; |     var steps = tutorial ? tutorial.steps : null; | ||||||
|     var step = steps ? steps[this.props.activeStep] : null; |     var step = steps ? steps[this.props.activeStep] : null; | ||||||
|  |     var name = step ? `${detectWhitespacesAndReturnReadableResult(tutorial.title)}_${detectWhitespacesAndReturnReadableResult(step.headline)}` : null; | ||||||
|     return ( |     return ( | ||||||
|       !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ? |       !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ? | ||||||
|         <NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/> |         <NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/> | ||||||
|       : |       : | ||||||
|       <div> |       <div> | ||||||
|         <Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/> |         <Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/> | ||||||
| 
 | 
 | ||||||
|         <StepperHorizontal /> |         <StepperHorizontal /> | ||||||
| 
 | 
 | ||||||
| @ -52,7 +57,7 @@ class Tutorial extends Component { | |||||||
|             {step ? |             {step ? | ||||||
|               step.type === 'instruction' ? |               step.type === 'instruction' ? | ||||||
|                 <Instruction step={step}/> |                 <Instruction step={step}/> | ||||||
|               : <Assessment step={step}/> // if step.type === 'assessment'
 |               : <Assessment step={step} name={name}/> // if step.type === 'assessment'
 | ||||||
|              : null} |              : null} | ||||||
| 
 | 
 | ||||||
|             <div style={{marginTop: '20px', position: 'absolute', bottom: '10px'}}> |             <div style={{marginTop: '20px', position: 'absolute', bottom: '10px'}}> | ||||||
| @ -69,6 +74,7 @@ class Tutorial extends Component { | |||||||
| Tutorial.propTypes = { | Tutorial.propTypes = { | ||||||
|   tutorialId: PropTypes.func.isRequired, |   tutorialId: PropTypes.func.isRequired, | ||||||
|   tutorialStep: PropTypes.func.isRequired, |   tutorialStep: PropTypes.func.isRequired, | ||||||
|  |   workspaceName: PropTypes.func.isRequired, | ||||||
|   currentTutorialId: PropTypes.number, |   currentTutorialId: PropTypes.number, | ||||||
|   status: PropTypes.array.isRequired, |   status: PropTypes.array.isRequired, | ||||||
|   change: PropTypes.number.isRequired, |   change: PropTypes.number.isRequired, | ||||||
| @ -82,4 +88,4 @@ const mapStateToProps = state => ({ | |||||||
|   activeStep: state.tutorial.activeStep |   activeStep: state.tutorial.activeStep | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, { tutorialId, tutorialStep })(Tutorial); | export default connect(mapStateToProps, { tutorialId, tutorialStep, workspaceName })(Tutorial); | ||||||
|  | |||||||
| @ -29,15 +29,15 @@ const styles = (theme) => ({ | |||||||
|     color: fade(theme.palette.secondary.main, 0.6) |     color: fade(theme.palette.secondary.main, 0.6) | ||||||
|   }, |   }, | ||||||
|   outerDivError: { |   outerDivError: { | ||||||
|     stroke: fade(theme.palette.error.dark, 0.2), |     stroke: fade(theme.palette.error.dark, 0.6), | ||||||
|     color: fade(theme.palette.error.dark, 0.2) |     color: fade(theme.palette.error.dark, 0.6) | ||||||
|   }, |   }, | ||||||
|   outerDivSuccess: { |   outerDivSuccess: { | ||||||
|     stroke: fade(theme.palette.primary.main, 0.2), |     stroke: fade(theme.palette.primary.main, 0.6), | ||||||
|     color: fade(theme.palette.primary.main, 0.2) |     color: fade(theme.palette.primary.main, 0.6) | ||||||
|   }, |   }, | ||||||
|   outerDivOther: { |   outerDivOther: { | ||||||
|     stroke: fade(theme.palette.secondary.main, 0.2) |     stroke: fade(theme.palette.secondary.main, 0.6) | ||||||
|   }, |   }, | ||||||
|   innerDiv: { |   innerDiv: { | ||||||
|     width: 'inherit', |     width: 'inherit', | ||||||
| @ -54,7 +54,7 @@ class TutorialHome extends Component { | |||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}]}/> |         <Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}]}/> | ||||||
| 
 | 
 | ||||||
|         <h1>Tutorial-Übersicht</h1> |         <h1>Tutorial-Übersicht</h1> | ||||||
|         <Grid container spacing={2}> |         <Grid container spacing={2}> | ||||||
|  | |||||||
| @ -1,58 +1,217 @@ | |||||||
| 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, workspaceName } from '../actions/workspaceActions'; | ||||||
|  | 
 | ||||||
|  | import * as Blockly from 'blockly/core'; | ||||||
|  | 
 | ||||||
|  | import { saveAs } from 'file-saver'; | ||||||
|  | 
 | ||||||
|  | import { detectWhitespacesAndReturnReadableResult } from '../helpers/whitespace'; | ||||||
|  | import { initialXml } from './Blockly/initialXml.js'; | ||||||
| 
 | 
 | ||||||
| import MaxBlocks from './MaxBlocks'; |  | ||||||
| import Compile from './Compile'; | import Compile from './Compile'; | ||||||
|  | import SolutionCheck from './Tutorial/SolutionCheck'; | ||||||
| 
 | 
 | ||||||
|  | import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | ||||||
|  | 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 TextField from '@material-ui/core/TextField'; | ||||||
|  | import Typography from '@material-ui/core/Typography'; | ||||||
|  | 
 | ||||||
|  | import { faPen, 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, | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   workspaceName: { | ||||||
|  |     backgroundColor: theme.palette.secondary.main, | ||||||
|  |     borderRadius: '25px', | ||||||
|  |     display: 'inline-flex', | ||||||
|  |     cursor: 'pointer', | ||||||
|  |     '&:hover': { | ||||||
|  |       color: theme.palette.primary.main, | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class WorkspaceFunc extends Component { | class WorkspaceFunc extends Component { | ||||||
| 
 | 
 | ||||||
|   state = { |   constructor(props){ | ||||||
|     title: '', |     super(props); | ||||||
|     content: '', |     this.inputRef = React.createRef(); | ||||||
|     open: false |     this.state = { | ||||||
|  |       title: '', | ||||||
|  |       content: '', | ||||||
|  |       open: false, | ||||||
|  |       file: false, | ||||||
|  |       saveXml: false, | ||||||
|  |       name: props.name | ||||||
|  |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   getArduinoCode = () => { |   componentDidUpdate(props){ | ||||||
|     this.setState({ title: 'Adurino Code', content: this.props.arduino, open: true }); |     if(props.name !== this.props.name){ | ||||||
|   } |       this.setState({name: this.props.name}); | ||||||
| 
 |     } | ||||||
|   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 = () => { | ||||||
|  |     var code = this.props.xml; | ||||||
|  |     this.toggleDialog(); | ||||||
|  |     var fileName = detectWhitespacesAndReturnReadableResult(this.state.name); | ||||||
|  |     this.props.workspaceName(this.state.name); | ||||||
|  |     fileName = `${fileName}.xml` | ||||||
|  |     var blob = new Blob([code], { type: 'text/xml' }); | ||||||
|  |     saveAs(blob, fileName); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   createFileName = () => { | ||||||
|  |     if(this.state.name){ | ||||||
|  |       this.saveXmlFile(); | ||||||
|  |     } | ||||||
|  |     else{ | ||||||
|  |       this.setState({ file: true, saveXml: true, open: true, title: 'Blöcke speichern', content: 'Bitte gib einen Namen für die Bennenung der XML-Datei ein und bestätige diesen mit einem Klick auf \'Eingabe\'.' }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setFileName = (e) => { | ||||||
|  |     this.setState({name: e.target.value}); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uploadXmlFile = (xmlFile) => { | ||||||
|  |     if(xmlFile.type !== 'text/xml'){ | ||||||
|  |       this.setState({ open: true, file: false, 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, file: false, title: 'Keine Blöcke', content: 'Es wurden keine Blöcke detektiert. Bitte überprüfe den XML-Code und versuche es erneut.' }); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             if(!this.props.solutionCheck){ | ||||||
|  |               var extensionPosition = xmlFile.name.lastIndexOf('.'); | ||||||
|  |               this.props.workspaceName(xmlFile.name.substr(0, extensionPosition)); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } catch(err){ | ||||||
|  |           this.setState({ open: true, file: false, 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(); | ||||||
|  |     if(!this.props.solutionCheck){ | ||||||
|  |       this.props.workspaceName(null); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ marginTop: '20px' }}> |       <div style={{width: 'max-content', display: 'flex'}}> | ||||||
|  |         {!this.props.solutionCheck ? | ||||||
|  |           <Tooltip title={`Name des Projekts${this.props.name ? `: ${this.props.name}` : ''}`} arrow style={{marginRight: '5px'}}> | ||||||
|  |           <div className={this.props.classes.workspaceName} onClick={() => {this.setState({file: true, open: true, saveXml: false, title: 'Projekt benennen', content: 'Bitte gib einen Namen für das Projekt ein und bestätige diesen mit einem Klick auf \'Eingabe\'.'})}}> | ||||||
|  |             {this.props.name && !isWidthDown('xs', this.props.width) ? <Typography style={{margin: 'auto -3px auto 12px'}}>{this.props.name}</Typography> : null} | ||||||
|  |             <div style={{width: '40px', display: 'flex'}}> | ||||||
|  |               <FontAwesomeIcon icon={faPen} style={{height: '18px', width: '18px', margin: 'auto'}}/> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           </Tooltip> | ||||||
|  |         : null} | ||||||
|  |         {this.props.solutionCheck ? <SolutionCheck /> : <Compile iconButton />} | ||||||
|  |         <Tooltip title='Blöcke speichern' arrow style={{marginRight: '5px'}}> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.createFileName()} | ||||||
|  |           > | ||||||
|  |             <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> | ||||||
|             {this.state.content} |             {this.state.content} | ||||||
|  |             {this.state.file ? | ||||||
|  |               <div style={{marginTop: '10px'}}> | ||||||
|  |                 <TextField autoFocus placeholder={this.state.saveXml ?'Dateiname' : 'Projektname'} value={this.state.name} onChange={this.setFileName} style={{marginRight: '10px'}}/> | ||||||
|  |                 <Button disabled={!this.state.name} variant='contained' color='primary' onClick={() => {this.state.saveXml ? this.saveXmlFile() : this.props.workspaceName(this.state.name); this.toggleDialog();}}>Eingabe</Button> | ||||||
|  |               </div> | ||||||
|  |             : null} | ||||||
|           </DialogContent> |           </DialogContent> | ||||||
|           <DialogActions> |           <DialogActions> | ||||||
|             <Button onClick={this.toggleDialog} color="primary"> |             <Button onClick={this.state.file ? () => {this.toggleDialog(); this.setState({name: this.props.name})} : this.toggleDialog} color="primary"> | ||||||
|               Schließen |               {this.state.file ? 'Abbrechen' : 'Schließen'} | ||||||
|             </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,12 +219,17 @@ class WorkspaceFunc extends Component { | |||||||
| 
 | 
 | ||||||
| WorkspaceFunc.propTypes = { | WorkspaceFunc.propTypes = { | ||||||
|   arduino: PropTypes.string.isRequired, |   arduino: PropTypes.string.isRequired, | ||||||
|   xml: PropTypes.string.isRequired |   xml: PropTypes.string.isRequired, | ||||||
|  |   name: PropTypes.string, | ||||||
|  |   clearStats: PropTypes.func.isRequired, | ||||||
|  |   onChangeCode: PropTypes.func.isRequired, | ||||||
|  |   workspaceName: PropTypes.func.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   arduino: state.workspace.code.arduino, |   arduino: state.workspace.code.arduino, | ||||||
|   xml: state.workspace.code.xml |   xml: state.workspace.code.xml, | ||||||
|  |   name: state.workspace.name | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(WorkspaceFunc); | export default connect(mapStateToProps, { clearStats, onChangeCode, workspaceName })(withStyles(styles, {withTheme: true})(withWidth()(WorkspaceFunc))); | ||||||
|  | |||||||
| @ -4,12 +4,15 @@ import { connect } from 'react-redux'; | |||||||
| 
 | 
 | ||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from 'blockly/core'; | ||||||
| 
 | 
 | ||||||
|  | import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import Tooltip from '@material-ui/core/Tooltip'; | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
| import Chip from '@material-ui/core/Chip'; | import Chip from '@material-ui/core/Chip'; | ||||||
| import Avatar from '@material-ui/core/Avatar'; | import Avatar from '@material-ui/core/Avatar'; | ||||||
|  | import Popover from '@material-ui/core/Popover'; | ||||||
| 
 | 
 | ||||||
| import { faPuzzlePiece, faTrash, faPlus, faPen, faArrowsAlt } from "@fortawesome/free-solid-svg-icons"; | import { faPuzzlePiece, faTrash, faPlus, faPen, faArrowsAlt, faEllipsisH } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| 
 | 
 | ||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
| @ -19,65 +22,123 @@ const styles = (theme) => ({ | |||||||
|     marginLeft: '50px', |     marginLeft: '50px', | ||||||
|     padding: '3px 10px', |     padding: '3px 10px', | ||||||
|     // borderRadius: '25%'
 |     // borderRadius: '25%'
 | ||||||
|  |   }, | ||||||
|  |   menu: { | ||||||
|  |     backgroundColor: theme.palette.secondary.main, | ||||||
|  |     color: theme.palette.secondary.contrastText, | ||||||
|  |     width: '40px', | ||||||
|  |     height: '40px', | ||||||
|  |     '&:hover': { | ||||||
|  |       backgroundColor: theme.palette.secondary.main, | ||||||
|  |       color: theme.palette.primary.main, | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| class WorkspaceStats extends Component { | class WorkspaceStats extends Component { | ||||||
| 
 | 
 | ||||||
|  |   state={ | ||||||
|  |     anchor: null | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   handleClose = () => { | ||||||
|  |     this.setState({ anchor: null }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   handleClick = (event) => { | ||||||
|  |     this.setState({ anchor: event.currentTarget }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   render() { |   render() { | ||||||
|  |     const bigDisplay = !isWidthDown('sm', this.props.width); | ||||||
|     const workspace = Blockly.getMainWorkspace(); |     const workspace = Blockly.getMainWorkspace(); | ||||||
|     const remainingBlocksInfinity = workspace ? workspace.remainingCapacity() !== Infinity : null; |     const remainingBlocksInfinity = workspace ? workspace.remainingCapacity() !== Infinity : null; | ||||||
|  |     const stats =  <div style={bigDisplay ? {display: 'flex'} : {display: 'inline'}}> | ||||||
|  |                     <Tooltip title="Anzahl aktueller Blöcke" arrow> | ||||||
|  |                       <Chip | ||||||
|  |                         style={bigDisplay ? {marginRight: '1rem'} : {marginRight: '1rem', marginBottom: '5px'}} | ||||||
|  |                         color="primary" | ||||||
|  |                         avatar={<Avatar><FontAwesomeIcon icon={faPuzzlePiece} /></Avatar>} | ||||||
|  |                         label={workspace ? workspace.getAllBlocks().length : 0}> | ||||||
|  |                       </Chip> | ||||||
|  |                     </Tooltip> | ||||||
|  |                     <Tooltip title="Anzahl neuer Blöcke" arrow> | ||||||
|  |                       <Chip | ||||||
|  |                         style={bigDisplay ? {marginRight: '1rem'} : {marginRight: '1rem', marginBottom: '5px'}} | ||||||
|  |                         color="primary" | ||||||
|  |                         avatar={<Avatar><FontAwesomeIcon icon={faPlus} /></Avatar>} | ||||||
|  |                         label={this.props.create > 0 ? this.props.create : 0}> {/* initialXML is created automatically, Block is not part of the statistics */} | ||||||
|  |                       </Chip> | ||||||
|  |                     </Tooltip> | ||||||
|  |                     <Tooltip title="Anzahl veränderter Blöcke" arrow> | ||||||
|  |                       <Chip | ||||||
|  |                         style={bigDisplay ? {marginRight: '1rem'} : {marginRight: '1rem', marginBottom: '5px'}} | ||||||
|  |                         color="primary" | ||||||
|  |                         avatar={<Avatar><FontAwesomeIcon icon={faPen} /></Avatar>} | ||||||
|  |                         label={this.props.change}> | ||||||
|  |                       </Chip> | ||||||
|  |                     </Tooltip> | ||||||
|  |                     <Tooltip title="Anzahl bewegter Blöcke" arrow> | ||||||
|  |                       <Chip | ||||||
|  |                         style={bigDisplay ? {marginRight: '1rem'} : {marginRight: '1rem', marginBottom: '5px'}} | ||||||
|  |                         color="primary" | ||||||
|  |                         avatar={<Avatar><FontAwesomeIcon icon={faArrowsAlt} /></Avatar>} | ||||||
|  |                         label={this.props.move > 0 ? this.props.move : 0}> {/* initialXML is moved automatically, Block is not part of the statistics */} | ||||||
|  |                       </Chip> | ||||||
|  |                     </Tooltip> | ||||||
|  |                     <Tooltip title="Anzahl gelöschter Blöcke" arrow> | ||||||
|  |                       <Chip | ||||||
|  |                         style={remainingBlocksInfinity ? bigDisplay ? {marginRight: '1rem'} : {marginRight: '1rem', marginBottom: '5px'} : {}} | ||||||
|  |                         color="primary" | ||||||
|  |                         avatar={<Avatar><FontAwesomeIcon icon={faTrash} /></Avatar>} | ||||||
|  |                         label={this.props.delete}> | ||||||
|  |                       </Chip> | ||||||
|  |                     </Tooltip> | ||||||
|  |                     {remainingBlocksInfinity ? | ||||||
|  |                       <Tooltip title="Verbleibende Blöcke" arrow> | ||||||
|  |                         <Chip | ||||||
|  |                           style={bigDisplay ? {marginRight: '1rem'} : {marginRight: '1rem', marginBottom: '5px'}} | ||||||
|  |                           color="primary" | ||||||
|  |                           label={workspace.remainingCapacity()}> | ||||||
|  |                         </Chip> | ||||||
|  |                       </Tooltip> : null} | ||||||
|  |                     </div> | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ marginBottom: '20px' }}> |       bigDisplay ? | ||||||
|         <Tooltip title="Anzahl aktueller Blöcke" > |         <div style={{bottom: 0, position: 'absolute'}}> | ||||||
|           <Chip |           {stats} | ||||||
|             style={{ marginRight: '1rem' }} |         </div> | ||||||
|             color="primary" |       : | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faPuzzlePiece} /></Avatar>} |         <div> | ||||||
|             label={workspace ? workspace.getAllBlocks().length : 0}> |           <Tooltip title='Statistiken anzeigen' arrow> | ||||||
|           </Chip> |             <IconButton | ||||||
|         </Tooltip> |               className={this.props.classes.menu} | ||||||
|         <Tooltip title="Anzahl neuer Blöcke" > |               onClick={(event) => this.handleClick(event)} | ||||||
|           <Chip |             > | ||||||
|             style={{ marginRight: '1rem' }} |               <FontAwesomeIcon icon={faEllipsisH} size="xs"/> | ||||||
|             color="primary" |             </IconButton> | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faPlus} /></Avatar>} |           </Tooltip> | ||||||
|             label={this.props.create > 0 ? this.props.create : 0}> {/* initialXML is created automatically, Block is not part of the statistics */} |           <Popover | ||||||
|           </Chip> |             open={Boolean(this.state.anchor)} | ||||||
|         </Tooltip> |             anchorEl={this.state.anchor} | ||||||
|         <Tooltip title="Anzahl veränderter Blöcke" > |             onClose={this.handleClose} | ||||||
|           <Chip |             anchorOrigin={{ | ||||||
|             style={{ marginRight: '1rem' }} |               vertical: 'bottom', | ||||||
|             color="primary" |               horizontal: 'center', | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faPen} /></Avatar>} |             }} | ||||||
|             label={this.props.change}> |             transformOrigin={{ | ||||||
|           </Chip> |               vertical: 'top', | ||||||
|         </Tooltip> |               horizontal: 'center', | ||||||
|         <Tooltip title="Anzahl bewegter Blöcke" > |             }} | ||||||
|           <Chip |             PaperProps={{ | ||||||
|             style={{ marginRight: '1rem' }} |               style: {margin: '5px'} | ||||||
|             color="primary" |             }} | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faArrowsAlt} /></Avatar>} |           > | ||||||
|             label={this.props.move > 0 ? this.props.move : 0}> {/* initialXML is moved automatically, Block is not part of the statistics */} |             <div style={{margin: '10px'}}> | ||||||
|           </Chip> |               {stats} | ||||||
|         </Tooltip> |             </div> | ||||||
|         <Tooltip title="Anzahl gelöschter Blöcke" > |           </Popover> | ||||||
|           <Chip |         </div> | ||||||
|             style={{ marginRight: '1rem' }} |  | ||||||
|             color="primary" |  | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faTrash} /></Avatar>} |  | ||||||
|             label={this.props.delete}> |  | ||||||
|           </Chip> |  | ||||||
|         </Tooltip> |  | ||||||
|         {remainingBlocksInfinity ? |  | ||||||
|           <Tooltip title="Verbleibende Blöcke" > |  | ||||||
|             <Chip |  | ||||||
|               style={{ marginRight: '1rem' }} |  | ||||||
|               color="primary" |  | ||||||
|               label={workspace.remainingCapacity()}> |  | ||||||
|             </Chip> |  | ||||||
|           </Tooltip> : null} |  | ||||||
|       </div> |  | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| @ -98,4 +159,4 @@ const mapStateToProps = state => ({ | |||||||
|   workspaceChange: state.workspace.change |   workspaceChange: state.workspace.change | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(WorkspaceStats)); | export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(withWidth()(WorkspaceStats))); | ||||||
|  | |||||||
| @ -12,11 +12,14 @@ const parseXml = (xmlString) => { | |||||||
| 
 | 
 | ||||||
| const compareNumberOfBlocks = (originalBlocks, userBlocks) => { | const compareNumberOfBlocks = (originalBlocks, userBlocks) => { | ||||||
|   if(originalBlocks.length !== userBlocks.length){ |   if(originalBlocks.length !== userBlocks.length){ | ||||||
|  |     var blocks; | ||||||
|     if(originalBlocks.length > userBlocks.length){ |     if(originalBlocks.length > userBlocks.length){ | ||||||
|       return {text: 'Es wurden zu wenig Blöcke verwendet.', type: 'error'}; |       blocks = originalBlocks.length-userBlocks.length; | ||||||
|  |       return {text: `Es wurde${blocks === 1 ? '' : 'n'} ${blocks} Bl${blocks === 1 ? 'ock' : 'öcke'} zu wenig verwendet.`, type: 'error'}; | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|       return {text: 'Es wurden zu viele Blöcke verwendet.', type: 'error'}; |       blocks = userBlocks.length-originalBlocks.length; | ||||||
|  |       return {text: `Es wurde${blocks === 1 ? '' : 'n'} ${blocks} Bl${blocks === 1 ? 'ock' : 'öcke'} zu viel verwendet.`, type: 'error'}; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| @ -74,5 +77,5 @@ const compareXml = (originalXml, userXml) => { | |||||||
|     if(parent){return parent;} |     if(parent){return parent;} | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return {text: 'Super. Alles richtig!', type: 'success'}; |   return {text: 'Super, alles richtig! Kompiliere nun die benutzen Blöcke, um eine BIN-Datei zu erhalten und damit das Programm auf die senseBox zu spielen und ausführen zu können.', type: 'success'}; | ||||||
| }; | }; | ||||||
							
								
								
									
										15
									
								
								src/helpers/whitespace.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,15 @@ | |||||||
|  | export const detectWhitespacesAndReturnReadableResult = (word) => { | ||||||
|  |   var readableResult = ''; | ||||||
|  |   var space = false; | ||||||
|  |   for(var i = 0; i < word.length; i++){ | ||||||
|  |     var letter = word[i]; | ||||||
|  |     if(/\s/g.test(letter)){ | ||||||
|  |       space = true; | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       readableResult += space ? letter.toUpperCase() : letter; | ||||||
|  |       space = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return readableResult; | ||||||
|  | }; | ||||||
| @ -1,4 +1,4 @@ | |||||||
| import { CHANGE_WORKSPACE, NEW_CODE, CREATE_BLOCK, MOVE_BLOCK, CHANGE_BLOCK, DELETE_BLOCK, CLEAR_STATS } from '../actions/types'; | import { CHANGE_WORKSPACE, NEW_CODE, CREATE_BLOCK, MOVE_BLOCK, CHANGE_BLOCK, DELETE_BLOCK, CLEAR_STATS, NAME } from '../actions/types'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| const initialState = { | const initialState = { | ||||||
| @ -12,7 +12,8 @@ const initialState = { | |||||||
|     delete: 0, |     delete: 0, | ||||||
|     move: -1 // initialXML is moved automatically, Block is not part of the statistics
 |     move: -1 // initialXML is moved automatically, Block is not part of the statistics
 | ||||||
|   }, |   }, | ||||||
|   change: 0 |   change: 0, | ||||||
|  |   name: null | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function(state = initialState, action){ | export default function(state = initialState, action){ | ||||||
| @ -36,6 +37,11 @@ export default function(state = initialState, action){ | |||||||
|         ...state, |         ...state, | ||||||
|         stats: action.payload |         stats: action.payload | ||||||
|       }; |       }; | ||||||
|  |     case NAME: | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         name: action.payload | ||||||
|  |       } | ||||||
|     default: |     default: | ||||||
|       return state; |       return state; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -11,7 +11,7 @@ const store = createStore( | |||||||
|   initialState, |   initialState, | ||||||
|   compose( |   compose( | ||||||
|     applyMiddleware(...middleware), |     applyMiddleware(...middleware), | ||||||
|     window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() |     // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | |||||||