diff --git a/src/actions/projectActions.js b/src/actions/projectActions.js index 94909ef..a41e685 100644 --- a/src/actions/projectActions.js +++ b/src/actions/projectActions.js @@ -71,24 +71,25 @@ export const getProjects = (type) => (dispatch) => { }); }; -export const updateProject = () => (dispatch, getState) => { +export const updateProject = (type, id) => (dispatch, getState) => { var workspace = getState().workspace; var body = { xml: workspace.code.xml, title: workspace.name } var project = getState().project; - var id = project.projects[0]._id._id ? project.projects[0]._id._id : project.projects[0]._id; - var type = project.type; if(type==='gallery'){ body.description = project.description; } axios.put(`${process.env.REACT_APP_BLOCKLY_API}/${type}/${id}`, body) .then(res => { var project = res.data[type]; + var projects = getState().project.projects; + var index = projects.findIndex(res => res._id === project._id); + projects[index] = project; dispatch({ - type: GET_PROJECT, - payload: project + type: GET_PROJECTS, + payload: projects }); if(type === 'project'){ dispatch(returnSuccess(res.data.message, res.status, 'PROJECT_UPDATE_SUCCESS')); @@ -103,13 +104,17 @@ export const updateProject = () => (dispatch, getState) => { }); } -export const deleteProject = () => (dispatch, getState) => { +export const deleteProject = (type, id) => (dispatch, getState) => { var project = getState().project; - var id = project.projects[0]._id._id ? project.projects[0]._id._id : project.projects[0]._id; - var type = project.type; axios.delete(`${process.env.REACT_APP_BLOCKLY_API}/${type}/${id}`) .then(res => { - dispatch({type: GET_PROJECTS, payload: []}); + var projects = getState().project.projects; + var index = projects.findIndex(res => res._id === id || res._id._id === id); + projects.splice(index, 1) + dispatch({ + type: GET_PROJECTS, + payload: projects + }); if(type === 'project'){ dispatch(returnSuccess(res.data.message, res.status, 'PROJECT_DELETE_SUCCESS')); } else { @@ -121,7 +126,41 @@ export const deleteProject = () => (dispatch, getState) => { dispatch(returnErrors(err.response.data.message, err.response.status, 'PROJECT_DELETE_FAIL')); } }); -} +}; + + +export const shareProject = (title, type, id) => (dispatch, getState) => { + var body = { + title: title + }; + if(type === 'project'){ + body.projectId = id; + } else { + body.xml = getState().workspace.code.xml; + } + axios.post(`${process.env.REACT_APP_BLOCKLY_API}/share`, body) + .then(res => { + var shareContent = res.data.content; + if(body.projectId){ + var projects = getState().project.projects; + var index = projects.findIndex(res => res._id === id); + projects[index]._id = { + _id: shareContent._id, + expiresAt: shareContent.expiresAt + }; + dispatch({ + type: GET_PROJECTS, + payload: projects + }); + } + dispatch(returnSuccess(res.data.message, shareContent._id, 'SHARE_SUCCESS')); + }) + .catch(err => { + if(err.response){ + dispatch(returnErrors(err.response.data.message, err.response.status, 'SHARE_FAIL')); + } + }); +}; export const resetProject = () => (dispatch) => { diff --git a/src/components/Home.js b/src/components/Home.js index 38368d8..bade15d 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -95,7 +95,9 @@ class Home extends Component {
: null } -
+
+ +
@@ -109,7 +111,7 @@ class Home extends Component { {this.props.project ? - < BlocklyWindow blocklyCSS={{ height: '80vH' }} initialXml={this.props.project} /> + < BlocklyWindow blocklyCSS={{ height: '80vH' }} initialXml={this.props.project.xml} /> : < BlocklyWindow blocklyCSS={{ height: '80vH' }} /> } diff --git a/src/components/Navbar.js b/src/components/Navbar.js index b7fa021..2bc7b62 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -157,7 +157,7 @@ const mapStateToProps = state => ({ tutorialIsLoading: state.tutorial.progress, projectIsLoading: state.project.progress, isAuthenticated: state.auth.isAuthenticated, - userRole: state.auth.user + user: state.auth.user }); export default connect(mapStateToProps, { logout })(withStyles(styles, { withTheme: true })(withRouter(Navbar))); diff --git a/src/components/Project/Project.js b/src/components/Project/Project.js index 80eb59d..a3a2403 100644 --- a/src/components/Project/Project.js +++ b/src/components/Project/Project.js @@ -75,7 +75,7 @@ class Project extends Component { {this.props.type !== 'share' ? : null} - + : null ); }; diff --git a/src/components/Project/ProjectHome.js b/src/components/Project/ProjectHome.js index 5ccd49b..b373f02 100644 --- a/src/components/Project/ProjectHome.js +++ b/src/components/Project/ProjectHome.js @@ -10,6 +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 { withStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; @@ -61,6 +62,14 @@ class ProjectHome extends Component { this.setState({snackbar: false}); this.props.getProjects(this.props.location.pathname.replace('/','')); } + if(props.message !== this.props.message){ + if(this.props.message.id === 'PROJECT_DELETE_SUCCESS'){ + this.setState({ snackbar: true, key: Date.now(), message: `Dein Projekt wurde erfolgreich gelöscht.`, type: 'success' }); + } + else if(this.props.message.id === 'GALLERY_DELETE_SUCCESS'){ + this.setState({ snackbar: true, key: Date.now(), message: `Dein Galerie-Projekt wurde erfolgreich gelöscht.`, type: 'success' }); + } + } } componentWillUnmount() { @@ -86,8 +95,8 @@ class ProjectHome extends Component { {this.props.projects.map((project, i) => { return ( - - + +

{project.title}

{project.description} -
- + + {this.props.user && this.props.user.email === project.creator ? +
+ +
+ +
+
+ : null} +
) })} @@ -128,12 +149,14 @@ ProjectHome.propTypes = { clearMessages: PropTypes.func.isRequired, projects: PropTypes.array.isRequired, progress: PropTypes.bool.isRequired, + user: PropTypes.object, message: PropTypes.object.isRequired }; const mapStateToProps = state => ({ projects: state.project.projects, progress: state.project.progress, + user: state.auth.user, message: state.message }); diff --git a/src/components/Route/PrivateRouteCreator.js b/src/components/Route/PrivateRouteCreator.js index b4734da..0efd48c 100644 --- a/src/components/Route/PrivateRouteCreator.js +++ b/src/components/Route/PrivateRouteCreator.js @@ -39,7 +39,7 @@ PrivateRoute.propTypes = { const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated, - userRole: state.auth.user + user: state.auth.user }); export default connect(mapStateToProps, null)(withRouter(PrivateRoute)); diff --git a/src/components/Tutorial/Builder/Builder.js b/src/components/Tutorial/Builder/Builder.js index 398c79f..753edd0 100644 --- a/src/components/Tutorial/Builder/Builder.js +++ b/src/components/Tutorial/Builder/Builder.js @@ -6,6 +6,7 @@ import { getTutorials, resetTutorial} from '../../../actions/tutorialActions'; import { clearMessages } from '../../../actions/messageActions'; import axios from 'axios'; +import { withRouter } from 'react-router-dom'; import { saveAs } from 'file-saver'; import { detectWhitespacesAndReturnReadableResult } from '../../../helpers/whitespace'; @@ -378,4 +379,4 @@ const mapStateToProps = state => ({ message: state.message }); -export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, tutorialId, resetTutorialBuilder, getTutorials, resetTutorial, clearMessages })(withStyles(styles, { withTheme: true })(Builder)); +export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, tutorialId, resetTutorialBuilder, getTutorials, resetTutorial, clearMessages })(withStyles(styles, { withTheme: true })(withRouter(Builder))); diff --git a/src/components/WorkspaceFunc.js b/src/components/WorkspaceFunc.js index a0a7600..f11e620 100644 --- a/src/components/WorkspaceFunc.js +++ b/src/components/WorkspaceFunc.js @@ -2,7 +2,7 @@ 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, setDescription } from '../actions/projectActions'; +import { updateProject, deleteProject, shareProject, setDescription } from '../actions/projectActions'; import * as Blockly from 'blockly/core'; @@ -100,6 +100,9 @@ class WorkspaceFunc extends Component { 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' }); @@ -116,6 +119,15 @@ class WorkspaceFunc extends Component { 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); + } } } @@ -155,23 +167,7 @@ class WorkspaceFunc extends Component { this.setState({ share: true, open: true, title: 'Programm teilen', id: this.props.project._id._id }); } else { - var body = { - title: this.state.name - }; - if(this.props.projectType === 'project'){ - body.projectId = this.props.project._id._id ? this.props.project._id._id : this.props.project._id - } else { - body.xml = this.props.xml; - } - axios.post(`${process.env.REACT_APP_BLOCKLY_API}/share`, body) - .then(res => { - var shareContent = res.data.content; - this.setState({ share: true, open: true, title: 'Programm teilen', id: shareContent._id }); - }) - .catch(err => { - 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); - }); + 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); } } @@ -274,11 +270,12 @@ class WorkspaceFunc extends Component { 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.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.` }); } @@ -307,7 +304,7 @@ class WorkspaceFunc extends Component {
{!this.props.assessment ? -
{ 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\'.' }) }}> +
{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}
@@ -315,24 +312,28 @@ class WorkspaceFunc extends Component {
: null} - {this.props.assessment ? : } - - this.props.updateProject() : () => this.saveProject()} - > - - - - - { this.createFileName('xml'); }} - > - - - - {!this.props.assessment? + {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?
: null} - {!this.props.assessment? + {!this.props.assessment && !this.props.multiple? : null} - {!this.props.assessment? + {this.props.projectType !== 'gallery' && !this.props.assessment ? :null} - - this.resetWorkspace()} - > - - - + {!this.props.multiple ? + + this.resetWorkspace()} + > + + + + : null} {!this.props.assessment && (this.props.projectType === 'project' || this.props.projectType === 'gallery') ? this.props.deleteProject()} + onClick={() => this.props.deleteProject(this.props.projectType, this.props.project._id._id ? this.props.project._id._id : this.props.project._id)} > @@ -406,7 +409,7 @@ class WorkspaceFunc extends Component { {this.props.projectType === 'gallery' ?
- +
: } @@ -426,7 +429,7 @@ class WorkspaceFunc extends Component {
{this.props.project && this.props.project._id._id ? - {`Das Projekt wurde bereits geteilt. Der Link ist noch ${ + {`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` @@ -454,15 +457,15 @@ WorkspaceFunc.propTypes = { 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, - projectType: PropTypes.string.isRequired, - project: PropTypes.object.isRequired, - message: PropTypes.object.isRequired + message: PropTypes.object.isRequired, + user: PropTypes.object }; const mapStateToProps = state => ({ @@ -470,9 +473,8 @@ const mapStateToProps = state => ({ xml: state.workspace.code.xml, name: state.workspace.name, description: state.project.description, - projectType: state.project.type, - project: state.project.projects[0], - message: state.message + message: state.message, + user: state.auth.user }); -export default connect(mapStateToProps, { clearStats, onChangeCode, workspaceName, updateProject, deleteProject, setDescription })(withStyles(styles, { withTheme: true })(withWidth()(withRouter(WorkspaceFunc)))); +export default connect(mapStateToProps, { clearStats, onChangeCode, workspaceName, updateProject, shareProject, deleteProject, setDescription })(withStyles(styles, { withTheme: true })(withWidth()(withRouter(WorkspaceFunc))));