From e6813ba2d31a227755abc2ef04baff36b3c6eff7 Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Tue, 8 Dec 2020 13:53:43 +0100 Subject: [PATCH] create modular structure of WorkspaceFunc component --- src/actions/projectActions.js | 10 +- src/components/Home.js | 6 +- src/components/Project/ProjectHome.js | 4 +- src/components/Tutorial/Assessment.js | 2 +- src/components/Tutorial/SolutionCheck.js | 2 +- src/components/{ => Workspace}/Compile.js | 6 +- src/components/Workspace/DeleteProject.js | 89 ++++ src/components/Workspace/DownloadProject.js | 66 +++ src/components/Workspace/OpenProject.js | 143 ++++++ src/components/Workspace/ResetWorkspace.js | 95 ++++ src/components/Workspace/SaveProject.js | 200 ++++++++ src/components/Workspace/Screenshot.js | 97 ++++ src/components/Workspace/ShareProject.js | 154 ++++++ .../{ => Workspace}/TrashcanButtons.js | 0 src/components/Workspace/WorkspaceFunc.js | 94 ++++ src/components/Workspace/WorkspaceName.js | 150 ++++++ .../{ => Workspace}/WorkspaceStats.js | 0 src/components/WorkspaceFunc.js | 480 ------------------ 18 files changed, 1105 insertions(+), 493 deletions(-) rename src/components/{ => Workspace}/Compile.js (96%) create mode 100644 src/components/Workspace/DeleteProject.js create mode 100644 src/components/Workspace/DownloadProject.js create mode 100644 src/components/Workspace/OpenProject.js create mode 100644 src/components/Workspace/ResetWorkspace.js create mode 100644 src/components/Workspace/SaveProject.js create mode 100644 src/components/Workspace/Screenshot.js create mode 100644 src/components/Workspace/ShareProject.js rename src/components/{ => Workspace}/TrashcanButtons.js (100%) create mode 100644 src/components/Workspace/WorkspaceFunc.js create mode 100644 src/components/Workspace/WorkspaceName.js rename src/components/{ => Workspace}/WorkspaceStats.js (100%) delete mode 100644 src/components/WorkspaceFunc.js diff --git a/src/actions/projectActions.js b/src/actions/projectActions.js index a41e685..344140b 100644 --- a/src/actions/projectActions.js +++ b/src/actions/projectActions.js @@ -76,7 +76,7 @@ export const updateProject = (type, id) => (dispatch, getState) => { var body = { xml: workspace.code.xml, title: workspace.name - } + }; var project = getState().project; if(type==='gallery'){ body.description = project.description; @@ -99,10 +99,14 @@ export const updateProject = (type, id) => (dispatch, getState) => { }) .catch(err => { if(err.response){ - dispatch(returnErrors(err.response.data.message, err.response.status, 'PROJECT_UPDATE_FAIL')); + if(type === 'project'){ + 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) => { var project = getState().project; diff --git a/src/components/Home.js b/src/components/Home.js index bade15d..7c385ac 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -6,11 +6,11 @@ import { clearStats, workspaceName } from '../actions/workspaceActions'; import * as Blockly from 'blockly/core'; import { createNameId } from 'mnemonic-id'; -import WorkspaceStats from './WorkspaceStats'; -import WorkspaceFunc from './WorkspaceFunc'; +import WorkspaceStats from './Workspace/WorkspaceStats'; +import WorkspaceFunc from './Workspace/WorkspaceFunc'; import BlocklyWindow from './Blockly/BlocklyWindow'; import CodeViewer from './CodeViewer'; -import TrashcanButtons from './TrashcanButtons'; +import TrashcanButtons from './Workspace/TrashcanButtons'; import HintTutorialExists from './Tutorial/HintTutorialExists'; import Snackbar from './Snackbar'; diff --git a/src/components/Project/ProjectHome.js b/src/components/Project/ProjectHome.js index b373f02..66f8f0a 100644 --- a/src/components/Project/ProjectHome.js +++ b/src/components/Project/ProjectHome.js @@ -10,7 +10,7 @@ import { Link, withRouter } from 'react-router-dom'; import Breadcrumbs from '../Breadcrumbs'; import BlocklyWindow from '../Blockly/BlocklyWindow'; import Snackbar from '../Snackbar'; -import WorkspaceFunc from '../WorkspaceFunc'; +import WorkspaceFunc from '../Workspace/WorkspaceFunc'; import { withStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; @@ -104,7 +104,7 @@ class ProjectHome extends Component { blockDisabled initialXml={project.xml} /> - {project.description} + {project.description} {this.props.user && this.props.user.email === project.creator ?
diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js index be845ee..75e180d 100644 --- a/src/components/Tutorial/Assessment.js +++ b/src/components/Tutorial/Assessment.js @@ -5,7 +5,7 @@ import { workspaceName } from '../../actions/workspaceActions'; import BlocklyWindow from '../Blockly/BlocklyWindow'; import CodeViewer from '../CodeViewer'; -import WorkspaceFunc from '../WorkspaceFunc'; +import WorkspaceFunc from '../Workspace/WorkspaceFunc'; import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; import Grid from '@material-ui/core/Grid'; diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index 37cafa7..5728ef1 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -5,7 +5,7 @@ import { tutorialCheck, tutorialStep } from '../../actions/tutorialActions'; import { withRouter } from 'react-router-dom'; -import Compile from '../Compile'; +import Compile from '../Workspace/Compile'; import Dialog from '../Dialog'; import { checkXml } from '../../helpers/compareXml'; diff --git a/src/components/Compile.js b/src/components/Workspace/Compile.js similarity index 96% rename from src/components/Compile.js rename to src/components/Workspace/Compile.js index 7fe5932..89b081e 100644 --- a/src/components/Compile.js +++ b/src/components/Workspace/Compile.js @@ -1,11 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; 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 Button from '@material-ui/core/Button'; diff --git a/src/components/Workspace/DeleteProject.js b/src/components/Workspace/DeleteProject.js new file mode 100644 index 0000000..70b617a --- /dev/null +++ b/src/components/Workspace/DeleteProject.js @@ -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 ( +
+ + this.props.deleteProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} + > + + + + + +
+ ); + }; +} + +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))); diff --git a/src/components/Workspace/DownloadProject.js b/src/components/Workspace/DownloadProject.js new file mode 100644 index 0000000..9b30273 --- /dev/null +++ b/src/components/Workspace/DownloadProject.js @@ -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 ( +
+ + this.downloadXmlFile()} + > + + + +
+ ); + }; +} + +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)); diff --git a/src/components/Workspace/OpenProject.js b/src/components/Workspace/OpenProject.js new file mode 100644 index 0000000..6038a49 --- /dev/null +++ b/src/components/Workspace/OpenProject.js @@ -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 ( +
+
+ { this.uploadXmlFile(e.target.files[0]) }} + id="open-blocks" + type="file" + /> + +
+ + + +
+ ); + }; +} + +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)); diff --git a/src/components/Workspace/ResetWorkspace.js b/src/components/Workspace/ResetWorkspace.js new file mode 100644 index 0000000..0c36299 --- /dev/null +++ b/src/components/Workspace/ResetWorkspace.js @@ -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 ( +
+ + this.resetWorkspace()} + > + + + + + +
+ ); + }; +} + +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)); diff --git a/src/components/Workspace/SaveProject.js b/src/components/Workspace/SaveProject.js new file mode 100644 index 0000000..91bc17a --- /dev/null +++ b/src/components/Workspace/SaveProject.js @@ -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 ( +
+ + 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())}} + > + + + + + {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'} + + {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'} + + + + + {this.toggleDialog(); this.setState({ description: this.props.description });}} + onClick={() => {this.toggleDialog(); this.setState({ description: this.props.description });}} + button={'Abbrechen'} + > +
+ + +
+
+
+ ); + }; +} + +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))); diff --git a/src/components/Workspace/Screenshot.js b/src/components/Workspace/Screenshot.js new file mode 100644 index 0000000..c150db2 --- /dev/null +++ b/src/components/Workspace/Screenshot.js @@ -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 = ''; + var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox(); + var content = new XMLSerializer().serializeToString(canvas); + var xml = ` + ${css}">${content}`; + 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 ( +
+ + this.getSvg()} + > + + + +
+ ); + }; +} + +Screenshot.propTypes = { + name: PropTypes.string.isRequired, +}; + +const mapStateToProps = state => ({ + name: state.workspace.name, +}); + +export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(Screenshot)); diff --git a/src/components/Workspace/ShareProject.js b/src/components/Workspace/ShareProject.js new file mode 100644 index 0000000..f4ca60d --- /dev/null +++ b/src/components/Workspace/ShareProject.js @@ -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 ( +
+ + this.shareBlocks()} + > + + + + + + +
+ Über den folgenden Link kannst du dein Programm teilen: + this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`} + + { + 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' }); + }} + > + + + + {this.props.project && this.props.project._id._id ? + {`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.`} + : {`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}} +
+
+
+ ); + }; +} + +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)); diff --git a/src/components/TrashcanButtons.js b/src/components/Workspace/TrashcanButtons.js similarity index 100% rename from src/components/TrashcanButtons.js rename to src/components/Workspace/TrashcanButtons.js diff --git a/src/components/Workspace/WorkspaceFunc.js b/src/components/Workspace/WorkspaceFunc.js new file mode 100644 index 0000000..8c8ae31 --- /dev/null +++ b/src/components/Workspace/WorkspaceFunc.js @@ -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 ( +
+ + {!this.props.assessment ? + + : null} + + {this.props.assessment ? + + : !this.props.multiple ? + + : null} + + {this.props.user && !this.props.multiple? + + : null} + + {!this.props.multiple ? + + : null} + + {!this.props.assessment && !this.props.multiple? + + : null} + + {!this.props.assessment && !this.props.multiple? + + : null} + + {this.props.projectType !== 'gallery' && !this.props.assessment ? + + :null} + + {!this.props.multiple ? + + : null} + + {!this.props.assessment && (this.props.projectType === 'project' || this.props.projectType === 'gallery') && this.props.user && this.props.user.email === this.props.project.creator ? + + :null} + +
+ ); + }; +} + +WorkspaceFunc.propTypes = { + user: PropTypes.object +}; + +const mapStateToProps = state => ({ + user: state.auth.user +}); + +export default connect(mapStateToProps, null)(WorkspaceFunc); diff --git a/src/components/Workspace/WorkspaceName.js b/src/components/Workspace/WorkspaceName.js new file mode 100644 index 0000000..33d94ab --- /dev/null +++ b/src/components/Workspace/WorkspaceName.js @@ -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 ( +
+ +
{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) ? + {this.props.name} + : null} +
+ +
+
+
+ + + {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'} + > +
+ {this.props.projectType === 'gallery' || this.state.projectType === 'gallery' ? +
+ + +
+ : } + +
+
+
+ ); + }; +} + +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))); diff --git a/src/components/WorkspaceStats.js b/src/components/Workspace/WorkspaceStats.js similarity index 100% rename from src/components/WorkspaceStats.js rename to src/components/Workspace/WorkspaceStats.js diff --git a/src/components/WorkspaceFunc.js b/src/components/WorkspaceFunc.js deleted file mode 100644 index f11e620..0000000 --- a/src/components/WorkspaceFunc.js +++ /dev/null @@ -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 = ''; - - var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox(); - var content = new XMLSerializer().serializeToString(canvas); - - var xml = ` - ${css}">${content}`; - var fileName = detectWhitespacesAndReturnReadableResult(this.state.name); - this.props.workspaceName(this.state.name); - fileName = `${fileName}.svg` - var blob = new Blob([xml], { type: 'image/svg+xml;base64' }); - saveAs(blob, fileName); - } - } - - createFileName = (filetype) => { - this.setState({ file: filetype }, () => { - if (this.state.name) { - this.state.file === 'xml' ? this.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 ( -
- {!this.props.assessment ? - -
{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) ? {this.props.name} : null} -
- -
-
-
- : null} - {this.props.assessment ? : !this.props.multiple ? : null} - {this.props.user && !this.props.multiple? - - this.props.updateProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id) : () => this.saveProject()} - > - - - - : null} - {!this.props.multiple ? - - { this.createFileName('xml'); }} - > - - - - : null} - {!this.props.assessment && !this.props.multiple? -
- { this.uploadXmlFile(e.target.files[0]) }} - id="open-blocks" - type="file" - /> - -
- : null} - {!this.props.assessment && !this.props.multiple? - - { this.createFileName('svg'); }} - > - - - - : null} - {this.props.projectType !== 'gallery' && !this.props.assessment ? - - this.shareBlocks()} - > - - - - :null} - {!this.props.multiple ? - - this.resetWorkspace()} - > - - - - : null} - {!this.props.assessment && (this.props.projectType === 'project' || this.props.projectType === 'gallery') ? - - this.props.deleteProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} - > - - - - :null} - - { this.toggleDialog(); this.setState({ name: this.props.name }) } : this.toggleDialog} - button={this.state.file ? 'Abbrechen' : 'Schließen'} - > - {this.state.file ? -
- {this.props.projectType === 'gallery' ? -
- - -
- : } - -
- : this.state.share ? -
- Über den folgenden Link kannst du dein Programm teilen: - this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`} - - { - 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' }); - }} - > - - - - {this.props.project && this.props.project._id._id ? - {`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.`} - : {`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}} -
- : null} -
- - - -
- ); - }; -} - -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))));