diff --git a/package.json b/package.json index ff3634c..9f78c9a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", "blockly": "^3.20200625.2", + "file-saver": "^2.0.2", "prismjs": "^1.20.0", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/src/components/ClearWorkspace.js b/src/components/ClearWorkspace.js deleted file mode 100644 index 2e31b01..0000000 --- a/src/components/ClearWorkspace.js +++ /dev/null @@ -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 ( - {this.clearWorkspace(); this.props.onClick();}}> - - - - ); - }; -} - -ClearWorkspace.propTypes = { - clearStats: PropTypes.func.isRequired, - onChangeCode: PropTypes.func.isRequired -}; - - -export default connect(null, { clearStats, onChangeCode })(ClearWorkspace); diff --git a/src/components/Compile.js b/src/components/Compile.js index a6c86c2..e42f5cf 100644 --- a/src/components/Compile.js +++ b/src/components/Compile.js @@ -10,11 +10,26 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import DialogContent from '@material-ui/core/DialogContent'; import DialogActions from '@material-ui/core/DialogActions'; 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) => ({ backdrop: { zIndex: theme.zIndex.drawer + 1, 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() { return ( -
- +
+ {this.props.iconButton ? + + this.compile()} + > + + + + : + + } diff --git a/src/components/Home.js b/src/components/Home.js index 5b6f71f..3da011d 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -72,7 +72,8 @@ class Home extends Component { render() { return (
- +
+
@@ -93,7 +94,6 @@ class Home extends Component { : null} -
); }; diff --git a/src/components/Navbar.js b/src/components/Navbar.js index fb5365f..55db003 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; -import ClearWorkspace from './ClearWorkspace'; import senseboxLogo from './sensebox_logo.svg'; import { withRouter } from 'react-router-dom'; @@ -105,7 +104,6 @@ class Navbar extends Component { ))} - diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js index ba67f0c..ead69ec 100644 --- a/src/components/Tutorial/Assessment.js +++ b/src/components/Tutorial/Assessment.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import BlocklyWindow from '../Blockly/BlocklyWindow'; import SolutionCheck from './SolutionCheck'; import CodeViewer from '../CodeViewer'; +import WorkspaceFunc from '../WorkspaceFunc'; import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; import Grid from '@material-ui/core/Grid'; @@ -22,10 +23,10 @@ class Assessment extends Component { return (
- {currentTask.headline} + {currentTask.headline} +
- - + diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index 3156fc6..7d5f979 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -63,7 +63,7 @@ class SolutionCheck extends Component { this.check()} > diff --git a/src/components/WorkspaceFunc.js b/src/components/WorkspaceFunc.js index 8c4826c..d0414c1 100644 --- a/src/components/WorkspaceFunc.js +++ b/src/components/WorkspaceFunc.js @@ -1,39 +1,144 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; 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 SolutionCheck from './Tutorial/SolutionCheck'; +import { withStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import DialogTitle from '@material-ui/core/DialogTitle'; import DialogContent from '@material-ui/core/DialogContent'; import DialogActions from '@material-ui/core/DialogActions'; 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 { - state = { - title: '', - content: '', - 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 }); + constructor(props){ + super(props); + this.inputRef = React.createRef(); + this.state = { + title: '', + content: '', + open: false + }; } toggleDialog = () => { 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() { return ( -
+
+ {this.props.solutionCheck ? : } + + this.saveXmlFile(this.props.xml)} + > + + + +
+ {this.uploadXmlFile(e.target.files[0])}} + id="open-blocks" + type="file" + /> + +
+ + this.resetWorkspace()} + > + + + {this.state.title} @@ -45,14 +150,6 @@ class WorkspaceFunc extends Component { - - - -
); }; @@ -60,7 +157,9 @@ class WorkspaceFunc extends Component { WorkspaceFunc.propTypes = { arduino: PropTypes.string.isRequired, - xml: PropTypes.string.isRequired + xml: PropTypes.string.isRequired, + clearStats: PropTypes.func.isRequired, + onChangeCode: PropTypes.func.isRequired }; const mapStateToProps = state => ({ @@ -68,4 +167,4 @@ const mapStateToProps = state => ({ xml: state.workspace.code.xml }); -export default connect(mapStateToProps, null)(WorkspaceFunc); +export default connect(mapStateToProps, { clearStats, onChangeCode })(withStyles(styles, {withTheme: true})(WorkspaceFunc)); diff --git a/src/components/WorkspaceStats.js b/src/components/WorkspaceStats.js index 9a98571..32fbf1d 100644 --- a/src/components/WorkspaceStats.js +++ b/src/components/WorkspaceStats.js @@ -4,12 +4,15 @@ import { connect } from 'react-redux'; import * as Blockly from 'blockly/core'; +import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; import { withStyles } from '@material-ui/core/styles'; import Tooltip from '@material-ui/core/Tooltip'; +import IconButton from '@material-ui/core/IconButton'; import Chip from '@material-ui/core/Chip'; 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"; const styles = (theme) => ({ @@ -19,65 +22,123 @@ const styles = (theme) => ({ marginLeft: '50px', padding: '3px 10px', // 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 { + state={ + anchor: null + } + + handleClose = () => { + this.setState({ anchor: null }); + } + + handleClick = (event) => { + this.setState({ anchor: event.currentTarget }); + }; + render() { + const bigDisplay = !isWidthDown('xs', this.props.width); const workspace = Blockly.getMainWorkspace(); const remainingBlocksInfinity = workspace ? workspace.remainingCapacity() !== Infinity : null; + const stats =
+ + } + label={workspace ? workspace.getAllBlocks().length : 0}> + + + + } + label={this.props.create > 0 ? this.props.create : 0}> {/* initialXML is created automatically, Block is not part of the statistics */} + + + + } + label={this.props.change}> + + + + } + label={this.props.move > 0 ? this.props.move : 0}> {/* initialXML is moved automatically, Block is not part of the statistics */} + + + + } + label={this.props.delete}> + + + {remainingBlocksInfinity ? + + + + : null} +
return ( -
- - } - label={workspace ? workspace.getAllBlocks().length : 0}> - - - - } - label={this.props.create > 0 ? this.props.create : 0}> {/* initialXML is created automatically, Block is not part of the statistics */} - - - - } - label={this.props.change}> - - - - } - label={this.props.move > 0 ? this.props.move : 0}> {/* initialXML is moved automatically, Block is not part of the statistics */} - - - - } - label={this.props.delete}> - - - {remainingBlocksInfinity ? - - - - : null} -
+ bigDisplay ? +
+ {stats} +
+ : +
+ + this.handleClick(event)} + > + + + + +
+ {stats} +
+
+
); }; } @@ -98,4 +159,4 @@ const mapStateToProps = state => ({ 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)));