create modular structure of WorkspaceFunc component
This commit is contained in:
		
							parent
							
								
									a1834875a0
								
							
						
					
					
						commit
						e6813ba2d3
					
				| @ -76,7 +76,7 @@ export const updateProject = (type, id) => (dispatch, getState) => { | |||||||
|   var body = { |   var body = { | ||||||
|     xml: workspace.code.xml, |     xml: workspace.code.xml, | ||||||
|     title: workspace.name |     title: workspace.name | ||||||
|   } |   }; | ||||||
|   var project = getState().project; |   var project = getState().project; | ||||||
|   if(type==='gallery'){ |   if(type==='gallery'){ | ||||||
|     body.description = project.description; |     body.description = project.description; | ||||||
| @ -99,10 +99,14 @@ export const updateProject = (type, id) => (dispatch, getState) => { | |||||||
|     }) |     }) | ||||||
|     .catch(err => { |     .catch(err => { | ||||||
|       if(err.response){ |       if(err.response){ | ||||||
|  |         if(type === 'project'){ | ||||||
|           dispatch(returnErrors(err.response.data.message, err.response.status, 'PROJECT_UPDATE_FAIL')); |           dispatch(returnErrors(err.response.data.message, err.response.status, 'PROJECT_UPDATE_FAIL')); | ||||||
|  |         } else { | ||||||
|  |           dispatch(returnErrors(err.response.data.message, err.response.status, 'GALLERY_UPDATE_FAIL')); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
| } | }; | ||||||
| 
 | 
 | ||||||
| export const deleteProject = (type, id) => (dispatch, getState) => { | export const deleteProject = (type, id) => (dispatch, getState) => { | ||||||
|   var project = getState().project; |   var project = getState().project; | ||||||
|  | |||||||
| @ -6,11 +6,11 @@ import { clearStats, workspaceName } from '../actions/workspaceActions'; | |||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from 'blockly/core'; | ||||||
| import { createNameId } from 'mnemonic-id'; | import { createNameId } from 'mnemonic-id'; | ||||||
| 
 | 
 | ||||||
| import WorkspaceStats from './WorkspaceStats'; | import WorkspaceStats from './Workspace/WorkspaceStats'; | ||||||
| import WorkspaceFunc from './WorkspaceFunc'; | import WorkspaceFunc from './Workspace/WorkspaceFunc'; | ||||||
| import BlocklyWindow from './Blockly/BlocklyWindow'; | import BlocklyWindow from './Blockly/BlocklyWindow'; | ||||||
| import CodeViewer from './CodeViewer'; | import CodeViewer from './CodeViewer'; | ||||||
| import TrashcanButtons from './TrashcanButtons'; | import TrashcanButtons from './Workspace/TrashcanButtons'; | ||||||
| import HintTutorialExists from './Tutorial/HintTutorialExists'; | import HintTutorialExists from './Tutorial/HintTutorialExists'; | ||||||
| import Snackbar from './Snackbar'; | import Snackbar from './Snackbar'; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ import { Link, withRouter } from 'react-router-dom'; | |||||||
| import Breadcrumbs from '../Breadcrumbs'; | import Breadcrumbs from '../Breadcrumbs'; | ||||||
| import BlocklyWindow from '../Blockly/BlocklyWindow'; | import BlocklyWindow from '../Blockly/BlocklyWindow'; | ||||||
| import Snackbar from '../Snackbar'; | import Snackbar from '../Snackbar'; | ||||||
| import WorkspaceFunc from '../WorkspaceFunc'; | import WorkspaceFunc from '../Workspace/WorkspaceFunc'; | ||||||
| 
 | 
 | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import Grid from '@material-ui/core/Grid'; | import Grid from '@material-ui/core/Grid'; | ||||||
| @ -104,7 +104,7 @@ class ProjectHome extends Component { | |||||||
|                             blockDisabled |                             blockDisabled | ||||||
|                             initialXml={project.xml} |                             initialXml={project.xml} | ||||||
|                           /> |                           /> | ||||||
|                           <Typography variant='body2' style={{fontStyle: 'italic', margin: 0, marginTop: '-30px'}}>{project.description}</Typography> |                           <Typography variant='body2' style={{fontStyle: 'italic', margin: 0, marginTop: '-10px'}}>{project.description}</Typography> | ||||||
|                         </Link> |                         </Link> | ||||||
|                         {this.props.user && this.props.user.email === project.creator ? |                         {this.props.user && this.props.user.email === project.creator ? | ||||||
|                           <div> |                           <div> | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import { workspaceName } from '../../actions/workspaceActions'; | |||||||
| 
 | 
 | ||||||
| import BlocklyWindow from '../Blockly/BlocklyWindow'; | import BlocklyWindow from '../Blockly/BlocklyWindow'; | ||||||
| import CodeViewer from '../CodeViewer'; | import CodeViewer from '../CodeViewer'; | ||||||
| import WorkspaceFunc from '../WorkspaceFunc'; | import WorkspaceFunc from '../Workspace/WorkspaceFunc'; | ||||||
| 
 | 
 | ||||||
| import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | ||||||
| import Grid from '@material-ui/core/Grid'; | import Grid from '@material-ui/core/Grid'; | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import { tutorialCheck, tutorialStep } from '../../actions/tutorialActions'; | |||||||
| 
 | 
 | ||||||
| import { withRouter } from 'react-router-dom'; | import { withRouter } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| import Compile from '../Compile'; | import Compile from '../Workspace/Compile'; | ||||||
| import Dialog from '../Dialog'; | import Dialog from '../Dialog'; | ||||||
| 
 | 
 | ||||||
| import { checkXml } from '../../helpers/compareXml'; | import { checkXml } from '../../helpers/compareXml'; | ||||||
|  | |||||||
| @ -1,11 +1,11 @@ | |||||||
| 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 { workspaceName } from '../../actions/workspaceActions'; | ||||||
| 
 | 
 | ||||||
| import { detectWhitespacesAndReturnReadableResult } from '../helpers/whitespace'; | import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace'; | ||||||
| 
 | 
 | ||||||
| import Dialog from './Dialog'; | import Dialog from '../Dialog'; | ||||||
| 
 | 
 | ||||||
| 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'; | ||||||
							
								
								
									
										89
									
								
								src/components/Workspace/DeleteProject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/components/Workspace/DeleteProject.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import { deleteProject } from '../../actions/projectActions'; | ||||||
|  | 
 | ||||||
|  | import { withRouter } from 'react-router-dom'; | ||||||
|  | 
 | ||||||
|  | import Snackbar from '../Snackbar'; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | 
 | ||||||
|  | import { faTrashAlt } from "@fortawesome/free-solid-svg-icons"; | ||||||
|  | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   buttonTrash: { | ||||||
|  |     backgroundColor: theme.palette.error.dark, | ||||||
|  |     color: theme.palette.primary.contrastText, | ||||||
|  |     width: '40px', | ||||||
|  |     height: '40px', | ||||||
|  |     '&:hover': { | ||||||
|  |       backgroundColor: theme.palette.error.dark, | ||||||
|  |       color: theme.palette.primary.contrastText, | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class DeleteProject extends Component { | ||||||
|  | 
 | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|  |       snackbar: false, | ||||||
|  |       type: '', | ||||||
|  |       key: '', | ||||||
|  |       message: '' | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentDidUpdate(props) { | ||||||
|  |     if(this.props.message !== props.message){ | ||||||
|  |       if(this.props.message.id === 'PROJECT_DELETE_SUCCESS'){ | ||||||
|  |         this.props.history.push(`/${this.props.projectType}`); | ||||||
|  |       } | ||||||
|  |       else if(this.props.message.id === 'PROJECT_DELETE_FAIL'){ | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Löschen des Projektes. Versuche es noch einmal.`, type: 'error' }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div> | ||||||
|  |         <Tooltip title='Projekt löschen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.buttonTrash} | ||||||
|  |             onClick={() => this.props.deleteProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faTrashAlt} size="xs" /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  | 
 | ||||||
|  |         <Snackbar | ||||||
|  |           open={this.state.snackbar} | ||||||
|  |           message={this.state.message} | ||||||
|  |           type={this.state.type} | ||||||
|  |           key={this.state.key} | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DeleteProject.propTypes = { | ||||||
|  |   deleteProject: PropTypes.func.isRequired, | ||||||
|  |   message: PropTypes.string.isRequired | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   message: state.message, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, { deleteProject })(withStyles(styles, { withTheme: true })(withRouter(DeleteProject))); | ||||||
							
								
								
									
										66
									
								
								src/components/Workspace/DownloadProject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/components/Workspace/DownloadProject.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | 
 | ||||||
|  | import { saveAs } from 'file-saver'; | ||||||
|  | 
 | ||||||
|  | import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace'; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | 
 | ||||||
|  | import { faFileDownload } 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 DownloadProject extends Component { | ||||||
|  | 
 | ||||||
|  |   downloadXmlFile = () => { | ||||||
|  |     var code = this.props.xml; | ||||||
|  |     var fileName = detectWhitespacesAndReturnReadableResult(this.props.name); | ||||||
|  |     fileName = `${fileName}.xml` | ||||||
|  |     var blob = new Blob([code], { type: 'text/xml' }); | ||||||
|  |     saveAs(blob, fileName); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div style={this.props.style}> | ||||||
|  |         <Tooltip title='Projekt herunterladen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.downloadXmlFile()} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faFileDownload} size="xs" /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | DownloadProject.propTypes = { | ||||||
|  |   xml: PropTypes.string.isRequired, | ||||||
|  |   name: PropTypes.string.isRequired | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   xml: state.workspace.code.xml, | ||||||
|  |   name: state.workspace.name | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(DownloadProject)); | ||||||
							
								
								
									
										143
									
								
								src/components/Workspace/OpenProject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/components/Workspace/OpenProject.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,143 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import { clearStats, workspaceName } from '../../actions/workspaceActions'; | ||||||
|  | 
 | ||||||
|  | import * as Blockly from 'blockly/core'; | ||||||
|  | 
 | ||||||
|  | import Snackbar from '../Snackbar'; | ||||||
|  | import Dialog from '../Dialog'; | ||||||
|  | 
 | ||||||
|  | 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 Typography from '@material-ui/core/Typography'; | ||||||
|  | 
 | ||||||
|  | import { faUpload } 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 OpenProject extends Component { | ||||||
|  | 
 | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|  |       title: '', | ||||||
|  |       content: '', | ||||||
|  |       open: false, | ||||||
|  |       snackbar: false, | ||||||
|  |       type: '', | ||||||
|  |       key: '', | ||||||
|  |       message: '' | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toggleDialog = () => { | ||||||
|  |     this.setState({ open: !this.state, title: '', content: '' }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   uploadXmlFile = (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.' }); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             if (!this.props.assessment) { | ||||||
|  |               var extensionPosition = xmlFile.name.lastIndexOf('.'); | ||||||
|  |               this.props.workspaceName(xmlFile.name.substr(0, extensionPosition)); | ||||||
|  |             } | ||||||
|  |             this.setState({ snackbar: true, type: 'success', key: Date.now(), message: 'Das Projekt aus gegebener XML-Datei wurde erfolgreich eingefügt.' }); | ||||||
|  |           } | ||||||
|  |         } 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.' }); | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div> | ||||||
|  |         <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='Projekt öffnen' arrow style={this.props.style}> | ||||||
|  |               <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> | ||||||
|  | 
 | ||||||
|  |         <Dialog | ||||||
|  |           open={this.state.open} | ||||||
|  |           title={this.state.title} | ||||||
|  |           content={this.state.content} | ||||||
|  |           onClose={this.toggleDialog} | ||||||
|  |           onClick={this.toggleDialog} | ||||||
|  |           button={'Schließen'} | ||||||
|  |         /> | ||||||
|  |         <Snackbar | ||||||
|  |           open={this.state.snackbar} | ||||||
|  |           message={this.state.message} | ||||||
|  |           type={this.state.type} | ||||||
|  |           key={this.state.key} | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | OpenProject.propTypes = { | ||||||
|  |   clearStats: PropTypes.func.isRequired, | ||||||
|  |   workspaceName: PropTypes.func.isRequired, | ||||||
|  |   xml: PropTypes.string.isRequired, | ||||||
|  |   name: PropTypes.string.isRequired | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   xml: state.workspace.code.xml, | ||||||
|  |   name: state.workspace.name | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, { clearStats, workspaceName })(withStyles(styles, { withTheme: true })(OpenProject)); | ||||||
							
								
								
									
										95
									
								
								src/components/Workspace/ResetWorkspace.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/components/Workspace/ResetWorkspace.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | |||||||
|  | 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 { createNameId } from 'mnemonic-id'; | ||||||
|  | import { initialXml } from '../Blockly/initialXml.js'; | ||||||
|  | 
 | ||||||
|  | import Snackbar from '../Snackbar'; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | 
 | ||||||
|  | import { 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 ResetWorkspace extends Component { | ||||||
|  | 
 | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|  |       snackbar: false, | ||||||
|  |       type: '', | ||||||
|  |       key: '', | ||||||
|  |       message: '', | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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.assessment) { | ||||||
|  |       this.props.workspaceName(createNameId()); | ||||||
|  |     } | ||||||
|  |     this.setState({ snackbar: true, type: 'success', key: Date.now(), message: 'Das Projekt wurde erfolgreich zurückgesetzt.' }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div style={this.props.style}> | ||||||
|  |         <Tooltip title='Workspace zurücksetzen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.resetWorkspace()} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faShare} size="xs" flip='horizontal' /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  | 
 | ||||||
|  |         <Snackbar | ||||||
|  |           open={this.state.snackbar} | ||||||
|  |           message={this.state.message} | ||||||
|  |           type={this.state.type} | ||||||
|  |           key={this.state.key} | ||||||
|  |         /> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ResetWorkspace.propTypes = { | ||||||
|  |   clearStats: PropTypes.func.isRequired, | ||||||
|  |   onChangeCode: PropTypes.func.isRequired, | ||||||
|  |   workspaceName: PropTypes.func.isRequired | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default connect(null, { clearStats, onChangeCode, workspaceName })(withStyles(styles, { withTheme: true })(ResetWorkspace)); | ||||||
							
								
								
									
										200
									
								
								src/components/Workspace/SaveProject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/components/Workspace/SaveProject.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,200 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import { updateProject, setDescription } from '../../actions/projectActions'; | ||||||
|  | 
 | ||||||
|  | import axios from 'axios'; | ||||||
|  | import { withRouter } from 'react-router-dom'; | ||||||
|  | 
 | ||||||
|  | import Snackbar from '../Snackbar'; | ||||||
|  | import Dialog from '../Dialog'; | ||||||
|  | 
 | ||||||
|  | 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 Menu from '@material-ui/core/Menu'; | ||||||
|  | import MenuItem from '@material-ui/core/MenuItem'; | ||||||
|  | 
 | ||||||
|  | import { faSave } 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 SaveProject extends Component { | ||||||
|  | 
 | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|  |       title: '', | ||||||
|  |       content: '', | ||||||
|  |       open: false, | ||||||
|  |       description: props.description, | ||||||
|  |       snackbar: false, | ||||||
|  |       type: '', | ||||||
|  |       key: '', | ||||||
|  |       message: '', | ||||||
|  |       menuOpen: false, | ||||||
|  |       anchor: '', | ||||||
|  |       projectType: props.projectType | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentDidUpdate(props) { | ||||||
|  |     if (props.projectType !== this.props.projectType) { | ||||||
|  |       this.setState({ projectType: this.props.projectType }); | ||||||
|  |     } | ||||||
|  |     if (props.description !== this.props.description) { | ||||||
|  |       this.setState({ description: this.props.description }); | ||||||
|  |     } | ||||||
|  |     if(this.props.message !== props.message){ | ||||||
|  |       if(this.props.message.id === 'PROJECT_UPDATE_SUCCESS'){ | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Das Projekt wurde erfolgreich aktualisiert.`, type: 'success' }); | ||||||
|  |       } | ||||||
|  |       else if(this.props.message.id === 'GALLERY_UPDATE_SUCCESS'){ | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Das Galerie-Projekt wurde erfolgreich aktualisiert.`, type: 'success' }); | ||||||
|  |       } | ||||||
|  |       else if(this.props.message.id === 'PROJECT_UPDATE_FAIL'){ | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Aktualisieren des Projektes. Versuche es noch einmal.`, type: 'error' }); | ||||||
|  |       } | ||||||
|  |       else if(this.props.message.id === 'GALLERY_UPDATE_FAIL'){ | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Aktualisieren des Galerie-Projektes. Versuche es noch einmal.`, type: 'error' }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toggleMenu = (e) => { | ||||||
|  |     this.setState({ menuOpen: !this.state.menuOpen, anchor: e.currentTarget }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   toggleDialog = () => { | ||||||
|  |     this.setState({ open: !this.state, title: '', content: '' }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   saveProject = () => { | ||||||
|  |     var body = { | ||||||
|  |       xml: this.props.xml, | ||||||
|  |       title: this.props.name | ||||||
|  |     }; | ||||||
|  |     if(this.state.projectType === 'gallery'){ | ||||||
|  |       body.description = this.state.description; | ||||||
|  |     } | ||||||
|  |     axios.post(`${process.env.REACT_APP_BLOCKLY_API}/${this.state.projectType}`, body) | ||||||
|  |       .then(res => { | ||||||
|  |         var project = res.data[this.state.projectType]; | ||||||
|  |         this.props.history.push(`/${this.state.projectType}/${project._id}`); | ||||||
|  |       }) | ||||||
|  |       .catch(err => { | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Speichern des ${this.state.projectType === 'gallery' ? 'Galerie-':''}Projektes. Versuche es noch einmal.`, type: 'error' }); | ||||||
|  |         window.scrollTo(0, 0); | ||||||
|  |       }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setDescription = (e) => { | ||||||
|  |     this.setState({ description: e.target.value }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   workspaceDescription = () => { | ||||||
|  |     this.props.setDescription(this.state.description); | ||||||
|  |     this.setState({projectType: 'gallery'}, | ||||||
|  |       () => this.saveProject() | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     console.log(1, this.props); | ||||||
|  |     return ( | ||||||
|  |       <div style={this.props.style}> | ||||||
|  |         <Tooltip title={this.state.projectType === 'project'? 'Projekt aktualisieren':'Projekt speichern'} arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={this.props.user.blocklyRole !== 'user' && (!this.props.project || this.props.user.email === this.props.project.creator) ? (e) => this.toggleMenu(e) : this.state.projectType === 'project' ? () => this.props.updateProject(this.state.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id) : () => {this.setState({projectType: 'project'}, () => this.saveProject())}} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faSave} size="xs" /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  |         <Menu | ||||||
|  |           anchorEl={this.state.anchor} | ||||||
|  |           anchorOrigin={{ | ||||||
|  |             vertical: 'bottom', | ||||||
|  |             horizontal: 'left', | ||||||
|  |           }} | ||||||
|  |           keepMounted | ||||||
|  |           transformOrigin={{ | ||||||
|  |             vertical: 'top', | ||||||
|  |             horizontal: 'left', | ||||||
|  |           }} | ||||||
|  |           open={this.state.menuOpen} | ||||||
|  |           onClose={this.toggleMenu} | ||||||
|  |         > | ||||||
|  |           <MenuItem | ||||||
|  |             onClick={this.state.projectType === 'project' ? (e) => {this.toggleMenu(e); this.props.updateProject(this.state.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} : (e) => {this.toggleMenu(e); this.setState({projectType: 'project'}, () => this.saveProject())}} | ||||||
|  |           > | ||||||
|  |             {this.state.projectType === 'project' ? 'Projekt aktualisieren' : 'Projekt erstellen'} | ||||||
|  |           </MenuItem> | ||||||
|  |           <MenuItem | ||||||
|  |             onClick={this.state.projectType === 'gallery' ? (e) => {this.toggleMenu(e); this.props.updateProject(this.state.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} : (e) => {this.toggleMenu(e); this.setState({ open: true, title: 'Projekbeschreibung ergänzen', content: 'Bitte gib eine Beschreibung für das Galerie-Projekt ein und bestätige deine Angabe mit einem Klick auf \'Eingabe\'.'});}} | ||||||
|  |           > | ||||||
|  |             {this.state.projectType === 'gallery' ? 'Galerie-Projekt aktualisieren' : 'Galerie-Projekt erstellen'} | ||||||
|  |           </MenuItem> | ||||||
|  |         </Menu> | ||||||
|  | 
 | ||||||
|  |         <Snackbar | ||||||
|  |           open={this.state.snackbar} | ||||||
|  |           message={this.state.message} | ||||||
|  |           type={this.state.type} | ||||||
|  |           key={this.state.key} | ||||||
|  |         /> | ||||||
|  |         <Dialog | ||||||
|  |           open={this.state.open} | ||||||
|  |           title={this.state.title} | ||||||
|  |           content={this.state.content} | ||||||
|  |           onClose={() => {this.toggleDialog(); this.setState({ description: this.props.description });}} | ||||||
|  |           onClick={() => {this.toggleDialog(); this.setState({ description: this.props.description });}} | ||||||
|  |           button={'Abbrechen'} | ||||||
|  |         > | ||||||
|  |           <div style={{ marginTop: '10px' }}> | ||||||
|  |             <TextField autoFocus fullWidth multiline placeholder={'Projektbeschreibung'} value={this.state.description} onChange={this.setDescription} style={{ marginBottom: '10px' }} /> | ||||||
|  |             <Button disabled={!this.state.description} variant='contained' color='primary' onClick={() => {this.workspaceDescription(); this.toggleDialog();}}>Eingabe</Button> | ||||||
|  |           </div> | ||||||
|  |         </Dialog> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SaveProject.propTypes = { | ||||||
|  |   updateProject: PropTypes.func.isRequired, | ||||||
|  |   setDescription: PropTypes.func.isRequired, | ||||||
|  |   name: PropTypes.string.isRequired, | ||||||
|  |   description: PropTypes.string.isRequired, | ||||||
|  |   xml: PropTypes.string.isRequired, | ||||||
|  |   message: PropTypes.object.isRequired, | ||||||
|  |   user: PropTypes.object | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   name: state.workspace.name, | ||||||
|  |   description: state.project.description, | ||||||
|  |   xml: state.workspace.code.xml, | ||||||
|  |   message: state.message, | ||||||
|  |   user: state.auth.user | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, { updateProject, setDescription })(withStyles(styles, { withTheme: true })(withRouter(SaveProject))); | ||||||
							
								
								
									
										97
									
								
								src/components/Workspace/Screenshot.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/components/Workspace/Screenshot.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | 
 | ||||||
|  | import * as Blockly from 'blockly/core'; | ||||||
|  | 
 | ||||||
|  | import { saveAs } from 'file-saver'; | ||||||
|  | 
 | ||||||
|  | import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace'; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | 
 | ||||||
|  | import { faCamera } 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 Screenshot extends Component { | ||||||
|  | 
 | ||||||
|  |   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; | ||||||
|  |       } | ||||||
|  |       .blocklyPathDark { | ||||||
|  |         display: flex; | ||||||
|  |       } | ||||||
|  |       .blocklyPathLight { | ||||||
|  |         display: flex; | ||||||
|  |       }  `;
 | ||||||
|  |       var css = '<defs><style type="text/css" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[' + cssContent + ']]></style></defs>'; | ||||||
|  |       var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox(); | ||||||
|  |       var content = new XMLSerializer().serializeToString(canvas); | ||||||
|  |       var xml = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
 | ||||||
|  |                   width="${bbox.width}" height="${bbox.height}" viewBox="${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}"> | ||||||
|  |                   ${css}">${content}</svg>`; | ||||||
|  |       var fileName = detectWhitespacesAndReturnReadableResult(this.props.name); | ||||||
|  |       // this.props.workspaceName(this.state.name);
 | ||||||
|  |       fileName = `${fileName}.svg` | ||||||
|  |       var blob = new Blob([xml], { type: 'image/svg+xml;base64' }); | ||||||
|  |       saveAs(blob, fileName); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div style={this.props.style}> | ||||||
|  |         <Tooltip title='Screenshot erstellen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.getSvg()} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faCamera} size="xs" /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Screenshot.propTypes = { | ||||||
|  |   name: PropTypes.string.isRequired, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   name: state.workspace.name, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(Screenshot)); | ||||||
							
								
								
									
										154
									
								
								src/components/Workspace/ShareProject.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								src/components/Workspace/ShareProject.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,154 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import { shareProject } from '../../actions/projectActions'; | ||||||
|  | 
 | ||||||
|  | import moment from 'moment'; | ||||||
|  | 
 | ||||||
|  | import Dialog from '../Dialog'; | ||||||
|  | import Snackbar from '../Snackbar'; | ||||||
|  | 
 | ||||||
|  | import { Link } from 'react-router-dom'; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import IconButton from '@material-ui/core/IconButton'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | import Typography from '@material-ui/core/Typography'; | ||||||
|  | 
 | ||||||
|  | import { faShareAlt, faCopy } 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, | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   link: { | ||||||
|  |     color: theme.palette.primary.main, | ||||||
|  |     textDecoration: 'none', | ||||||
|  |     '&:hover': { | ||||||
|  |       color: theme.palette.primary.main, | ||||||
|  |       textDecoration: 'underline' | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class WorkspaceFunc extends Component { | ||||||
|  | 
 | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|  |       snackbar: false, | ||||||
|  |       type: '', | ||||||
|  |       key: '', | ||||||
|  |       message: '', | ||||||
|  |       title: '', | ||||||
|  |       content: '', | ||||||
|  |       open: false, | ||||||
|  |       id: '', | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentDidUpdate(props) { | ||||||
|  |     if(this.props.message !== props.message){ | ||||||
|  |       if(this.props.message.id === 'SHARE_SUCCESS' && (!this.props.multiple || | ||||||
|  |               (this.props.message.status === this.props.project._id || this.props.message.status === this.props.project._id._id))){ | ||||||
|  |         this.setState({ share: true, open: true, title: 'Programm teilen', id: this.props.message.status }); | ||||||
|  |       } | ||||||
|  |       else if(this.props.message.id === 'SHARE_FAIL' && (!this.props.multiple || | ||||||
|  |               (this.props.message.status === this.props.project._id || this.props.message.status === this.props.project._id._id))){ | ||||||
|  |         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Erstellen eines Links zum Teilen deines Programmes. Versuche es noch einmal.`, type: 'error' }); | ||||||
|  |         window.scrollTo(0, 0); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toggleDialog = () => { | ||||||
|  |     this.setState({ open: !this.state, title: '', content: '' }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   shareBlocks = () => { | ||||||
|  |     if(this.props.projectType === 'project' && this.props.project._id._id){ | ||||||
|  |       // project is already shared
 | ||||||
|  |       this.setState({ open: true, title: 'Programm teilen', id: this.props.project._id._id }); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       this.props.shareProject(this.props.name || this.props.project.title, this.props.projectType, this.props.project ? this.props.project._id._id ? this.props.project._id._id : this.props.project._id : undefined); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div style={this.props.style}> | ||||||
|  |         <Tooltip title='Projekt teilen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.shareBlocks()} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faShareAlt} size="xs" /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  | 
 | ||||||
|  |         <Snackbar | ||||||
|  |           open={this.state.snackbar} | ||||||
|  |           message={this.state.message} | ||||||
|  |           type={this.state.type} | ||||||
|  |           key={this.state.key} | ||||||
|  |         /> | ||||||
|  |         <Dialog | ||||||
|  |           open={this.state.open} | ||||||
|  |           title={this.state.title} | ||||||
|  |           content={this.state.content} | ||||||
|  |           onClose={this.toggleDialog} | ||||||
|  |           onClick={this.toggleDialog} | ||||||
|  |           button={'Schließen'} | ||||||
|  |         > | ||||||
|  |           <div style={{ marginTop: '10px' }}> | ||||||
|  |             <Typography>Über den folgenden Link kannst du dein Programm teilen:</Typography> | ||||||
|  |             <Link to={`/share/${this.state.id}`} onClick={() => this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`}</Link> | ||||||
|  |             <Tooltip title='Link kopieren' arrow style={{ marginRight: '5px' }}> | ||||||
|  |               <IconButton | ||||||
|  |                 onClick={() => { | ||||||
|  |                   navigator.clipboard.writeText(`${window.location.origin}/share/${this.state.id}`); | ||||||
|  |                   this.setState({ snackbar: true, key: Date.now(), message: 'Link erfolgreich in Zwischenablage gespeichert.', type: 'success' }); | ||||||
|  |                 }} | ||||||
|  |               > | ||||||
|  |                 <FontAwesomeIcon icon={faCopy} size="xs" /> | ||||||
|  |               </IconButton> | ||||||
|  |             </Tooltip> | ||||||
|  |             {this.props.project && this.props.project._id._id ? | ||||||
|  |               <Typography variant='body2' style={{marginTop: '20px'}}>{`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${ | ||||||
|  |                 moment(this.props.project._id.expiresAt).diff(moment().utc(), 'days') === 0 ? | ||||||
|  |                   moment(this.props.project._id.expiresAt).diff(moment().utc(), 'hours') === 0 ? | ||||||
|  |                     `${moment(this.props.project._id.expiresAt).diff(moment().utc(), 'minutes')} Minuten` | ||||||
|  |                   : `${moment(this.props.project._id.expiresAt).diff(moment().utc(), 'hours')} Stunden` | ||||||
|  |                 : `${moment(this.props.project._id.expiresAt).diff(moment().utc(), 'days')} Tage`} gültig.`}</Typography>
 | ||||||
|  |             : <Typography variant='body2' style={{marginTop: '20px'}}>{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}</Typography>} | ||||||
|  |           </div> | ||||||
|  |         </Dialog> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | WorkspaceFunc.propTypes = { | ||||||
|  |   shareProject: PropTypes.func.isRequired, | ||||||
|  |   name: PropTypes.string.isRequired, | ||||||
|  |   message: PropTypes.object.isRequired | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   name: state.workspace.name, | ||||||
|  |   message: state.message | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, { shareProject })(withStyles(styles, { withTheme: true })(WorkspaceFunc)); | ||||||
							
								
								
									
										94
									
								
								src/components/Workspace/WorkspaceFunc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/components/Workspace/WorkspaceFunc.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | 
 | ||||||
|  | import WorkspaceName from './WorkspaceName'; | ||||||
|  | import SaveProject from './SaveProject'; | ||||||
|  | import Compile from './Compile'; | ||||||
|  | import SolutionCheck from '../Tutorial/SolutionCheck'; | ||||||
|  | import DownloadProject from './DownloadProject'; | ||||||
|  | import OpenProject from './OpenProject'; | ||||||
|  | import Screenshot from './Screenshot'; | ||||||
|  | import ShareProject from './ShareProject'; | ||||||
|  | import ResetWorkspace from './ResetWorkspace'; | ||||||
|  | import DeleteProject from './DeleteProject'; | ||||||
|  | 
 | ||||||
|  | class WorkspaceFunc extends Component { | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div style={{ width: 'max-content', display: 'flex' }}> | ||||||
|  | 
 | ||||||
|  |         {!this.props.assessment ? | ||||||
|  |           <WorkspaceName | ||||||
|  |             style={{marginRight: '5px'}} | ||||||
|  |             multiple={this.props.multiple} | ||||||
|  |             project={this.props.project} | ||||||
|  |             projectType={this.props.projectType} | ||||||
|  |           /> | ||||||
|  |           : null} | ||||||
|  | 
 | ||||||
|  |         {this.props.assessment ? | ||||||
|  |           <SolutionCheck /> | ||||||
|  |         : !this.props.multiple ? | ||||||
|  |             <Compile iconButton /> | ||||||
|  |           : null} | ||||||
|  | 
 | ||||||
|  |         {this.props.user && !this.props.multiple? | ||||||
|  |           <SaveProject | ||||||
|  |             style={{marginRight: '5px'}} | ||||||
|  |             projectType={this.props.projectType} | ||||||
|  |             project={this.props.project} | ||||||
|  |           /> | ||||||
|  |         : null} | ||||||
|  | 
 | ||||||
|  |         {!this.props.multiple ? | ||||||
|  |           <DownloadProject style={{marginRight: '5px'}} /> | ||||||
|  |         : null} | ||||||
|  | 
 | ||||||
|  |         {!this.props.assessment && !this.props.multiple? | ||||||
|  |           <OpenProject | ||||||
|  |             style={{marginRight: '5px'}} | ||||||
|  |             assessment={this.props.assessment} | ||||||
|  |           /> | ||||||
|  |         : null} | ||||||
|  | 
 | ||||||
|  |         {!this.props.assessment && !this.props.multiple? | ||||||
|  |           <Screenshot style={{marginRight: '5px'}} /> | ||||||
|  |         : null} | ||||||
|  | 
 | ||||||
|  |         {this.props.projectType !== 'gallery' && !this.props.assessment ? | ||||||
|  |           <ShareProject | ||||||
|  |             style={{marginRight: '5px'}} | ||||||
|  |             multiple={this.props.multiple} | ||||||
|  |             project={this.props.project} | ||||||
|  |             projectType={this.props.projectType} | ||||||
|  |           /> | ||||||
|  |         :null} | ||||||
|  | 
 | ||||||
|  |         {!this.props.multiple ? | ||||||
|  |           <ResetWorkspace style={this.props.projectType === 'project' || this.props.projectType === 'gallery' ? { marginRight: '5px' }:null} | ||||||
|  |           /> | ||||||
|  |         : null} | ||||||
|  | 
 | ||||||
|  |         {!this.props.assessment && (this.props.projectType === 'project' || this.props.projectType === 'gallery') && this.props.user && this.props.user.email === this.props.project.creator ? | ||||||
|  |           <DeleteProject | ||||||
|  |             project={this.props.project} | ||||||
|  |             projectType={this.props.projectType} | ||||||
|  |           /> | ||||||
|  |         :null} | ||||||
|  | 
 | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | WorkspaceFunc.propTypes = { | ||||||
|  |   user: PropTypes.object | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   user: state.auth.user | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, null)(WorkspaceFunc); | ||||||
							
								
								
									
										150
									
								
								src/components/Workspace/WorkspaceName.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/components/Workspace/WorkspaceName.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,150 @@ | |||||||
|  | import React, { Component } from 'react'; | ||||||
|  | import PropTypes from 'prop-types'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
|  | import { workspaceName } from '../../actions/workspaceActions'; | ||||||
|  | import { setDescription, updateProject } from '../../actions/projectActions'; | ||||||
|  | 
 | ||||||
|  | import Snackbar from '../Snackbar'; | ||||||
|  | import Dialog from '../Dialog'; | ||||||
|  | 
 | ||||||
|  | import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | ||||||
|  | import { withStyles } from '@material-ui/core/styles'; | ||||||
|  | import Button from '@material-ui/core/Button'; | ||||||
|  | import Tooltip from '@material-ui/core/Tooltip'; | ||||||
|  | import TextField from '@material-ui/core/TextField'; | ||||||
|  | import Typography from '@material-ui/core/Typography'; | ||||||
|  | 
 | ||||||
|  | import { faPen } from "@fortawesome/free-solid-svg-icons"; | ||||||
|  | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   workspaceName: { | ||||||
|  |     backgroundColor: theme.palette.secondary.main, | ||||||
|  |     borderRadius: '25px', | ||||||
|  |     display: 'inline-flex', | ||||||
|  |     cursor: 'pointer', | ||||||
|  |     '&:hover': { | ||||||
|  |       color: theme.palette.primary.main, | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class WorkspaceName extends Component { | ||||||
|  | 
 | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.inputRef = React.createRef(); | ||||||
|  |     this.state = { | ||||||
|  |       title: '', | ||||||
|  |       content: '', | ||||||
|  |       open: false, | ||||||
|  |       name: props.name, | ||||||
|  |       description: props.description, | ||||||
|  |       snackbar: false, | ||||||
|  |       type: '', | ||||||
|  |       key: '', | ||||||
|  |       message: '' | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   componentDidUpdate(props) { | ||||||
|  |     if (props.name !== this.props.name) { | ||||||
|  |       this.setState({ name: this.props.name }); | ||||||
|  |     } | ||||||
|  |     if (props.description !== this.props.description) { | ||||||
|  |       this.setState({ description: this.props.description }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   toggleDialog = () => { | ||||||
|  |     this.setState({ open: !this.state, title: '', content: '' }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setFileName = (e) => { | ||||||
|  |     this.setState({ name: e.target.value }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   setDescription = (e) => { | ||||||
|  |     this.setState({ description: e.target.value }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   renameWorkspace = () => { | ||||||
|  |     this.props.workspaceName(this.state.name); | ||||||
|  |     this.toggleDialog(); | ||||||
|  |     if(this.props.projectType === 'project' || this.props.projectType === 'gallery' || this.state.projectType === 'gallery'){ | ||||||
|  |       if(this.props.projectType === 'gallery' || this.state.projectType === 'gallery'){ | ||||||
|  |         this.props.setDescription(this.state.description); | ||||||
|  |       } | ||||||
|  |       if(this.state.projectType === 'gallery'){ | ||||||
|  |         this.saveGallery(); | ||||||
|  |       } else { | ||||||
|  |         this.props.updateProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id); | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       this.setState({ snackbar: true, type: 'success', key: Date.now(), message: `Das Projekt wurde erfolgreich in '${this.state.name}' umbenannt.` }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <div style={this.props.style}> | ||||||
|  |         <Tooltip title={`Titel des Projektes${this.props.name ? `: ${this.props.name}` : ''}`} arrow style={{height: '100%'}}> | ||||||
|  |           <div | ||||||
|  |             className={this.props.classes.workspaceName} | ||||||
|  |             onClick={() => {if(this.props.multiple){this.props.workspaceName(this.props.project.title);if(this.props.projectType === 'gallery'){this.props.setDescription(this.props.project.description);}} this.setState({ open: true, title: this.props.projectType === 'gallery' ? 'Projektdaten ändern': this.props.projectType === 'project' ? 'Projekt umbenennen' : 'Projekt benennen', content: this.props.projectType === 'gallery' ? 'Bitte gib einen Titel und eine Beschreibung für das Galerie-Projekt ein und bestätige die Angaben mit einem Klick auf \'Eingabe\'.':'Bitte gib einen Namen für das Projekt ein und bestätige diesen mit einem Klick auf \'Eingabe\'.' }) }} | ||||||
|  |           > | ||||||
|  |             {this.props.name && !isWidthDown(this.props.projectType === 'project' || this.props.projectType === 'gallery' ? 'xl':'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> | ||||||
|  | 
 | ||||||
|  |         <Snackbar | ||||||
|  |           open={this.state.snackbar} | ||||||
|  |           message={this.state.message} | ||||||
|  |           type={this.state.type} | ||||||
|  |           key={this.state.key} | ||||||
|  |         /> | ||||||
|  |         <Dialog | ||||||
|  |           open={this.state.open} | ||||||
|  |           title={this.state.title} | ||||||
|  |           content={this.state.content} | ||||||
|  |           onClose={() => {this.toggleDialog(); this.setState({ name: this.props.name, description: this.props.description });}} | ||||||
|  |           onClick={() => {this.toggleDialog(); this.setState({ name: this.props.name, description: this.props.description });}} | ||||||
|  |           button={'Abbrechen'} | ||||||
|  |         > | ||||||
|  |           <div style={{ marginTop: '10px' }}> | ||||||
|  |             {this.props.projectType === 'gallery' || this.state.projectType === 'gallery' ? | ||||||
|  |               <div> | ||||||
|  |                 <TextField autoFocus placeholder={this.state.saveXml ? 'Dateiname' : 'Projekttitel'} value={this.state.name} onChange={this.setFileName} style={{marginBottom: '10px'}}/> | ||||||
|  |                 <TextField fullWidth multiline placeholder={'Projektbeschreibung'} value={this.state.description} onChange={this.setDescription} style={{ marginBottom: '10px' }} /> | ||||||
|  |               </div> | ||||||
|  |             : <TextField autoFocus placeholder={this.state.saveXml ? 'Dateiname' : 'Projekttitel'} value={this.state.name} onChange={this.setFileName} style={{ marginRight: '10px' }} />} | ||||||
|  |             <Button disabled={!this.state.name} variant='contained' color='primary' onClick={() => { this.renameWorkspace(); this.toggleDialog(); }}>Eingabe</Button> | ||||||
|  |           </div> | ||||||
|  |         </Dialog> | ||||||
|  |       </div> | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | WorkspaceName.propTypes = { | ||||||
|  |   workspaceName: PropTypes.func.isRequired, | ||||||
|  |   setDescription: PropTypes.func.isRequired, | ||||||
|  |   updateProject: PropTypes.func.isRequired, | ||||||
|  |   name: PropTypes.string.isRequired, | ||||||
|  |   description: PropTypes.string.isRequired, | ||||||
|  |   message: PropTypes.object.isRequired, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = state => ({ | ||||||
|  |   name: state.workspace.name, | ||||||
|  |   description: state.project.description, | ||||||
|  |   message: state.message, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, { workspaceName, setDescription, updateProject })(withStyles(styles, { withTheme: true })(withWidth()(WorkspaceName))); | ||||||
| @ -1,480 +0,0 @@ | |||||||
| import React, { Component } from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| import { clearStats, onChangeCode, workspaceName } from '../actions/workspaceActions'; |  | ||||||
| import { updateProject, deleteProject, shareProject, setDescription } from '../actions/projectActions'; |  | ||||||
| 
 |  | ||||||
| import * as Blockly from 'blockly/core'; |  | ||||||
| 
 |  | ||||||
| import { withRouter } from 'react-router-dom'; |  | ||||||
| import axios from 'axios'; |  | ||||||
| import moment from 'moment'; |  | ||||||
| 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 Snackbar from './Snackbar'; |  | ||||||
| import Dialog from './Dialog'; |  | ||||||
| 
 |  | ||||||
| import { Link } from 'react-router-dom'; |  | ||||||
| 
 |  | ||||||
| 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, faFileDownload, faTrashAlt, faCamera, faShare, faShareAlt, faCopy } 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, |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   buttonTrash: { |  | ||||||
|     backgroundColor: theme.palette.error.dark, |  | ||||||
|     color: theme.palette.primary.contrastText, |  | ||||||
|     width: '40px', |  | ||||||
|     height: '40px', |  | ||||||
|     '&:hover': { |  | ||||||
|       backgroundColor: theme.palette.error.dark, |  | ||||||
|       color: theme.palette.primary.contrastText, |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   link: { |  | ||||||
|     color: theme.palette.primary.main, |  | ||||||
|     textDecoration: 'none', |  | ||||||
|     '&:hover': { |  | ||||||
|       color: theme.palette.primary.main, |  | ||||||
|       textDecoration: 'underline' |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class WorkspaceFunc extends Component { |  | ||||||
| 
 |  | ||||||
|   constructor(props) { |  | ||||||
|     super(props); |  | ||||||
|     this.inputRef = React.createRef(); |  | ||||||
|     this.state = { |  | ||||||
|       title: '', |  | ||||||
|       content: '', |  | ||||||
|       open: false, |  | ||||||
|       file: false, |  | ||||||
|       saveFile: false, |  | ||||||
|       share: false, |  | ||||||
|       name: props.name, |  | ||||||
|       description: props.description, |  | ||||||
|       snackbar: false, |  | ||||||
|       type: '', |  | ||||||
|       key: '', |  | ||||||
|       message: '', |  | ||||||
|       id: '' |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   componentDidUpdate(props) { |  | ||||||
|     if (props.name !== this.props.name) { |  | ||||||
|       this.setState({ name: this.props.name }); |  | ||||||
|     } |  | ||||||
|     if (props.description !== this.props.description) { |  | ||||||
|       this.setState({ description: this.props.description }); |  | ||||||
|     } |  | ||||||
|     if(this.props.message !== props.message){ |  | ||||||
|       if(this.props.message.id === 'PROJECT_UPDATE_SUCCESS'){ |  | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: `Das Projekt wurde erfolgreich aktualisiert.`, type: 'success' }); |  | ||||||
|       } |  | ||||||
|       if(this.props.message.id === 'GALLERY_UPDATE_SUCCESS'){ |  | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: `Das Galerie-Projekt wurde erfolgreich aktualisiert.`, type: 'success' }); |  | ||||||
|       } |  | ||||||
|       else if(this.props.message.id === 'PROJECT_DELETE_SUCCESS'){ |  | ||||||
|         this.props.history.push(`/${this.props.projectType}`); |  | ||||||
|       } |  | ||||||
|       else if(this.props.message.id === 'PROJECT_UPDATE_FAIL'){ |  | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Aktualisieren des Projektes. Versuche es noch einmal.`, type: 'error' }); |  | ||||||
|       } |  | ||||||
|       else if(this.props.message.id === 'PROJECT_DELETE_FAIL'){ |  | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Löschen des Projektes. Versuche es noch einmal.`, type: 'error' }); |  | ||||||
|       } |  | ||||||
|       else if(this.props.message.id === 'SHARE_SUCCESS' && (!this.props.multiple || |  | ||||||
|               (this.props.message.status === this.props.project._id || this.props.message.status === this.props.project._id._id))){ |  | ||||||
|         this.setState({ share: true, open: true, title: 'Programm teilen', id: this.props.message.status }); |  | ||||||
|       } |  | ||||||
|       else if(this.props.message.id === 'SHARE_FAIL' && (!this.props.multiple || |  | ||||||
|               (this.props.message.status === this.props.project._id || this.props.message.status === this.props.project._id._id))){ |  | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Erstellen eines Links zum Teilen deines Programmes. Versuche es noch einmal.`, type: 'error' }); |  | ||||||
|         window.scrollTo(0, 0); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   toggleDialog = () => { |  | ||||||
|     this.setState({ open: !this.state, share: false, file: false, saveFile: false, title: '', content: '' }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   saveProject = () => { |  | ||||||
|     var body = { |  | ||||||
|       xml: this.props.xml, |  | ||||||
|       title: this.props.name |  | ||||||
|     }; |  | ||||||
|     axios.post(`${process.env.REACT_APP_BLOCKLY_API}/project`, body) |  | ||||||
|       .then(res => { |  | ||||||
|         var project = res.data.project; |  | ||||||
|         this.props.history.push(`/project/${project._id}`); |  | ||||||
|       }) |  | ||||||
|       .catch(err => { |  | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Speichern des Projektes. Versuche es noch einmal.`, type: 'error' }); |  | ||||||
|         window.scrollTo(0, 0); |  | ||||||
|       }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   downloadXmlFile = () => { |  | ||||||
|     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); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   shareBlocks = () => { |  | ||||||
|     if(this.props.projectType === 'project' && this.props.project._id._id){ |  | ||||||
|       // project is already shared
 |  | ||||||
|       this.setState({ share: true, open: true, title: 'Programm teilen', id: this.props.project._id._id }); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|       this.props.shareProject(this.state.name || this.props.project.title, this.props.projectType, this.props.project ? this.props.project._id._id ? this.props.project._id._id : this.props.project._id : undefined); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   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; |  | ||||||
|       } |  | ||||||
|       .blocklyPathDark { |  | ||||||
|         display: flex; |  | ||||||
|       } |  | ||||||
|       .blocklyPathLight { |  | ||||||
|         display: flex; |  | ||||||
|       }  `;
 |  | ||||||
| 
 |  | ||||||
|       var css = '<defs><style type="text/css" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[' + cssContent + ']]></style></defs>'; |  | ||||||
| 
 |  | ||||||
|       var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox(); |  | ||||||
|       var content = new XMLSerializer().serializeToString(canvas); |  | ||||||
| 
 |  | ||||||
|       var xml = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
 |  | ||||||
|                   width="${bbox.width}" height="${bbox.height}" viewBox="${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}"> |  | ||||||
|                   ${css}">${content}</svg>`; |  | ||||||
|       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.downloadXmlFile() : this.getSvg() |  | ||||||
|       } |  | ||||||
|       else { |  | ||||||
|         this.setState({ saveFile: true, file: filetype, open: true, title: this.state.file === 'xml' ? 'Projekt herunterladen' : '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 }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setDescription = (e) => { |  | ||||||
|     this.setState({ description: 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.assessment) { |  | ||||||
|               var extensionPosition = xmlFile.name.lastIndexOf('.'); |  | ||||||
|               this.props.workspaceName(xmlFile.name.substr(0, extensionPosition)); |  | ||||||
|             } |  | ||||||
|             this.setState({ snackbar: true, type: 'success', 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(); |  | ||||||
|     console.log(this.props.projectType); |  | ||||||
|     if(this.props.projectType === 'project' || this.props.projectType === 'gallery'){ |  | ||||||
|       if(this.props.projectType === 'gallery'){ |  | ||||||
|         this.props.setDescription(this.state.description); |  | ||||||
|       } |  | ||||||
|       this.props.updateProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id); |  | ||||||
|     } else { |  | ||||||
|       this.setState({ snackbar: true, type: 'success', 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.assessment) { |  | ||||||
|       this.props.workspaceName(null); |  | ||||||
|     } |  | ||||||
|     this.setState({ snackbar: true, type: 'success', key: Date.now(), message: 'Das Projekt wurde erfolgreich zurückgesetzt.' }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   render() { |  | ||||||
|     return ( |  | ||||||
|       <div style={{ width: 'max-content', display: 'flex' }}> |  | ||||||
|         {!this.props.assessment ? |  | ||||||
|           <Tooltip title={`Titel des Projektes${this.props.name ? `: ${this.props.name}` : ''}`} arrow style={{ marginRight: '5px' }}> |  | ||||||
|             <div className={this.props.classes.workspaceName} onClick={() => {if(this.props.multiple){this.props.workspaceName(this.props.project.title);if(this.props.projectType === 'gallery'){this.props.setDescription(this.props.project.description);}} this.setState({ file: true, open: true, saveFile: false, title: this.props.projectType === 'gallery' ? 'Projektdaten eintragen':'Projekt benennen', content: this.props.projectType === 'gallery' ? 'Bitte gib einen Titel und eine Beschreibung für das Galerie-Projekt ein und bestätige die Angaben mit einem Klick auf \'Eingabe\'.':'Bitte gib einen Namen für das Projekt ein und bestätige diesen mit einem Klick auf \'Eingabe\'.' }) }}> |  | ||||||
|               {this.props.name && !isWidthDown(this.props.projectType === 'project' || this.props.projectType === 'gallery' ? 'xl':'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.assessment ? <SolutionCheck /> : !this.props.multiple ? <Compile iconButton /> : null} |  | ||||||
|         {this.props.user && !this.props.multiple? |  | ||||||
|           <Tooltip title={this.props.projectType === 'project'? 'Projekt aktualisieren':'Projekt speichern'} arrow style={{ marginRight: '5px' }}> |  | ||||||
|             <IconButton |  | ||||||
|               className={this.props.classes.button} |  | ||||||
|               onClick={this.props.projectType === 'project' ? () => this.props.updateProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id) : () => this.saveProject()} |  | ||||||
|             > |  | ||||||
|               <FontAwesomeIcon icon={faSave} size="xs" /> |  | ||||||
|             </IconButton> |  | ||||||
|           </Tooltip> |  | ||||||
|         : null} |  | ||||||
|         {!this.props.multiple ? |  | ||||||
|           <Tooltip title='Projekt herunterladen' arrow style={{ marginRight: '5px' }}> |  | ||||||
|             <IconButton |  | ||||||
|               className={this.props.classes.button} |  | ||||||
|               onClick={() => { this.createFileName('xml'); }} |  | ||||||
|             > |  | ||||||
|               <FontAwesomeIcon icon={faFileDownload} size="xs" /> |  | ||||||
|             </IconButton> |  | ||||||
|           </Tooltip> |  | ||||||
|         : null} |  | ||||||
|         {!this.props.assessment && !this.props.multiple? |  | ||||||
|           <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='Projekt ö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> |  | ||||||
|         : null} |  | ||||||
|         {!this.props.assessment && !this.props.multiple? |  | ||||||
|           <Tooltip title='Screenshot erstellen' arrow style={{ marginRight: '5px' }}> |  | ||||||
|             <IconButton |  | ||||||
|               className={this.props.classes.button} |  | ||||||
|               onClick={() => { this.createFileName('svg'); }} |  | ||||||
|             > |  | ||||||
|               <FontAwesomeIcon icon={faCamera} size="xs" /> |  | ||||||
|             </IconButton> |  | ||||||
|           </Tooltip> |  | ||||||
|         : null} |  | ||||||
|         {this.props.projectType !== 'gallery' && !this.props.assessment ? |  | ||||||
|           <Tooltip title='Projekt teilen' arrow style={{marginRight: '5px'}}> |  | ||||||
|             <IconButton |  | ||||||
|               className={this.props.classes.button} |  | ||||||
|               onClick={() => this.shareBlocks()} |  | ||||||
|             > |  | ||||||
|               <FontAwesomeIcon icon={faShareAlt} size="xs" /> |  | ||||||
|             </IconButton> |  | ||||||
|           </Tooltip> |  | ||||||
|         :null} |  | ||||||
|         {!this.props.multiple ? |  | ||||||
|           <Tooltip title='Workspace zurücksetzen' arrow style={this.props.projectType === 'project' || this.props.projectType === 'gallery' ? { marginRight: '5px' }:null}> |  | ||||||
|             <IconButton |  | ||||||
|               className={this.props.classes.button} |  | ||||||
|               onClick={() => this.resetWorkspace()} |  | ||||||
|             > |  | ||||||
|               <FontAwesomeIcon icon={faShare} size="xs" flip='horizontal' /> |  | ||||||
|             </IconButton> |  | ||||||
|           </Tooltip> |  | ||||||
|         : null} |  | ||||||
|         {!this.props.assessment && (this.props.projectType === 'project' || this.props.projectType === 'gallery') ? |  | ||||||
|           <Tooltip title='Projekt löschen' arrow> |  | ||||||
|             <IconButton |  | ||||||
|               className={this.props.classes.buttonTrash} |  | ||||||
|               onClick={() => this.props.deleteProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} |  | ||||||
|             > |  | ||||||
|               <FontAwesomeIcon icon={faTrashAlt} size="xs" /> |  | ||||||
|             </IconButton> |  | ||||||
|           </Tooltip> |  | ||||||
|         :null} |  | ||||||
| 
 |  | ||||||
|         <Dialog |  | ||||||
|           open={this.state.open} |  | ||||||
|           title={this.state.title} |  | ||||||
|           content={this.state.content} |  | ||||||
|           onClose={this.toggleDialog} |  | ||||||
|           onClick={this.state.file ? () => { this.toggleDialog(); this.setState({ name: this.props.name }) } : this.toggleDialog} |  | ||||||
|           button={this.state.file ? 'Abbrechen' : 'Schließen'} |  | ||||||
|         > |  | ||||||
|           {this.state.file ? |  | ||||||
|             <div style={{ marginTop: '10px' }}> |  | ||||||
|               {this.props.projectType === 'gallery' ? |  | ||||||
|                 <div> |  | ||||||
|                   <TextField autoFocus placeholder={this.state.saveXml ? 'Dateiname' : 'Projekttitel'} value={this.state.name} onChange={this.setFileName} style={{marginBottom: '10px'}}/> |  | ||||||
|                   <TextField fullWidth multiline placeholder={'Projektbeschreibung'} value={this.state.description} onChange={this.setDescription} style={{ marginBottom: '10px' }} /> |  | ||||||
|                 </div> |  | ||||||
|               : <TextField autoFocus placeholder={this.state.saveXml ? 'Dateiname' : 'Projekttitel'} value={this.state.name} onChange={this.setFileName} style={{ marginRight: '10px' }} />} |  | ||||||
|               <Button disabled={!this.state.name} variant='contained' color='primary' onClick={() => { this.state.saveFile ? this.state.file === 'xml' ? this.downloadXmlFile() : this.getSvg() : this.renameWorkspace(); this.toggleDialog(); }}>Eingabe</Button> |  | ||||||
|             </div> |  | ||||||
|           : this.state.share ? |  | ||||||
|             <div style={{ marginTop: '10px' }}> |  | ||||||
|               <Typography>Über den folgenden Link kannst du dein Programm teilen:</Typography> |  | ||||||
|               <Link to={`/share/${this.state.id}`} onClick={() => this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`}</Link> |  | ||||||
|               <Tooltip title='Link kopieren' arrow style={{ marginRight: '5px' }}> |  | ||||||
|                 <IconButton |  | ||||||
|                   onClick={() => { |  | ||||||
|                     navigator.clipboard.writeText(`${window.location.origin}/share/${this.state.id}`); |  | ||||||
|                     this.setState({ snackbar: true, key: Date.now(), message: 'Link erfolgreich in Zwischenablage gespeichert.', type: 'success' }); |  | ||||||
|                   }} |  | ||||||
|                 > |  | ||||||
|                   <FontAwesomeIcon icon={faCopy} size="xs" /> |  | ||||||
|                 </IconButton> |  | ||||||
|               </Tooltip> |  | ||||||
|               {this.props.project && this.props.project._id._id ? |  | ||||||
|                 <Typography variant='body2' style={{marginTop: '20px'}}>{`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${ |  | ||||||
|                   moment(this.props.project._id.expiresAt).diff(moment().utc(), 'days') === 0 ? |  | ||||||
|                     moment(this.props.project._id.expiresAt).diff(moment().utc(), 'hours') === 0 ? |  | ||||||
|                       `${moment(this.props.project._id.expiresAt).diff(moment().utc(), 'minutes')} Minuten` |  | ||||||
|                     : `${moment(this.props.project._id.expiresAt).diff(moment().utc(), 'hours')} Stunden` |  | ||||||
|                   : `${moment(this.props.project._id.expiresAt).diff(moment().utc(), 'days')} Tage`} gültig.`}</Typography>
 |  | ||||||
|               : <Typography variant='body2' style={{marginTop: '20px'}}>{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}</Typography>} |  | ||||||
|             </div> |  | ||||||
|           : null} |  | ||||||
|         </Dialog> |  | ||||||
| 
 |  | ||||||
|         <Snackbar |  | ||||||
|           open={this.state.snackbar} |  | ||||||
|           message={this.state.message} |  | ||||||
|           type={this.state.type} |  | ||||||
|           key={this.state.key} |  | ||||||
|         /> |  | ||||||
| 
 |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| WorkspaceFunc.propTypes = { |  | ||||||
|   clearStats: PropTypes.func.isRequired, |  | ||||||
|   onChangeCode: PropTypes.func.isRequired, |  | ||||||
|   workspaceName: PropTypes.func.isRequired, |  | ||||||
|   updateProject: PropTypes.func.isRequired, |  | ||||||
|   shareProject: PropTypes.func.isRequired, |  | ||||||
|   deleteProject: PropTypes.func.isRequired, |  | ||||||
|   setDescription: PropTypes.func.isRequired, |  | ||||||
|   arduino: PropTypes.string.isRequired, |  | ||||||
|   xml: PropTypes.string.isRequired, |  | ||||||
|   name: PropTypes.string.isRequired, |  | ||||||
|   description: PropTypes.string.isRequired, |  | ||||||
|   message: PropTypes.object.isRequired, |  | ||||||
|   user: PropTypes.object |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const mapStateToProps = state => ({ |  | ||||||
|   arduino: state.workspace.code.arduino, |  | ||||||
|   xml: state.workspace.code.xml, |  | ||||||
|   name: state.workspace.name, |  | ||||||
|   description: state.project.description, |  | ||||||
|   message: state.message, |  | ||||||
|   user: state.auth.user |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export default connect(mapStateToProps, { clearStats, onChangeCode, workspaceName, updateProject, shareProject, deleteProject, setDescription })(withStyles(styles, { withTheme: true })(withWidth()(withRouter(WorkspaceFunc)))); |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user