import React, { Component } from 'react'; import PropTypes from 'prop-types'; 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 Compile from './Compile'; import SolutionCheck from './Tutorial/SolutionCheck'; import Dialog from './Dialog'; import Snackbar from './Snackbar'; import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; import { withStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; 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, faCamera, 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 { constructor(props){ super(props); this.inputRef = React.createRef(); this.state = { title: '', content: '', open: false, file: false, saveFile: false, name: props.name, snackbar: false, key: '', message: '' }; } componentDidUpdate(props){ if(props.name !== this.props.name){ this.setState({name: this.props.name}); } } toggleDialog = () => { 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); } getSvg = () => { const workspace = Blockly.getMainWorkspace(); var canvas = workspace.svgBlockCanvas_.cloneNode(true); if (canvas.children[0] !== undefined) { canvas.removeAttribute("transform"); // does not work in react // var cssContent = Blockly.Css.CONTENT.join(''); var cssContent = ''; for (var i = 0; i < document.getElementsByTagName('style').length; i++) { if(/^blockly.*$/.test(document.getElementsByTagName('style')[i].id)){ cssContent += document.getElementsByTagName('style')[i].firstChild.data.replace(/\..* \./g, '.'); } } // ensure that fill-opacity is 1, because there cannot be a replacing // https://github.com/google/blockly/pull/3431/files#diff-00254795773903d3c0430915a68c9521R328 cssContent += `.blocklyPath { fill-opacity: 1; } `; var css = ''; var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox(); var content = new XMLSerializer().serializeToString(canvas); var xml = ` ${css}">${content}`; var fileName = detectWhitespacesAndReturnReadableResult(this.state.name); this.props.workspaceName(this.state.name); fileName = `${fileName}.svg` var blob = new Blob([xml], { type: 'image/svg+xml;base64' }); saveAs(blob, fileName); } } createFileName = (filetype) => { this.setState({file: filetype}, () => { if(this.state.name){ this.state.file === 'xml' ? this.saveXmlFile() : this.getSvg() } else{ this.setState({ saveFile: true, file: filetype, open: true, title: this.state.file === 'xml' ? 'Blöcke speichern' : 'Screenshot erstellen', content: `Bitte gib einen Namen für die Bennenung der ${this.state.file === 'xml' ? 'XML' : 'SVG'}-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)); } this.setState({ snackbar: true, key: Date.now(), message: 'Das Projekt aus gegebener XML-Datei wurde erfolgreich eingefügt.' }); } } 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.' }); } }; } } renameWorkspace = () => { this.props.workspaceName(this.state.name); this.toggleDialog(); this.setState({ snackbar: true, key: Date.now(), message: `Das Projekt wurde erfolgreich in '${this.state.name}' umbenannt.` }); } 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); } this.setState({ snackbar: true, key: Date.now(), message: 'Das Projekt wurde erfolgreich zurückgesetzt.' }); } render() { return (
{!this.props.solutionCheck ?
{this.setState({file: true, open: true, saveFile: 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) ? {this.props.name} : null}
: null} {this.props.solutionCheck ? : } {this.createFileName('xml');}} >
{this.uploadXmlFile(e.target.files[0])}} id="open-blocks" type="file" />
{this.createFileName('svg');}} > this.resetWorkspace()} > {this.toggleDialog(); this.setState({name: this.props.name})} : this.toggleDialog} button={this.state.file ? 'Abbrechen' : 'Schließen'} > {this.state.file ?
: null}
); }; } WorkspaceFunc.propTypes = { arduino: 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 => ({ arduino: state.workspace.code.arduino, xml: state.workspace.code.xml, name: state.workspace.name }); export default connect(mapStateToProps, { clearStats, onChangeCode, workspaceName })(withStyles(styles, {withTheme: true})(withWidth()(WorkspaceFunc)));