update tutorial builder
- update tutorial modell - add difficulty closes #82 - add review system - make tutorials private by default - add admin overview
This commit is contained in:
		
							parent
							
								
									39c8fd504f
								
							
						
					
					
						commit
						0414d2043d
					
				| @ -33,6 +33,7 @@ | |||||||
|     "react-markdown": "^8.0.0", |     "react-markdown": "^8.0.0", | ||||||
|     "react-markdown-editor-lite": "^1.3.2", |     "react-markdown-editor-lite": "^1.3.2", | ||||||
|     "react-mde": "^11.5.0", |     "react-mde": "^11.5.0", | ||||||
|  |     "react-rating-stars-component": "^2.2.0", | ||||||
|     "react-redux": "^7.2.4", |     "react-redux": "^7.2.4", | ||||||
|     "react-router-dom": "^5.2.0", |     "react-router-dom": "^5.2.0", | ||||||
|     "react-scripts": "^5.0.0", |     "react-scripts": "^5.0.0", | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								src/App.css
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/App.css
									
									
									
									
									
								
							| @ -43,6 +43,20 @@ blockquote p { | |||||||
|   display: inline; |   display: inline; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .tutorial table, | ||||||
|  | th, | ||||||
|  | td { | ||||||
|  |   border: 1px solid #ddd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .tutorial th { | ||||||
|  |   padding-top: 12px; | ||||||
|  |   padding-bottom: 12px; | ||||||
|  |   text-align: left; | ||||||
|  |   background-color: #4eaf47; | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .overlay { | .overlay { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: column; |   flex-direction: column; | ||||||
|  | |||||||
| @ -84,6 +84,77 @@ export const getTutorials = () => (dispatch, getState) => { | |||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | export const getAllTutorials = () => (dispatch, getState) => { | ||||||
|  |   axios | ||||||
|  |     .get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getAllTutorials`) | ||||||
|  |     .then((res) => { | ||||||
|  |       var tutorials = res.data.tutorials; | ||||||
|  |       existingTutorials(tutorials, getState().tutorial.status).then( | ||||||
|  |         (status) => { | ||||||
|  |           dispatch({ | ||||||
|  |             type: TUTORIAL_SUCCESS, | ||||||
|  |             payload: status, | ||||||
|  |           }); | ||||||
|  |           dispatch(updateStatus(status)); | ||||||
|  |           dispatch({ | ||||||
|  |             type: GET_TUTORIALS, | ||||||
|  |             payload: tutorials, | ||||||
|  |           }); | ||||||
|  |           dispatch({ type: TUTORIAL_PROGRESS }); | ||||||
|  |           dispatch(returnSuccess(res.data.message, res.status)); | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     }) | ||||||
|  |     .catch((err) => { | ||||||
|  |       if (err.response) { | ||||||
|  |         dispatch( | ||||||
|  |           returnErrors( | ||||||
|  |             err.response.data.message, | ||||||
|  |             err.response.status, | ||||||
|  |             "GET_TUTORIALS_FAIL" | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |       dispatch({ type: TUTORIAL_PROGRESS }); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const getUserTutorials = () => (dispatch, getState) => { | ||||||
|  |   axios | ||||||
|  |     .get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getUserTutorials`) | ||||||
|  |     .then((res) => { | ||||||
|  |       var tutorials = res.data.tutorials; | ||||||
|  |       existingTutorials(tutorials, getState().tutorial.status).then( | ||||||
|  |         (status) => { | ||||||
|  |           dispatch({ | ||||||
|  |             type: TUTORIAL_SUCCESS, | ||||||
|  |             payload: status, | ||||||
|  |           }); | ||||||
|  |           dispatch(updateStatus(status)); | ||||||
|  |           dispatch({ | ||||||
|  |             type: GET_TUTORIALS, | ||||||
|  |             payload: tutorials, | ||||||
|  |           }); | ||||||
|  |           dispatch({ type: TUTORIAL_PROGRESS }); | ||||||
|  |           dispatch(returnSuccess(res.data.message, res.status)); | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |     }) | ||||||
|  |     .catch((err) => { | ||||||
|  |       console.log(err); | ||||||
|  |       if (err.response) { | ||||||
|  |         dispatch( | ||||||
|  |           returnErrors( | ||||||
|  |             err.response.data.message, | ||||||
|  |             err.response.status, | ||||||
|  |             "GET_TUTORIALS_FAIL" | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |       dispatch({ type: TUTORIAL_PROGRESS }); | ||||||
|  |     }); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export const updateStatus = (status) => (dispatch, getState) => { | export const updateStatus = (status) => (dispatch, getState) => { | ||||||
|   if (getState().auth.isAuthenticated) { |   if (getState().auth.isAuthenticated) { | ||||||
|     // update user account in database - sync with redux store
 |     // update user account in database - sync with redux store
 | ||||||
|  | |||||||
| @ -4,6 +4,9 @@ import { | |||||||
|   BUILDER_CHANGE, |   BUILDER_CHANGE, | ||||||
|   BUILDER_ERROR, |   BUILDER_ERROR, | ||||||
|   BUILDER_TITLE, |   BUILDER_TITLE, | ||||||
|  |   BUILDER_PUBLIC, | ||||||
|  |   BUILDER_DIFFICULTY, | ||||||
|  |   BUILDER_REVIEW, | ||||||
|   BUILDER_ID, |   BUILDER_ID, | ||||||
|   BUILDER_ADD_STEP, |   BUILDER_ADD_STEP, | ||||||
|   BUILDER_DELETE_STEP, |   BUILDER_DELETE_STEP, | ||||||
| @ -35,6 +38,30 @@ export const tutorialTitle = (title) => (dispatch) => { | |||||||
|   dispatch(changeTutorialBuilder()); |   dispatch(changeTutorialBuilder()); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | export const tutorialPublic = (pub) => (dispatch) => { | ||||||
|  |   dispatch({ | ||||||
|  |     type: BUILDER_PUBLIC, | ||||||
|  |     payload: pub, | ||||||
|  |   }); | ||||||
|  |   dispatch(changeTutorialBuilder()); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const tutorialDifficulty = (difficulty) => (dispatch) => { | ||||||
|  |   dispatch({ | ||||||
|  |     type: BUILDER_DIFFICULTY, | ||||||
|  |     payload: difficulty, | ||||||
|  |   }); | ||||||
|  |   dispatch(changeTutorialBuilder()); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const tutorialReview = (review) => (dispatch) => { | ||||||
|  |   dispatch({ | ||||||
|  |     type: BUILDER_REVIEW, | ||||||
|  |     payload: review, | ||||||
|  |   }); | ||||||
|  |   dispatch(changeTutorialBuilder()); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| export const tutorialSteps = (steps) => (dispatch) => { | export const tutorialSteps = (steps) => (dispatch) => { | ||||||
|   dispatch({ |   dispatch({ | ||||||
|     type: BUILDER_ADD_STEP, |     type: BUILDER_ADD_STEP, | ||||||
| @ -320,6 +347,7 @@ export const readJSON = (json) => (dispatch, getState) => { | |||||||
|     return object; |     return object; | ||||||
|   }); |   }); | ||||||
|   dispatch(tutorialTitle(json.title)); |   dispatch(tutorialTitle(json.title)); | ||||||
|  |   dispatch(tutorialDifficulty(json.difficulty)); | ||||||
|   dispatch(tutorialSteps(steps)); |   dispatch(tutorialSteps(steps)); | ||||||
|   dispatch(setSubmitError()); |   dispatch(setSubmitError()); | ||||||
|   dispatch(progress(false)); |   dispatch(progress(false)); | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ export const NAME = "NAME"; | |||||||
| export const TUTORIAL_PROGRESS = "TUTORIAL_PROGRESS"; | export const TUTORIAL_PROGRESS = "TUTORIAL_PROGRESS"; | ||||||
| export const GET_TUTORIAL = "GET_TUTORIAL"; | export const GET_TUTORIAL = "GET_TUTORIAL"; | ||||||
| export const GET_TUTORIALS = "GET_TUTORIALS"; | export const GET_TUTORIALS = "GET_TUTORIALS"; | ||||||
|  | export const GET_USERTUTORIALS = "GET_USERTUTORIALS"; | ||||||
| export const GET_STATUS = "GET_STATUS"; | export const GET_STATUS = "GET_STATUS"; | ||||||
| export const TUTORIAL_SUCCESS = "TUTORIAL_SUCCESS"; | export const TUTORIAL_SUCCESS = "TUTORIAL_SUCCESS"; | ||||||
| export const TUTORIAL_ERROR = "TUTORIAL_ERROR"; | export const TUTORIAL_ERROR = "TUTORIAL_ERROR"; | ||||||
| @ -32,6 +33,9 @@ export const JSON_STRING = "JSON_STRING"; | |||||||
| 
 | 
 | ||||||
| export const BUILDER_CHANGE = "BUILDER_CHANGE"; | export const BUILDER_CHANGE = "BUILDER_CHANGE"; | ||||||
| export const BUILDER_TITLE = "BUILDER_TITLE"; | export const BUILDER_TITLE = "BUILDER_TITLE"; | ||||||
|  | export const BUILDER_DIFFICULTY = "BUILDER_DIFFICULTY"; | ||||||
|  | export const BUILDER_PUBLIC = "BUILDER_PUBLIC"; | ||||||
|  | export const BUILDER_REVIEW = "BUILDER_REVIEW"; | ||||||
| export const BUILDER_ID = "BUILDER_ID"; | export const BUILDER_ID = "BUILDER_ID"; | ||||||
| export const BUILDER_ADD_STEP = "BUILDER_ADD_STEP"; | export const BUILDER_ADD_STEP = "BUILDER_ADD_STEP"; | ||||||
| export const BUILDER_DELETE_STEP = "BUILDER_DELETE_STEP"; | export const BUILDER_DELETE_STEP = "BUILDER_DELETE_STEP"; | ||||||
|  | |||||||
| @ -228,6 +228,7 @@ export const UI = { | |||||||
|   builder_requirements_head: "Voraussetzungen", |   builder_requirements_head: "Voraussetzungen", | ||||||
|   builder_requirements_order: |   builder_requirements_order: | ||||||
|     "Beachte, dass die Reihenfolge des Anhakens maßgebend ist.", |     "Beachte, dass die Reihenfolge des Anhakens maßgebend ist.", | ||||||
|  |   builder_difficulty: "Schwierigkeitsgrad", | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Login |    * Login | ||||||
|  | |||||||
| @ -10,6 +10,8 @@ import { | |||||||
|   resetTutorial as resetTutorialBuilder, |   resetTutorial as resetTutorialBuilder, | ||||||
| } from "../../../actions/tutorialBuilderActions"; | } from "../../../actions/tutorialBuilderActions"; | ||||||
| import { | import { | ||||||
|  |   getAllTutorials, | ||||||
|  |   getUserTutorials, | ||||||
|   getTutorials, |   getTutorials, | ||||||
|   resetTutorial, |   resetTutorial, | ||||||
|   deleteTutorial, |   deleteTutorial, | ||||||
| @ -19,12 +21,17 @@ import { clearMessages } from "../../../actions/messageActions"; | |||||||
| 
 | 
 | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| import { withRouter } from "react-router-dom"; | import { withRouter } from "react-router-dom"; | ||||||
|  | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
|  | import { faEyeSlash, faUserCheck } from "@fortawesome/free-solid-svg-icons"; | ||||||
| 
 | 
 | ||||||
| import Breadcrumbs from "../../Breadcrumbs"; | import Breadcrumbs from "../../Breadcrumbs"; | ||||||
| import Textfield from "./Textfield"; | import Textfield from "./Textfield"; | ||||||
|  | import Difficulty from "./Difficulty"; | ||||||
| import Step from "./Step"; | import Step from "./Step"; | ||||||
| import Dialog from "../../Dialog"; | import Dialog from "../../Dialog"; | ||||||
| import Snackbar from "../../Snackbar"; | import Snackbar from "../../Snackbar"; | ||||||
|  | import Public from "./Public"; | ||||||
|  | import Review from "./Review"; | ||||||
| 
 | 
 | ||||||
| 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"; | ||||||
| @ -66,6 +73,8 @@ class Builder extends Component { | |||||||
|       tutorial: "new", |       tutorial: "new", | ||||||
|       open: false, |       open: false, | ||||||
|       title: "", |       title: "", | ||||||
|  |       public: false, | ||||||
|  |       difficulty: "", | ||||||
|       content: "", |       content: "", | ||||||
|       string: false, |       string: false, | ||||||
|       snackbar: false, |       snackbar: false, | ||||||
| @ -80,7 +89,11 @@ class Builder extends Component { | |||||||
|     // retrieve tutorials only if a potential user is loaded - authentication
 |     // retrieve tutorials only if a potential user is loaded - authentication
 | ||||||
|     // is finished (success or failed)
 |     // is finished (success or failed)
 | ||||||
|     if (!this.props.authProgress) { |     if (!this.props.authProgress) { | ||||||
|       this.props.getTutorials(); |       if (this.props.user.role === "admin") { | ||||||
|  |         this.props.getAllTutorials(); | ||||||
|  |       } else { | ||||||
|  |         this.props.getUserTutorials(); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -89,8 +102,12 @@ class Builder extends Component { | |||||||
|       props.authProgress !== this.props.authProgress && |       props.authProgress !== this.props.authProgress && | ||||||
|       !this.props.authProgress |       !this.props.authProgress | ||||||
|     ) { |     ) { | ||||||
|       // authentication is completed
 |       if (this.props.user.role === "admin") { | ||||||
|       this.props.getTutorials(); |         // authentication is completed
 | ||||||
|  |         this.props.getAllTutorials(); | ||||||
|  |       } else { | ||||||
|  |         this.props.getUserTutorials(); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|     if (props.message !== this.props.message) { |     if (props.message !== this.props.message) { | ||||||
|       if (this.props.message.id === "GET_TUTORIALS_FAIL") { |       if (this.props.message.id === "GET_TUTORIALS_FAIL") { | ||||||
| @ -258,6 +275,9 @@ class Builder extends Component { | |||||||
|       var steps = this.props.steps; |       var steps = this.props.steps; | ||||||
|       var newTutorial = new FormData(); |       var newTutorial = new FormData(); | ||||||
|       newTutorial.append("title", this.props.title); |       newTutorial.append("title", this.props.title); | ||||||
|  |       newTutorial.append("difficulty", this.props.difficulty); | ||||||
|  |       newTutorial.append("public", this.props.public); | ||||||
|  |       newTutorial.append("review", this.props.review); | ||||||
|       steps.forEach((step, i) => { |       steps.forEach((step, i) => { | ||||||
|         if (step._id) { |         if (step._id) { | ||||||
|           newTutorial.append(`steps[${i}][_id]`, step._id); |           newTutorial.append(`steps[${i}][_id]`, step._id); | ||||||
| @ -284,6 +304,7 @@ class Builder extends Component { | |||||||
|           newTutorial.append(`steps[${i}][xml]`, step.xml); |           newTutorial.append(`steps[${i}][xml]`, step.xml); | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|  |       console.log(newTutorial); | ||||||
|       return newTutorial; |       return newTutorial; | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| @ -362,6 +383,12 @@ class Builder extends Component { | |||||||
|         (tutorial) => tutorial.creator === this.props.user.email |         (tutorial) => tutorial.creator === this.props.user.email | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // } else {
 | ||||||
|  |     //   filteredTutorials = this.props.userTutorials.filter(
 | ||||||
|  |     //     (tutorial) => tutorial.creator === this.props.user.email
 | ||||||
|  |     //   );
 | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <Breadcrumbs |         <Breadcrumbs | ||||||
| @ -451,7 +478,24 @@ class Builder extends Component { | |||||||
|               label="Tutorial" |               label="Tutorial" | ||||||
|             > |             > | ||||||
|               {filteredTutorials.map((tutorial) => ( |               {filteredTutorials.map((tutorial) => ( | ||||||
|                 <MenuItem value={tutorial._id}>{tutorial.title}</MenuItem> |                 <MenuItem value={tutorial._id}> | ||||||
|  |                   {tutorial.title}{" "} | ||||||
|  |                   {tutorial.review && tutorial.public === false ? ( | ||||||
|  |                     <div> | ||||||
|  |                       <FontAwesomeIcon mx={2} icon={faUserCheck} /> | ||||||
|  |                       <FontAwesomeIcon mx={2} icon={faEyeSlash} /> | ||||||
|  |                     </div> | ||||||
|  |                   ) : tutorial.public === false ? ( | ||||||
|  |                     <FontAwesomeIcon mx={2} icon={faEyeSlash} /> | ||||||
|  |                   ) : null} | ||||||
|  |                 </MenuItem> | ||||||
|  |                 /* ) : tutorial.public === false ? ( | ||||||
|  |                     <MenuItem value={tutorial._id}> | ||||||
|  |                       {tutorial.title} <FontAwesomeIcon icon={faEyeSlash} /> | ||||||
|  |                     </MenuItem> | ||||||
|  |                   ) : ( | ||||||
|  |                     <MenuItem value={tutorial._id}>{tutorial.title}</MenuItem> | ||||||
|  |                   )} */ | ||||||
|               ))} |               ))} | ||||||
|             </Select> |             </Select> | ||||||
|           </FormControl> |           </FormControl> | ||||||
| @ -476,6 +520,45 @@ class Builder extends Component { | |||||||
|               label={"Titel"} |               label={"Titel"} | ||||||
|               error={this.props.error.title} |               error={this.props.error.title} | ||||||
|             /> |             /> | ||||||
|  |             <div | ||||||
|  |               style={{ | ||||||
|  |                 borderRadius: "25px", | ||||||
|  |                 border: "1px solid lightgrey", | ||||||
|  |                 padding: "10px 14px 10px 10px", | ||||||
|  |                 marginBottom: "20px", | ||||||
|  |               }} | ||||||
|  |             > | ||||||
|  |               <Difficulty | ||||||
|  |                 value={this.props.difficulty} | ||||||
|  |                 property={"difficulty"} | ||||||
|  |                 label={"difficulty"} | ||||||
|  |                 error={this.props.error.difficulty} | ||||||
|  |               /> | ||||||
|  |               <Divider | ||||||
|  |                 variant="fullWidth" | ||||||
|  |                 style={{ margin: "30px 0 10px 0" }} | ||||||
|  |               /> | ||||||
|  | 
 | ||||||
|  |               <Review | ||||||
|  |                 value={this.props.review} | ||||||
|  |                 property={"review"} | ||||||
|  |                 label={"review"} | ||||||
|  |                 error={this.props.error.review} | ||||||
|  |               /> | ||||||
|  |               <Divider | ||||||
|  |                 variant="fullWidth" | ||||||
|  |                 style={{ margin: "30px 0 10px 0" }} | ||||||
|  |               /> | ||||||
|  | 
 | ||||||
|  |               {this.props.user.blocklyRole === "admin" ? ( | ||||||
|  |                 <Public | ||||||
|  |                   value={this.props.public} | ||||||
|  |                   property={"public"} | ||||||
|  |                   label={"public"} | ||||||
|  |                   error={this.props.error.public} | ||||||
|  |                 /> | ||||||
|  |               ) : null} | ||||||
|  |             </div> | ||||||
| 
 | 
 | ||||||
|             {this.props.steps.map((step, i) => ( |             {this.props.steps.map((step, i) => ( | ||||||
|               <Step step={step} index={i} key={i} /> |               <Step step={step} index={i} key={i} /> | ||||||
| @ -608,6 +691,8 @@ class Builder extends Component { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Builder.propTypes = { | Builder.propTypes = { | ||||||
|  |   getAllTutorials: PropTypes.func.isRequired, | ||||||
|  |   getUserTutorials: PropTypes.func.isRequired, | ||||||
|   getTutorials: PropTypes.func.isRequired, |   getTutorials: PropTypes.func.isRequired, | ||||||
|   resetTutorial: PropTypes.func.isRequired, |   resetTutorial: PropTypes.func.isRequired, | ||||||
|   clearMessages: PropTypes.func.isRequired, |   clearMessages: PropTypes.func.isRequired, | ||||||
| @ -620,6 +705,9 @@ Builder.propTypes = { | |||||||
|   resetTutorialBuilder: PropTypes.func.isRequired, |   resetTutorialBuilder: PropTypes.func.isRequired, | ||||||
|   tutorialProgress: PropTypes.func.isRequired, |   tutorialProgress: PropTypes.func.isRequired, | ||||||
|   title: PropTypes.string.isRequired, |   title: PropTypes.string.isRequired, | ||||||
|  |   difficulty: PropTypes.number.isRequired, | ||||||
|  |   public: PropTypes.bool.isRequired, | ||||||
|  |   review: PropTypes.bool.isRequired, | ||||||
|   id: PropTypes.string.isRequired, |   id: PropTypes.string.isRequired, | ||||||
|   steps: PropTypes.array.isRequired, |   steps: PropTypes.array.isRequired, | ||||||
|   change: PropTypes.number.isRequired, |   change: PropTypes.number.isRequired, | ||||||
| @ -634,12 +722,16 @@ Builder.propTypes = { | |||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state) => ({ | const mapStateToProps = (state) => ({ | ||||||
|   title: state.builder.title, |   title: state.builder.title, | ||||||
|  |   difficulty: state.builder.difficulty, | ||||||
|  |   review: state.builder.review, | ||||||
|  |   public: state.builder.public, | ||||||
|   id: state.builder.id, |   id: state.builder.id, | ||||||
|   steps: state.builder.steps, |   steps: state.builder.steps, | ||||||
|   change: state.builder.change, |   change: state.builder.change, | ||||||
|   error: state.builder.error, |   error: state.builder.error, | ||||||
|   json: state.builder.json, |   json: state.builder.json, | ||||||
|   isProgress: state.builder.progress, |   isProgress: state.builder.progress, | ||||||
|  |   userTutorials: state.tutorial.userTutorials, | ||||||
|   tutorials: state.tutorial.tutorials, |   tutorials: state.tutorial.tutorials, | ||||||
|   message: state.message, |   message: state.message, | ||||||
|   user: state.auth.user, |   user: state.auth.user, | ||||||
| @ -654,6 +746,8 @@ export default connect(mapStateToProps, { | |||||||
|   tutorialId, |   tutorialId, | ||||||
|   resetTutorialBuilder, |   resetTutorialBuilder, | ||||||
|   getTutorials, |   getTutorials, | ||||||
|  |   getUserTutorials, | ||||||
|  |   getAllTutorials, | ||||||
|   resetTutorial, |   resetTutorial, | ||||||
|   tutorialProgress, |   tutorialProgress, | ||||||
|   clearMessages, |   clearMessages, | ||||||
|  | |||||||
							
								
								
									
										101
									
								
								src/components/Tutorial/Builder/Difficulty.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/components/Tutorial/Builder/Difficulty.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | |||||||
|  | import React, { Component } from "react"; | ||||||
|  | import PropTypes from "prop-types"; | ||||||
|  | import { connect } from "react-redux"; | ||||||
|  | import { | ||||||
|  |   tutorialDifficulty, | ||||||
|  |   jsonString, | ||||||
|  |   changeContent, | ||||||
|  |   setError, | ||||||
|  |   deleteError, | ||||||
|  | } from "../../../actions/tutorialBuilderActions"; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from "@material-ui/core/styles"; | ||||||
|  | import ReactStars from "react-rating-stars-component"; | ||||||
|  | import * as Blockly from "blockly"; | ||||||
|  | import FormGroup from "@material-ui/core/FormGroup"; | ||||||
|  | import FormControlLabel from "@material-ui/core/FormControlLabel"; | ||||||
|  | import FormControl from "@material-ui/core/FormControl"; | ||||||
|  | import FormLabel from "@material-ui/core/FormLabel"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   multiline: { | ||||||
|  |     padding: "18.5px 14px 18.5px 24px", | ||||||
|  |   }, | ||||||
|  |   errorColor: { | ||||||
|  |     color: `${theme.palette.error.dark} !important`, | ||||||
|  |   }, | ||||||
|  |   errorColorShrink: { | ||||||
|  |     color: `rgba(0, 0, 0, 0.54) !important`, | ||||||
|  |   }, | ||||||
|  |   errorBorder: { | ||||||
|  |     borderColor: `${theme.palette.error.dark} !important`, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class Difficulty extends Component { | ||||||
|  |   ratingChanged = (newRating) => { | ||||||
|  |     console.log(newRating); | ||||||
|  |     this.handleChange(newRating); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleChange = (e) => { | ||||||
|  |     var value = e; | ||||||
|  |     console.log(value); | ||||||
|  |     if (this.props.property === "difficulty") { | ||||||
|  |       this.props.tutorialDifficulty(value); | ||||||
|  |     } else if (this.props.property === "json") { | ||||||
|  |       this.props.jsonString(value); | ||||||
|  |     } else { | ||||||
|  |       this.props.changeContent( | ||||||
|  |         value, | ||||||
|  |         this.props.index, | ||||||
|  |         this.props.property, | ||||||
|  |         this.props.property2 | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <FormControl component="fieldset"> | ||||||
|  |         <FormLabel component="legend"> | ||||||
|  |           {Blockly.Msg.builder_difficulty} | ||||||
|  |         </FormLabel> | ||||||
|  |         <FormGroup aria-label="position" row> | ||||||
|  |           <FormControlLabel | ||||||
|  |             value="Tutorial veröffentlichen" | ||||||
|  |             control={ | ||||||
|  |               <ReactStars | ||||||
|  |                 count={5} | ||||||
|  |                 onChange={this.handleChange} | ||||||
|  |                 value={this.props.value} | ||||||
|  |                 size={30} | ||||||
|  |                 isHalf={true} | ||||||
|  |                 emptyIcon={<i className="fa fa-star"></i>} | ||||||
|  |                 halfIcon={<i className="fa fa-star-half-alt"></i>} | ||||||
|  |                 fullIcon={<i className="fa fa-star"></i>} | ||||||
|  |                 activeColor="#ffd700" | ||||||
|  |               /> | ||||||
|  |             } | ||||||
|  |             label="Schwierigkeitsgrad" | ||||||
|  |             labelPlacement="start" | ||||||
|  |           /> | ||||||
|  |         </FormGroup> | ||||||
|  |       </FormControl> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Difficulty.propTypes = { | ||||||
|  |   tutorialDifficulty: PropTypes.func.isRequired, | ||||||
|  |   jsonString: PropTypes.func.isRequired, | ||||||
|  |   changeContent: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default connect(null, { | ||||||
|  |   tutorialDifficulty, | ||||||
|  |   jsonString, | ||||||
|  |   changeContent, | ||||||
|  |   setError, | ||||||
|  |   deleteError, | ||||||
|  | })(withStyles(styles, { withTheme: true })(Difficulty)); | ||||||
							
								
								
									
										91
									
								
								src/components/Tutorial/Builder/Public.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/components/Tutorial/Builder/Public.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | |||||||
|  | import React, { Component } from "react"; | ||||||
|  | import PropTypes from "prop-types"; | ||||||
|  | import { connect } from "react-redux"; | ||||||
|  | import { | ||||||
|  |   tutorialPublic, | ||||||
|  |   jsonString, | ||||||
|  |   changeContent, | ||||||
|  |   setError, | ||||||
|  |   deleteError, | ||||||
|  | } from "../../../actions/tutorialBuilderActions"; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from "@material-ui/core/styles"; | ||||||
|  | 
 | ||||||
|  | import * as Blockly from "blockly"; | ||||||
|  | import Checkbox from "@material-ui/core/Checkbox"; | ||||||
|  | import FormGroup from "@material-ui/core/FormGroup"; | ||||||
|  | import FormControlLabel from "@material-ui/core/FormControlLabel"; | ||||||
|  | import FormControl from "@material-ui/core/FormControl"; | ||||||
|  | import FormLabel from "@material-ui/core/FormLabel"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   multiline: { | ||||||
|  |     padding: "18.5px 14px 18.5px 24px", | ||||||
|  |   }, | ||||||
|  |   errorColor: { | ||||||
|  |     color: `${theme.palette.error.dark} !important`, | ||||||
|  |   }, | ||||||
|  |   errorColorShrink: { | ||||||
|  |     color: `rgba(0, 0, 0, 0.54) !important`, | ||||||
|  |   }, | ||||||
|  |   errorBorder: { | ||||||
|  |     borderColor: `${theme.palette.error.dark} !important`, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class Public extends Component { | ||||||
|  |   handleChange = (e) => { | ||||||
|  |     var value = e.target.checked; | ||||||
|  |     console.log(value); | ||||||
|  |     if (this.props.property === "public") { | ||||||
|  |       this.props.tutorialPublic(value); | ||||||
|  |     } else if (this.props.property === "json") { | ||||||
|  |       this.props.jsonString(value); | ||||||
|  |     } else { | ||||||
|  |       this.props.changeContent( | ||||||
|  |         value, | ||||||
|  |         this.props.index, | ||||||
|  |         this.props.property, | ||||||
|  |         this.props.property2 | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <FormControl component="fieldset"> | ||||||
|  |         <FormLabel component="legend">Tutorial veröffentlichen</FormLabel> | ||||||
|  |         <FormGroup aria-label="position" row> | ||||||
|  |           <FormControlLabel | ||||||
|  |             value="Tutorial veröffentlichen" | ||||||
|  |             control={ | ||||||
|  |               <Checkbox | ||||||
|  |                 checked={this.props.value} | ||||||
|  |                 onChange={this.handleChange} | ||||||
|  |                 color="primary" | ||||||
|  |                 name="checkedA" | ||||||
|  |                 inputProps={{ "aria-label": "secondary checkbox" }} | ||||||
|  |               /> | ||||||
|  |             } | ||||||
|  |             label="Tutorial veröffentlichen" | ||||||
|  |             labelPlacement="start" | ||||||
|  |           /> | ||||||
|  |         </FormGroup> | ||||||
|  |       </FormControl> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Public.propTypes = { | ||||||
|  |   tutorialPublic: PropTypes.func.isRequired, | ||||||
|  |   jsonString: PropTypes.func.isRequired, | ||||||
|  |   changeContent: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default connect(null, { | ||||||
|  |   tutorialPublic, | ||||||
|  |   jsonString, | ||||||
|  |   changeContent, | ||||||
|  |   setError, | ||||||
|  |   deleteError, | ||||||
|  | })(withStyles(styles, { withTheme: true })(Public)); | ||||||
							
								
								
									
										97
									
								
								src/components/Tutorial/Builder/Review.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/components/Tutorial/Builder/Review.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | import React, { Component } from "react"; | ||||||
|  | import PropTypes from "prop-types"; | ||||||
|  | import { connect } from "react-redux"; | ||||||
|  | import { | ||||||
|  |   tutorialReview, | ||||||
|  |   jsonString, | ||||||
|  |   changeContent, | ||||||
|  |   setError, | ||||||
|  |   deleteError, | ||||||
|  | } from "../../../actions/tutorialBuilderActions"; | ||||||
|  | 
 | ||||||
|  | import { withStyles } from "@material-ui/core/styles"; | ||||||
|  | 
 | ||||||
|  | import * as Blockly from "blockly"; | ||||||
|  | import Checkbox from "@material-ui/core/Checkbox"; | ||||||
|  | import FormGroup from "@material-ui/core/FormGroup"; | ||||||
|  | import FormControlLabel from "@material-ui/core/FormControlLabel"; | ||||||
|  | import FormControl from "@material-ui/core/FormControl"; | ||||||
|  | import FormLabel from "@material-ui/core/FormLabel"; | ||||||
|  | 
 | ||||||
|  | const styles = (theme) => ({ | ||||||
|  |   multiline: { | ||||||
|  |     padding: "18.5px 14px 18.5px 24px", | ||||||
|  |   }, | ||||||
|  |   errorColor: { | ||||||
|  |     color: `${theme.palette.error.dark} !important`, | ||||||
|  |   }, | ||||||
|  |   errorColorShrink: { | ||||||
|  |     color: `rgba(0, 0, 0, 0.54) !important`, | ||||||
|  |   }, | ||||||
|  |   errorBorder: { | ||||||
|  |     borderColor: `${theme.palette.error.dark} !important`, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | class Review extends Component { | ||||||
|  |   handleChange = (e) => { | ||||||
|  |     var value = e.target.checked; | ||||||
|  |     if (this.props.property === "review") { | ||||||
|  |       this.props.tutorialReview(value); | ||||||
|  |     } else if (this.props.property === "json") { | ||||||
|  |       this.props.jsonString(value); | ||||||
|  |     } else { | ||||||
|  |       this.props.changeContent( | ||||||
|  |         value, | ||||||
|  |         this.props.index, | ||||||
|  |         this.props.property, | ||||||
|  |         this.props.property2 | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     return ( | ||||||
|  |       <FormControl component="fieldset"> | ||||||
|  |         <FormLabel component="legend">Tutorial veröffentlichen</FormLabel> | ||||||
|  |         <p> | ||||||
|  |           {" "} | ||||||
|  |           Du kannst dein Tutorial direkt über den Link mit anderen Personen | ||||||
|  |           teilen. Wenn du dein Tutorial für alle Nutzer:innen in der Überischt | ||||||
|  |           veröffenltichen wollen kannst du es hier aktivieren. Ein Administrator | ||||||
|  |           wird dein Tutorial ansehen und anschließend freischalten. | ||||||
|  |         </p> | ||||||
|  |         <FormGroup aria-label="position" row> | ||||||
|  |           <FormControlLabel | ||||||
|  |             value="Tutorial veröffentlichen" | ||||||
|  |             control={ | ||||||
|  |               <Checkbox | ||||||
|  |                 checked={this.props.value} | ||||||
|  |                 onChange={this.handleChange} | ||||||
|  |                 color="primary" | ||||||
|  |                 name="checkedA" | ||||||
|  |                 inputProps={{ "aria-label": "secondary checkbox" }} | ||||||
|  |               /> | ||||||
|  |             } | ||||||
|  |             label="Ich möchte mein Tutorial öffentlich machen" | ||||||
|  |             labelPlacement="start" | ||||||
|  |           /> | ||||||
|  |         </FormGroup> | ||||||
|  |       </FormControl> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Review.propTypes = { | ||||||
|  |   tutorialReview: PropTypes.func.isRequired, | ||||||
|  |   jsonString: PropTypes.func.isRequired, | ||||||
|  |   changeContent: PropTypes.func.isRequired, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export default connect(null, { | ||||||
|  |   tutorialReview, | ||||||
|  |   jsonString, | ||||||
|  |   changeContent, | ||||||
|  |   setError, | ||||||
|  |   deleteError, | ||||||
|  | })(withStyles(styles, { withTheme: true })(Review)); | ||||||
| @ -1,124 +1,234 @@ | |||||||
| 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 clsx from 'clsx'; | import clsx from "clsx"; | ||||||
| import { withRouter, Link } from 'react-router-dom'; | import { withRouter, Link } from "react-router-dom"; | ||||||
| 
 | 
 | ||||||
| import { fade } from '@material-ui/core/styles/colorManipulator'; | import { fade } from "@material-ui/core/styles/colorManipulator"; | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from "@material-ui/core/styles"; | ||||||
| import Typography from '@material-ui/core/Typography'; | import Typography from "@material-ui/core/Typography"; | ||||||
| import List from '@material-ui/core/List'; | import List from "@material-ui/core/List"; | ||||||
| import Tooltip from '@material-ui/core/Tooltip'; | import Tooltip from "@material-ui/core/Tooltip"; | ||||||
| 
 | 
 | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; | import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import * as Blockly from 'blockly' | import * as Blockly from "blockly"; | ||||||
| 
 | 
 | ||||||
| const styles = theme => ({ | const styles = (theme) => ({ | ||||||
|   outerDiv: { |   outerDiv: { | ||||||
|     width: '50px', |     width: "50px", | ||||||
|     height: '50px', |     height: "50px", | ||||||
|     position: 'absolute', |     position: "absolute", | ||||||
|     color: fade(theme.palette.secondary.main, 0.6) |     color: fade(theme.palette.secondary.main, 0.6), | ||||||
|   }, |   }, | ||||||
|   outerDivError: { |   outerDivError: { | ||||||
|     stroke: fade(theme.palette.error.dark, 0.6), |     stroke: fade(theme.palette.error.dark, 0.6), | ||||||
|     color: fade(theme.palette.error.dark, 0.6) |     color: fade(theme.palette.error.dark, 0.6), | ||||||
|   }, |   }, | ||||||
|   outerDivSuccess: { |   outerDivSuccess: { | ||||||
|     stroke: fade(theme.palette.primary.main, 0.6), |     stroke: fade(theme.palette.primary.main, 0.6), | ||||||
|     color: fade(theme.palette.primary.main, 0.6) |     color: fade(theme.palette.primary.main, 0.6), | ||||||
|   }, |   }, | ||||||
|   outerDivOther: { |   outerDivOther: { | ||||||
|     stroke: fade(theme.palette.secondary.main, 0.6) |     stroke: fade(theme.palette.secondary.main, 0.6), | ||||||
|   }, |   }, | ||||||
|   innerDiv: { |   innerDiv: { | ||||||
|     width: 'inherit', |     width: "inherit", | ||||||
|     height: 'inherit', |     height: "inherit", | ||||||
|     display: 'table-cell', |     display: "table-cell", | ||||||
|     verticalAlign: 'middle', |     verticalAlign: "middle", | ||||||
|     textAlign: 'center' |     textAlign: "center", | ||||||
|   }, |   }, | ||||||
|   link: { |   link: { | ||||||
|     color: theme.palette.text.primary, |     color: theme.palette.text.primary, | ||||||
|     position: 'relative', |     position: "relative", | ||||||
|     height: '50px', |     height: "50px", | ||||||
|     display: 'flex', |     display: "flex", | ||||||
|     margin: '5px 0 5px 10px', |     margin: "5px 0 5px 10px", | ||||||
|     textDecoration: 'none' |     textDecoration: "none", | ||||||
|   }, |   }, | ||||||
|   hoverLink: { |   hoverLink: { | ||||||
|     '&:hover': { |     "&:hover": { | ||||||
|       background: fade(theme.palette.secondary.main, 0.5), |       background: fade(theme.palette.secondary.main, 0.5), | ||||||
|       borderRadius: '0 25px 25px 0 ' |       borderRadius: "0 25px 25px 0 ", | ||||||
|     } |     }, | ||||||
|   } |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| class Requirement extends Component { | class Requirement extends Component { | ||||||
| 
 |  | ||||||
|   render() { |   render() { | ||||||
|     var requirements = this.props.requirements; |     var requirements = this.props.requirements; | ||||||
|     var tutorialIds = requirements.map(requirement => requirement._id); |     var tutorialIds = requirements.map((requirement) => requirement._id); | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ marginTop: '20px', marginBottom: '5px' }}> |       <div style={{ marginTop: "20px", marginBottom: "5px" }}> | ||||||
|         <Typography>{Blockly.Msg.tutorials_requirements}</Typography> |         <Typography>{Blockly.Msg.tutorials_requirements}</Typography> | ||||||
|         <List component="div"> |         <List component="div"> | ||||||
|           {tutorialIds.map((tutorialId, i) => { |           {tutorialIds.map((tutorialId, i) => { | ||||||
|             var title = requirements[i].title |             var title = requirements[i].title; | ||||||
|             var status = this.props.status.filter(status => status._id === tutorialId)[0]; |             var status = this.props.status.filter( | ||||||
|  |               (status) => status._id === tutorialId | ||||||
|  |             )[0]; | ||||||
|             var tasks = status.tasks; |             var tasks = status.tasks; | ||||||
|             var error = status.tasks.filter(task => task.type === 'error').length > 0; |             var error = | ||||||
|             var success = status.tasks.filter(task => task.type === 'success').length / tasks.length |               status.tasks.filter((task) => task.type === "error").length > 0; | ||||||
|             var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other'; |             var success = | ||||||
|  |               status.tasks.filter((task) => task.type === "success").length / | ||||||
|  |               tasks.length; | ||||||
|  |             var tutorialStatus = | ||||||
|  |               success === 1 ? "Success" : error ? "Error" : "Other"; | ||||||
|             return ( |             return ( | ||||||
|               <Link to={`/tutorial/${tutorialId}`} className={this.props.classes.link} key={i}> |               <Link | ||||||
|                 <Tooltip style={{ height: '50px', width: '50px', position: 'absolute', background: 'white', zIndex: 1, borderRadius: '25px' }} title={error ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` : success === 1 ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` : `Das Tutorial ist zu ${success * 100}% abgeschlossen.`} arrow> |                 target={"_blank"} | ||||||
|  |                 to={`/tutorial/${tutorialId}`} | ||||||
|  |                 className={this.props.classes.link} | ||||||
|  |                 key={i} | ||||||
|  |               > | ||||||
|  |                 <Tooltip | ||||||
|  |                   style={{ | ||||||
|  |                     height: "50px", | ||||||
|  |                     width: "50px", | ||||||
|  |                     position: "absolute", | ||||||
|  |                     background: "white", | ||||||
|  |                     zIndex: 1, | ||||||
|  |                     borderRadius: "25px", | ||||||
|  |                   }} | ||||||
|  |                   title={ | ||||||
|  |                     error | ||||||
|  |                       ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` | ||||||
|  |                       : success === 1 | ||||||
|  |                       ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` | ||||||
|  |                       : `Das Tutorial ist zu ${success * 100}% abgeschlossen.` | ||||||
|  |                   } | ||||||
|  |                   arrow | ||||||
|  |                 > | ||||||
|                   <div> |                   <div> | ||||||
|                     <div className={clsx(this.props.classes.outerDiv)} style={{ width: '50px', height: '50px', border: 0 }}> |                     <div | ||||||
|                       <svg style={{ width: '100%', height: '100%' }}> |                       className={clsx(this.props.classes.outerDiv)} | ||||||
|                         {error || success === 1 ? |                       style={{ width: "50px", height: "50px", border: 0 }} | ||||||
|                           <circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5"></circle> |                     > | ||||||
|                           : <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5"></circle>} |                       <svg style={{ width: "100%", height: "100%" }}> | ||||||
|                         {success < 1 && !error ? |                         {error || success === 1 ? ( | ||||||
|                           <circle className={this.props.classes.outerDivSuccess} style={{ transform: 'rotate(-90deg)', transformOrigin: "50% 50%" }} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5" strokeDashoffset={`${(22.5 * 2 * Math.PI) * (1 - success)}`} strokeDasharray={`${(22.5 * 2 * Math.PI)}`}> |                           <circle | ||||||
|                           </circle> |                             className={ | ||||||
|                           : null} |                               error | ||||||
|  |                                 ? this.props.classes.outerDivError | ||||||
|  |                                 : this.props.classes.outerDivSuccess | ||||||
|  |                             } | ||||||
|  |                             r="22.5" | ||||||
|  |                             cx="50%" | ||||||
|  |                             cy="50%" | ||||||
|  |                             fill="none" | ||||||
|  |                             strokeWidth="5" | ||||||
|  |                           ></circle> | ||||||
|  |                         ) : ( | ||||||
|  |                           <circle | ||||||
|  |                             className={this.props.classes.outerDivOther} | ||||||
|  |                             r="22.5" | ||||||
|  |                             cx="50%" | ||||||
|  |                             cy="50%" | ||||||
|  |                             fill="none" | ||||||
|  |                             strokeWidth="5" | ||||||
|  |                           ></circle> | ||||||
|  |                         )} | ||||||
|  |                         {success < 1 && !error ? ( | ||||||
|  |                           <circle | ||||||
|  |                             className={this.props.classes.outerDivSuccess} | ||||||
|  |                             style={{ | ||||||
|  |                               transform: "rotate(-90deg)", | ||||||
|  |                               transformOrigin: "50% 50%", | ||||||
|  |                             }} | ||||||
|  |                             r="22.5" | ||||||
|  |                             cx="50%" | ||||||
|  |                             cy="50%" | ||||||
|  |                             fill="none" | ||||||
|  |                             strokeWidth="5" | ||||||
|  |                             strokeDashoffset={`${ | ||||||
|  |                               22.5 * 2 * Math.PI * (1 - success) | ||||||
|  |                             }`}
 | ||||||
|  |                             strokeDasharray={`${22.5 * 2 * Math.PI}`} | ||||||
|  |                           ></circle> | ||||||
|  |                         ) : null} | ||||||
|                       </svg> |                       </svg> | ||||||
|                     </div> |                     </div> | ||||||
|                     <div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : tutorialStatus === 'Success' ? this.props.classes.outerDivSuccess : null)}> |                     <div | ||||||
|  |                       className={clsx( | ||||||
|  |                         this.props.classes.outerDiv, | ||||||
|  |                         tutorialStatus === "Error" | ||||||
|  |                           ? this.props.classes.outerDivError | ||||||
|  |                           : tutorialStatus === "Success" | ||||||
|  |                           ? this.props.classes.outerDivSuccess | ||||||
|  |                           : null | ||||||
|  |                       )} | ||||||
|  |                     > | ||||||
|                       <div className={this.props.classes.innerDiv}> |                       <div className={this.props.classes.innerDiv}> | ||||||
|                         {error || success === 1 ? |                         {error || success === 1 ? ( | ||||||
|                           <FontAwesomeIcon icon={tutorialStatus === 'Success' ? faCheck : faTimes} /> |                           <FontAwesomeIcon | ||||||
|                           : <Typography variant='h7' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%</Typography> |                             icon={ | ||||||
|                         } |                               tutorialStatus === "Success" ? faCheck : faTimes | ||||||
|  |                             } | ||||||
|  |                           /> | ||||||
|  |                         ) : ( | ||||||
|  |                           <Typography | ||||||
|  |                             variant="h7" | ||||||
|  |                             className={ | ||||||
|  |                               success > 0 | ||||||
|  |                                 ? this.props.classes.outerDivSuccess | ||||||
|  |                                 : {} | ||||||
|  |                             } | ||||||
|  |                           > | ||||||
|  |                             {Math.round(success * 100)}% | ||||||
|  |                           </Typography> | ||||||
|  |                         )} | ||||||
|                       </div> |                       </div> | ||||||
|                     </div> |                     </div> | ||||||
|                   </div> |                   </div> | ||||||
|                 </Tooltip> |                 </Tooltip> | ||||||
|                 <div style={{ height: '50px', width: 'calc(100% - 25px)', transform: 'translate(25px)' }} className={this.props.classes.hoverLink}> |                 <div | ||||||
|                   <Typography style={{ margin: 0, position: 'absolute', top: '50%', transform: 'translate(45px, -50%)', maxHeight: '50px', overflow: 'hidden', maxWidth: 'calc(100% - 45px)', textOverflow: 'ellipsis', whiteSpace: 'pre-line', overflowWrap: 'anywhere' }}>{title}</Typography> |                   style={{ | ||||||
|  |                     height: "50px", | ||||||
|  |                     width: "calc(100% - 25px)", | ||||||
|  |                     transform: "translate(25px)", | ||||||
|  |                   }} | ||||||
|  |                   className={this.props.classes.hoverLink} | ||||||
|  |                 > | ||||||
|  |                   <Typography | ||||||
|  |                     style={{ | ||||||
|  |                       margin: 0, | ||||||
|  |                       position: "absolute", | ||||||
|  |                       top: "50%", | ||||||
|  |                       transform: "translate(45px, -50%)", | ||||||
|  |                       maxHeight: "50px", | ||||||
|  |                       overflow: "hidden", | ||||||
|  |                       maxWidth: "calc(100% - 45px)", | ||||||
|  |                       textOverflow: "ellipsis", | ||||||
|  |                       whiteSpace: "pre-line", | ||||||
|  |                       overflowWrap: "anywhere", | ||||||
|  |                     }} | ||||||
|  |                   > | ||||||
|  |                     {title} | ||||||
|  |                   </Typography> | ||||||
|                 </div> |                 </div> | ||||||
|               </Link> |               </Link> | ||||||
|             ) |             ); | ||||||
|           } |           })} | ||||||
|           )} |  | ||||||
|         </List> |         </List> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Requirement.propTypes = { | Requirement.propTypes = { | ||||||
|   status: PropTypes.array.isRequired, |   status: PropTypes.array.isRequired, | ||||||
|   change: PropTypes.number.isRequired |   change: PropTypes.number.isRequired, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = (state) => ({ | ||||||
|   change: state.tutorial.change, |   change: state.tutorial.change, | ||||||
|   status: state.tutorial.status, |   status: state.tutorial.status, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(withRouter(Requirement))); | export default connect( | ||||||
|  |   mapStateToProps, | ||||||
|  |   null | ||||||
|  | )(withStyles(styles, { withTheme: true })(withRouter(Requirement))); | ||||||
|  | |||||||
| @ -3,9 +3,13 @@ import PropTypes from "prop-types"; | |||||||
| import { connect } from "react-redux"; | import { connect } from "react-redux"; | ||||||
| import { | import { | ||||||
|   getTutorials, |   getTutorials, | ||||||
|  |   getAllTutorials, | ||||||
|  |   getUserTutorials, | ||||||
|   resetTutorial, |   resetTutorial, | ||||||
|   tutorialProgress, |   tutorialProgress, | ||||||
| } from "../../actions/tutorialActions"; | } from "../../actions/tutorialActions"; | ||||||
|  | import { progress } from "../../actions/tutorialBuilderActions"; | ||||||
|  | 
 | ||||||
| import { clearMessages } from "../../actions/messageActions"; | import { clearMessages } from "../../actions/messageActions"; | ||||||
| 
 | 
 | ||||||
| import clsx from "clsx"; | import clsx from "clsx"; | ||||||
| @ -20,9 +24,20 @@ import Grid from "@material-ui/core/Grid"; | |||||||
| import Paper from "@material-ui/core/Paper"; | import Paper from "@material-ui/core/Paper"; | ||||||
| import Typography from "@material-ui/core/Typography"; | import Typography from "@material-ui/core/Typography"; | ||||||
| 
 | 
 | ||||||
| import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; | import { | ||||||
|  |   faCheck, | ||||||
|  |   faTimes, | ||||||
|  |   faShareAlt, | ||||||
|  |   faEye, | ||||||
|  |   faUserCheck, | ||||||
|  | } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| import * as Blockly from "blockly"; | import * as Blockly from "blockly"; | ||||||
|  | import ReactStars from "react-rating-stars-component"; | ||||||
|  | import Tooltip from "@material-ui/core/Tooltip"; | ||||||
|  | import IconButton from "@material-ui/core/IconButton"; | ||||||
|  | import Snackbar from "../Snackbar"; | ||||||
|  | import Divider from "@material-ui/core/Divider"; | ||||||
| 
 | 
 | ||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
|   outerDiv: { |   outerDiv: { | ||||||
| @ -51,26 +66,89 @@ const styles = (theme) => ({ | |||||||
|     verticalAlign: "middle", |     verticalAlign: "middle", | ||||||
|     textAlign: "center", |     textAlign: "center", | ||||||
|   }, |   }, | ||||||
|  |   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 TutorialHome extends Component { | class TutorialHome extends Component { | ||||||
|  |   constructor(props) { | ||||||
|  |     super(props); | ||||||
|  |     this.state = { | ||||||
|  |       userTutorials: [], | ||||||
|  |       tutorials: [], | ||||||
|  |       snackbar: false, | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   componentDidMount() { |   componentDidMount() { | ||||||
|     this.props.tutorialProgress(); |     this.props.tutorialProgress(); | ||||||
|     // retrieve tutorials only if a potential user is loaded - authentication
 |     // retrieve tutorials only if a potential user is loaded - authentication
 | ||||||
|     // is finished (success or failed)
 |     // is finished (success or failed)
 | ||||||
|     if (!this.props.progress) { |     // if (!this.props.progress) {
 | ||||||
|       this.props.getTutorials(); |     //   if (this.props.user) {
 | ||||||
|  |     //     if (this.props.user.blocklyRole === "admin") {
 | ||||||
|  |     //       this.props.getAllTutorials();
 | ||||||
|  |     //     }
 | ||||||
|  |     //     if (this.props.user.blocklyRole === "creator") {
 | ||||||
|  |     //       this.props.getUserTutorials();
 | ||||||
|  |     //       this.props.getTutorials();
 | ||||||
|  |     //       console.log("get user tutorials");
 | ||||||
|  |     //       console.log(this.props.userTutorials);
 | ||||||
|  |     //     }
 | ||||||
|  |     //   } else {
 | ||||||
|  |     //     this.props.getTutorials();
 | ||||||
|  |     //   }
 | ||||||
|  |     // }
 | ||||||
|  |     if (!this.props.authProgress) { | ||||||
|  |       if (this.props.user) { | ||||||
|  |         if (this.props.user.role === "admin") { | ||||||
|  |           this.props.getAllTutorials(); | ||||||
|  |         } else { | ||||||
|  |           this.props.getUserTutorials(); | ||||||
|  |           //this.props.getTutorials();
 | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         this.props.getTutorials(); | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate(props, state) { |   componentDidUpdate(props, state) { | ||||||
|     if (props.progress !== this.props.progress && !this.props.progress) { |     if ( | ||||||
|       // authentication is completed
 |       props.authProgress !== this.props.authProgress && | ||||||
|       this.props.getTutorials(); |       !this.props.authProgress | ||||||
|     } |     ) | ||||||
|  |       if (this.props.user) { | ||||||
|  |         if (this.props.user.role === "admin") { | ||||||
|  |           // authentication is completed
 | ||||||
|  |           this.props.getAllTutorials(); | ||||||
|  |         } else { | ||||||
|  |           this.props.getUserTutorials(); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         this.props.getTutorials(); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|     if (this.props.message.id === "GET_TUTORIALS_FAIL") { |     if (this.props.message.id === "GET_TUTORIALS_FAIL") { | ||||||
|       alert(this.props.message.msg); |       alert(this.props.message.msg); | ||||||
|     } |     } | ||||||
|  |     console.log(this.props.user); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillUnmount() { |   componentWillUnmount() { | ||||||
| @ -81,13 +159,25 @@ class TutorialHome extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|  |     var userTutorials = []; | ||||||
|  |     const publicTutorials = this.props.tutorials.filter( | ||||||
|  |       (tutorial) => tutorial.public === true | ||||||
|  |     ); | ||||||
|  |     if (this.props.user && this.props.user.blocklyRole === "admin") { | ||||||
|  |       userTutorials = this.props.tutorials; | ||||||
|  |     } | ||||||
|  |     if (this.props.user && this.props.user.blocklyRole === "creator") { | ||||||
|  |       userTutorials = this.props.tutorials.filter( | ||||||
|  |         (tutorial) => tutorial.creator === this.props.user.email | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|     return this.props.isLoading ? null : ( |     return this.props.isLoading ? null : ( | ||||||
|       <div> |       <div> | ||||||
|         <Breadcrumbs content={[{ link: "/tutorial", title: "Tutorial" }]} /> |         <Breadcrumbs content={[{ link: "/tutorial", title: "Tutorial" }]} /> | ||||||
| 
 |  | ||||||
|         <h1>{Blockly.Msg.tutorials_home_head}</h1> |         <h1>{Blockly.Msg.tutorials_home_head}</h1> | ||||||
|  |         <h2>Alle Tutorials</h2> | ||||||
|         <Grid container spacing={2}> |         <Grid container spacing={2}> | ||||||
|           {this.props.tutorials.map((tutorial, i) => { |           {publicTutorials.map((tutorial, i) => { | ||||||
|             var status = this.props.status.filter( |             var status = this.props.status.filter( | ||||||
|               (status) => status._id === tutorial._id |               (status) => status._id === tutorial._id | ||||||
|             )[0]; |             )[0]; | ||||||
| @ -99,6 +189,12 @@ class TutorialHome extends Component { | |||||||
|               tasks.length; |               tasks.length; | ||||||
|             var tutorialStatus = |             var tutorialStatus = | ||||||
|               success === 1 ? "Success" : error ? "Error" : "Other"; |               success === 1 ? "Success" : error ? "Error" : "Other"; | ||||||
|  |             const firstExample = { | ||||||
|  |               size: 30, | ||||||
|  |               value: tutorial.difficulty, | ||||||
|  |               edit: false, | ||||||
|  |               isHalf: true, | ||||||
|  |             }; | ||||||
|             return ( |             return ( | ||||||
|               <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> |               <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> | ||||||
|                 <Link |                 <Link | ||||||
| @ -114,6 +210,7 @@ class TutorialHome extends Component { | |||||||
|                     }} |                     }} | ||||||
|                   > |                   > | ||||||
|                     {tutorial.title} |                     {tutorial.title} | ||||||
|  |                     <ReactStars {...firstExample} /> | ||||||
|                     <div |                     <div | ||||||
|                       className={clsx(this.props.classes.outerDiv)} |                       className={clsx(this.props.classes.outerDiv)} | ||||||
|                       style={{ width: "160px", height: "160px", border: 0 }} |                       style={{ width: "160px", height: "160px", border: 0 }} | ||||||
| @ -216,6 +313,119 @@ class TutorialHome extends Component { | |||||||
|             ); |             ); | ||||||
|           })} |           })} | ||||||
|         </Grid> |         </Grid> | ||||||
|  |         {this.props.user ? ( | ||||||
|  |           <div> | ||||||
|  |             <h2>User Tutorials</h2> | ||||||
|  |             <Grid container spacing={2}> | ||||||
|  |               {userTutorials.map((tutorial, i) => { | ||||||
|  |                 var status = this.props.status.filter( | ||||||
|  |                   (status) => status._id === tutorial._id | ||||||
|  |                 )[0]; | ||||||
|  |                 var tasks = status.tasks; | ||||||
|  |                 var error = | ||||||
|  |                   status.tasks.filter((task) => task.type === "error").length > | ||||||
|  |                   0; | ||||||
|  |                 var success = | ||||||
|  |                   status.tasks.filter((task) => task.type === "success") | ||||||
|  |                     .length / tasks.length; | ||||||
|  |                 var tutorialStatus = | ||||||
|  |                   success === 1 ? "Success" : error ? "Error" : "Other"; | ||||||
|  |                 const firstExample = { | ||||||
|  |                   size: 30, | ||||||
|  |                   value: tutorial.difficulty, | ||||||
|  |                   edit: false, | ||||||
|  |                   isHalf: true, | ||||||
|  |                 }; | ||||||
|  |                 return ( | ||||||
|  |                   <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> | ||||||
|  |                     <Paper | ||||||
|  |                       style={{ | ||||||
|  |                         height: "150", | ||||||
|  |                         padding: "10px", | ||||||
|  |                         position: "relative", | ||||||
|  |                         overflow: "hidden", | ||||||
|  |                         backgroundColor: tutorial.review | ||||||
|  |                           ? "lightyellow" | ||||||
|  |                           : "white", | ||||||
|  |                       }} | ||||||
|  |                     > | ||||||
|  |                       <Link | ||||||
|  |                         to={`/tutorial/${tutorial._id}`} | ||||||
|  |                         style={{ textDecoration: "none", color: "inherit" }} | ||||||
|  |                       > | ||||||
|  |                         {tutorial.title} | ||||||
|  |                         <ReactStars {...firstExample} /> | ||||||
|  |                       </Link> | ||||||
|  |                       <Divider | ||||||
|  |                         style={{ | ||||||
|  |                           marginTop: "10px", | ||||||
|  |                           marginBottom: "10px", | ||||||
|  |                         }} | ||||||
|  |                       /> | ||||||
|  |                       <p> | ||||||
|  |                         Creator:{tutorial.creator} <br /> | ||||||
|  |                         <div style={this.props.style}> | ||||||
|  |                           <Tooltip | ||||||
|  |                             title={Blockly.Msg.tooltip_share_tutorial} | ||||||
|  |                             arrow | ||||||
|  |                           > | ||||||
|  |                             <IconButton | ||||||
|  |                               className={`shareTutorial ${this.props.classes.button}`} | ||||||
|  |                               onClick={() => { | ||||||
|  |                                 navigator.clipboard.writeText( | ||||||
|  |                                   `${window.location.origin}/tutorial/${tutorial._id}` | ||||||
|  |                                 ); | ||||||
|  |                                 this.setState({ | ||||||
|  |                                   snackbar: true, | ||||||
|  |                                   key: Date.now(), | ||||||
|  |                                   message: | ||||||
|  |                                     Blockly.Msg.messages_copylink_success, | ||||||
|  |                                   type: "success", | ||||||
|  |                                 }); | ||||||
|  |                               }} | ||||||
|  |                             > | ||||||
|  |                               <FontAwesomeIcon icon={faShareAlt} size="xs" /> | ||||||
|  |                             </IconButton> | ||||||
|  |                           </Tooltip> | ||||||
|  |                           <Tooltip | ||||||
|  |                             title={Blockly.Msg.tooltip_share_tutorial} | ||||||
|  |                             arrow | ||||||
|  |                           > | ||||||
|  |                             <IconButton | ||||||
|  |                               className={`publicTutorial ${this.props.classes.button}`} | ||||||
|  |                               disabled={!tutorial.public} | ||||||
|  |                             > | ||||||
|  |                               <FontAwesomeIcon icon={faEye} size="xs" /> | ||||||
|  |                             </IconButton> | ||||||
|  |                           </Tooltip> | ||||||
|  |                           {tutorial.review ? ( | ||||||
|  |                             <Tooltip | ||||||
|  |                               title={Blockly.Msg.tooltip_share_tutorial} | ||||||
|  |                               arrow | ||||||
|  |                             > | ||||||
|  |                               <IconButton | ||||||
|  |                                 className={`publicTutorial ${this.props.classes.button}`} | ||||||
|  |                                 disabled={!tutorial.review} | ||||||
|  |                               > | ||||||
|  |                                 <FontAwesomeIcon icon={faUserCheck} size="xs" /> | ||||||
|  |                               </IconButton> | ||||||
|  |                             </Tooltip> | ||||||
|  |                           ) : null} | ||||||
|  |                           <Snackbar | ||||||
|  |                             open={this.state.snackbar} | ||||||
|  |                             message={Blockly.Msg.messages_copylink_success} | ||||||
|  |                             type={this.state.type} | ||||||
|  |                             key={this.state.key} | ||||||
|  |                           /> | ||||||
|  |                         </div> | ||||||
|  |                       </p> | ||||||
|  |                     </Paper> | ||||||
|  |                   </Grid> | ||||||
|  |                 ); | ||||||
|  |               })} | ||||||
|  |             </Grid> | ||||||
|  |           </div> | ||||||
|  |         ) : null} | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @ -223,6 +433,8 @@ class TutorialHome extends Component { | |||||||
| 
 | 
 | ||||||
| TutorialHome.propTypes = { | TutorialHome.propTypes = { | ||||||
|   getTutorials: PropTypes.func.isRequired, |   getTutorials: PropTypes.func.isRequired, | ||||||
|  |   getAllTutorials: PropTypes.func.isRequired, | ||||||
|  |   getUserTutorials: PropTypes.func.isRequired, | ||||||
|   resetTutorial: PropTypes.func.isRequired, |   resetTutorial: PropTypes.func.isRequired, | ||||||
|   tutorialProgress: PropTypes.func.isRequired, |   tutorialProgress: PropTypes.func.isRequired, | ||||||
|   clearMessages: PropTypes.func.isRequired, |   clearMessages: PropTypes.func.isRequired, | ||||||
| @ -232,19 +444,27 @@ TutorialHome.propTypes = { | |||||||
|   isLoading: PropTypes.bool.isRequired, |   isLoading: PropTypes.bool.isRequired, | ||||||
|   message: PropTypes.object.isRequired, |   message: PropTypes.object.isRequired, | ||||||
|   progress: PropTypes.bool.isRequired, |   progress: PropTypes.bool.isRequired, | ||||||
|  |   user: PropTypes.object.isRequired, | ||||||
|  |   authProgress: PropTypes.bool.isRequired, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state) => ({ | const mapStateToProps = (state) => ({ | ||||||
|   change: state.tutorial.change, |   change: state.tutorial.change, | ||||||
|   status: state.tutorial.status, |   status: state.tutorial.status, | ||||||
|   tutorials: state.tutorial.tutorials, |   tutorials: state.tutorial.tutorials, | ||||||
|  |   userTutorials: state.tutorial.userTutorials, | ||||||
|   isLoading: state.tutorial.progress, |   isLoading: state.tutorial.progress, | ||||||
|   message: state.message, |   message: state.message, | ||||||
|   progress: state.auth.progress, |   progress: state.auth.progress, | ||||||
|  |   user: state.auth.user, | ||||||
|  |   authProgress: state.auth.progress, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, { | export default connect(mapStateToProps, { | ||||||
|   getTutorials, |   getTutorials, | ||||||
|  |   progress, | ||||||
|  |   getUserTutorials, | ||||||
|  |   getAllTutorials, | ||||||
|   resetTutorial, |   resetTutorial, | ||||||
|   clearMessages, |   clearMessages, | ||||||
|   tutorialProgress, |   tutorialProgress, | ||||||
|  | |||||||
| @ -1,72 +1,87 @@ | |||||||
| 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 { shareProject } from '../../actions/projectActions'; | import { shareProject } from "../../actions/projectActions"; | ||||||
| import { clearMessages } from '../../actions/messageActions'; | import { clearMessages } from "../../actions/messageActions"; | ||||||
| 
 | 
 | ||||||
| import moment from 'moment'; | import moment from "moment"; | ||||||
| 
 | 
 | ||||||
| import Dialog from '../Dialog'; | import Dialog from "../Dialog"; | ||||||
| import Snackbar from '../Snackbar'; | import Snackbar from "../Snackbar"; | ||||||
| 
 | 
 | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from "react-router-dom"; | ||||||
| 
 | 
 | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from "@material-ui/core/styles"; | ||||||
| import IconButton from '@material-ui/core/IconButton'; | import IconButton from "@material-ui/core/IconButton"; | ||||||
| import Tooltip from '@material-ui/core/Tooltip'; | import Tooltip from "@material-ui/core/Tooltip"; | ||||||
| import Typography from '@material-ui/core/Typography'; | import Typography from "@material-ui/core/Typography"; | ||||||
| 
 | 
 | ||||||
| import { faShareAlt, faCopy } from "@fortawesome/free-solid-svg-icons"; | import { faShareAlt, faCopy } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| 
 | 
 | ||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from "blockly/core"; | ||||||
| 
 | 
 | ||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
|   button: { |   button: { | ||||||
|     backgroundColor: theme.palette.primary.main, |     backgroundColor: theme.palette.primary.main, | ||||||
|     color: theme.palette.primary.contrastText, |     color: theme.palette.primary.contrastText, | ||||||
|     width: '40px', |     width: "40px", | ||||||
|     height: '40px', |     height: "40px", | ||||||
|     '&:hover': { |     "&:hover": { | ||||||
|       backgroundColor: theme.palette.primary.main, |       backgroundColor: theme.palette.primary.main, | ||||||
|       color: theme.palette.primary.contrastText, |       color: theme.palette.primary.contrastText, | ||||||
|     } |     }, | ||||||
|   }, |   }, | ||||||
|   link: { |   link: { | ||||||
|     color: theme.palette.primary.main, |     color: theme.palette.primary.main, | ||||||
|     textDecoration: 'none', |     textDecoration: "none", | ||||||
|     '&:hover': { |     "&:hover": { | ||||||
|       color: theme.palette.primary.main, |       color: theme.palette.primary.main, | ||||||
|       textDecoration: 'underline' |       textDecoration: "underline", | ||||||
|     } |     }, | ||||||
|   } |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| class WorkspaceFunc extends Component { | class WorkspaceFunc extends Component { | ||||||
| 
 |  | ||||||
|   constructor(props) { |   constructor(props) { | ||||||
|     super(props); |     super(props); | ||||||
|     this.inputRef = React.createRef(); |     this.inputRef = React.createRef(); | ||||||
|     this.state = { |     this.state = { | ||||||
|       snackbar: false, |       snackbar: false, | ||||||
|       type: '', |       type: "", | ||||||
|       key: '', |       key: "", | ||||||
|       message: '', |       message: "", | ||||||
|       title: '', |       title: "", | ||||||
|       content: '', |       content: "", | ||||||
|       open: false, |       open: false, | ||||||
|       id: '', |       id: "", | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate(props) { |   componentDidUpdate(props) { | ||||||
|     if (this.props.message !== props.message) { |     if (this.props.message !== props.message) { | ||||||
|       if (this.props.message.id === 'SHARE_SUCCESS' && (!this.props.multiple || this.props.message.status === this.props.project._id)) { |       if ( | ||||||
|         this.setState({ share: true, open: true, title: Blockly.Msg.messages_SHARE_SUCCESS, id: this.props.message.status }); |         this.props.message.id === "SHARE_SUCCESS" && | ||||||
|       } |         (!this.props.multiple || | ||||||
|       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) | ||||||
|         this.setState({ snackbar: true, key: Date.now(), message: Blockly.Msg.messages_SHARE_FAIL, type: 'error' }); |       ) { | ||||||
|  |         this.setState({ | ||||||
|  |           share: true, | ||||||
|  |           open: true, | ||||||
|  |           title: Blockly.Msg.messages_SHARE_SUCCESS, | ||||||
|  |           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.setState({ | ||||||
|  |           snackbar: true, | ||||||
|  |           key: Date.now(), | ||||||
|  |           message: Blockly.Msg.messages_SHARE_FAIL, | ||||||
|  |           type: "error", | ||||||
|  |         }); | ||||||
|         window.scrollTo(0, 0); |         window.scrollTo(0, 0); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @ -77,18 +92,25 @@ class WorkspaceFunc extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toggleDialog = () => { |   toggleDialog = () => { | ||||||
|     this.setState({ open: !this.state, title: '', content: '' }); |     this.setState({ open: !this.state, title: "", content: "" }); | ||||||
|   } |   }; | ||||||
| 
 | 
 | ||||||
|   shareBlocks = () => { |   shareBlocks = () => { | ||||||
|     if (this.props.projectType === 'project' && this.props.project.shared) { |     if (this.props.projectType === "project" && this.props.project.shared) { | ||||||
|       // project is already shared
 |       // project is already shared
 | ||||||
|       this.setState({ open: true, title: Blockly.Msg.messages_SHARE_SUCCESS, id: this.props.project._id }); |       this.setState({ | ||||||
|  |         open: true, | ||||||
|  |         title: Blockly.Msg.messages_SHARE_SUCCESS, | ||||||
|  |         id: this.props.project._id, | ||||||
|  |       }); | ||||||
|  |     } else { | ||||||
|  |       this.props.shareProject( | ||||||
|  |         this.props.name || this.props.project.title, | ||||||
|  |         this.props.projectType, | ||||||
|  |         this.props.project ? this.props.project._id : undefined | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
|     else { |   }; | ||||||
|       this.props.shareProject(this.props.name || this.props.project.title, this.props.projectType, this.props.project ? this.props.project._id : undefined); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
| @ -116,43 +138,89 @@ class WorkspaceFunc extends Component { | |||||||
|           onClick={this.toggleDialog} |           onClick={this.toggleDialog} | ||||||
|           button={Blockly.Msg.button_close} |           button={Blockly.Msg.button_close} | ||||||
|         > |         > | ||||||
|           <div style={{ marginTop: '10px' }}> |           <div style={{ marginTop: "10px" }}> | ||||||
|             <Typography>Über den folgenden Link kannst du dein Programm teilen:</Typography> |             <Typography> | ||||||
|             <Link to={`/share/${this.state.id}`} onClick={() => this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`}</Link> |               Über den folgenden Link kannst du dein Programm teilen: | ||||||
|             <Tooltip title={Blockly.Msg.tooltip_copy_link} arrow style={{ marginRight: '5px' }}> |             </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={Blockly.Msg.tooltip_copy_link} | ||||||
|  |               arrow | ||||||
|  |               style={{ marginRight: "5px" }} | ||||||
|  |             > | ||||||
|               <IconButton |               <IconButton | ||||||
|                 onClick={() => { |                 onClick={() => { | ||||||
|                   navigator.clipboard.writeText(`${window.location.origin}/share/${this.state.id}`); |                   navigator.clipboard.writeText( | ||||||
|                   this.setState({ snackbar: true, key: Date.now(), message: Blockly.Msg.messages_copylink_success, type: 'success' }); |                     `${window.location.origin}/share/${this.state.id}` | ||||||
|  |                   ); | ||||||
|  |                   this.setState({ | ||||||
|  |                     snackbar: true, | ||||||
|  |                     key: Date.now(), | ||||||
|  |                     message: Blockly.Msg.messages_copylink_success, | ||||||
|  |                     type: "success", | ||||||
|  |                   }); | ||||||
|                 }} |                 }} | ||||||
|               > |               > | ||||||
|                 <FontAwesomeIcon icon={faCopy} size="xs" /> |                 <FontAwesomeIcon icon={faCopy} size="xs" /> | ||||||
|               </IconButton> |               </IconButton> | ||||||
|             </Tooltip> |             </Tooltip> | ||||||
|             {this.props.project && this.props.project.shared && this.props.message.id !== 'SHARE_SUCCESS' ? |             {this.props.project && | ||||||
|               <Typography variant='body2' style={{ marginTop: '20px' }}>{`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${moment(this.props.project.shared).diff(moment().utc(), 'days') === 0 ? |             this.props.project.shared && | ||||||
|                 moment(this.props.project.shared).diff(moment().utc(), 'hours') === 0 ? |             this.props.message.id !== "SHARE_SUCCESS" ? ( | ||||||
|                   `${moment(this.props.project.shared).diff(moment().utc(), 'minutes')} Minuten` |               <Typography | ||||||
|                   : `${moment(this.props.project.shared).diff(moment().utc(), 'hours')} Stunden` |                 variant="body2" | ||||||
|                 : `${moment(this.props.project.shared).diff(moment().utc(), 'days')} Tage`} gültig.`}</Typography>
 |                 style={{ marginTop: "20px" }} | ||||||
|               : <Typography variant='body2' style={{ marginTop: '20px' }}>{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}</Typography>} |               >{`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${ | ||||||
|  |                 moment(this.props.project.shared).diff( | ||||||
|  |                   moment().utc(), | ||||||
|  |                   "days" | ||||||
|  |                 ) === 0 | ||||||
|  |                   ? moment(this.props.project.shared).diff( | ||||||
|  |                       moment().utc(), | ||||||
|  |                       "hours" | ||||||
|  |                     ) === 0 | ||||||
|  |                     ? `${moment(this.props.project.shared).diff( | ||||||
|  |                         moment().utc(), | ||||||
|  |                         "minutes" | ||||||
|  |                       )} Minuten` | ||||||
|  |                     : `${moment(this.props.project.shared).diff( | ||||||
|  |                         moment().utc(), | ||||||
|  |                         "hours" | ||||||
|  |                       )} Stunden` | ||||||
|  |                   : `${moment(this.props.project.shared).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> |           </div> | ||||||
|         </Dialog> |         </Dialog> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| WorkspaceFunc.propTypes = { | WorkspaceFunc.propTypes = { | ||||||
|   shareProject: PropTypes.func.isRequired, |   shareProject: PropTypes.func.isRequired, | ||||||
|   clearMessages: PropTypes.func.isRequired, |   clearMessages: PropTypes.func.isRequired, | ||||||
|   name: PropTypes.string.isRequired, |   name: PropTypes.string.isRequired, | ||||||
|   message: PropTypes.object.isRequired |   message: PropTypes.object.isRequired, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = (state) => ({ | ||||||
|   name: state.workspace.name, |   name: state.workspace.name, | ||||||
|   message: state.message |   message: state.message, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, { shareProject, clearMessages })(withStyles(styles, { withTheme: true })(WorkspaceFunc)); | export default connect(mapStateToProps, { shareProject, clearMessages })( | ||||||
|  |   withStyles(styles, { withTheme: true })(WorkspaceFunc) | ||||||
|  | ); | ||||||
|  | |||||||
| @ -10,6 +10,9 @@ import { | |||||||
|   BUILDER_CHANGE_STEP, |   BUILDER_CHANGE_STEP, | ||||||
|   BUILDER_CHANGE_ORDER, |   BUILDER_CHANGE_ORDER, | ||||||
|   BUILDER_DELETE_PROPERTY, |   BUILDER_DELETE_PROPERTY, | ||||||
|  |   BUILDER_DIFFICULTY, | ||||||
|  |   BUILDER_PUBLIC, | ||||||
|  |   BUILDER_REVIEW, | ||||||
| } from "../actions/types"; | } from "../actions/types"; | ||||||
| 
 | 
 | ||||||
| const initialState = { | const initialState = { | ||||||
| @ -17,6 +20,9 @@ const initialState = { | |||||||
|   progress: false, |   progress: false, | ||||||
|   json: "", |   json: "", | ||||||
|   title: "", |   title: "", | ||||||
|  |   difficulty: 0, | ||||||
|  |   public: false, | ||||||
|  |   review: false, | ||||||
|   id: "", |   id: "", | ||||||
|   steps: [ |   steps: [ | ||||||
|     { |     { | ||||||
| @ -45,6 +51,21 @@ export default function foo(state = initialState, action) { | |||||||
|         ...state, |         ...state, | ||||||
|         title: action.payload, |         title: action.payload, | ||||||
|       }; |       }; | ||||||
|  |     case BUILDER_PUBLIC: | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         public: action.payload, | ||||||
|  |       }; | ||||||
|  |     case BUILDER_DIFFICULTY: | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         difficulty: action.payload, | ||||||
|  |       }; | ||||||
|  |     case BUILDER_REVIEW: | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         review: action.payload, | ||||||
|  |       }; | ||||||
|     case BUILDER_ID: |     case BUILDER_ID: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|  | |||||||
| @ -1,24 +1,23 @@ | |||||||
| import { TUTORIAL_PROGRESS, GET_TUTORIAL, GET_TUTORIALS, GET_STATUS, TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_STEP } from '../actions/types'; | import { | ||||||
| 
 |   TUTORIAL_PROGRESS, | ||||||
| 
 |   GET_TUTORIAL, | ||||||
| //
 |   GET_TUTORIALS, | ||||||
| // const initialStatus = () => {
 |   GET_USERTUTORIALS, | ||||||
| //   if(store.getState().auth.user){
 |   GET_STATUS, | ||||||
| //     return store.getState().auth.user.status || []
 |   TUTORIAL_SUCCESS, | ||||||
| //   }
 |   TUTORIAL_ERROR, | ||||||
| //   else if (window.localStorage.getItem('status')) {
 |   TUTORIAL_CHANGE, | ||||||
| //     var status = JSON.parse(window.localStorage.getItem('status'));
 |   TUTORIAL_XML, | ||||||
| //     return status;
 |   TUTORIAL_STEP, | ||||||
| //   }
 | } from "../actions/types"; | ||||||
| //   return [];
 |  | ||||||
| // };
 |  | ||||||
| 
 | 
 | ||||||
| const initialState = { | const initialState = { | ||||||
|   status: [], |   status: [], | ||||||
|   activeStep: 0, |   activeStep: 0, | ||||||
|   change: 0, |   change: 0, | ||||||
|   tutorials: [], |   tutorials: [], | ||||||
|   progress: false |   userTutorials: [], | ||||||
|  |   progress: false, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function foo(state = initialState, action) { | export default function foo(state = initialState, action) { | ||||||
| @ -26,18 +25,23 @@ export default function foo(state = initialState, action) { | |||||||
|     case TUTORIAL_PROGRESS: |     case TUTORIAL_PROGRESS: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         progress: !state.progress |         progress: !state.progress, | ||||||
|       } |       }; | ||||||
|     case GET_TUTORIALS: |     case GET_TUTORIALS: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         tutorials: action.payload |         tutorials: action.payload, | ||||||
|  |       }; | ||||||
|  |     case GET_USERTUTORIALS: | ||||||
|  |       return { | ||||||
|  |         ...state, | ||||||
|  |         tutorials: action.payload, | ||||||
|       }; |       }; | ||||||
|     case GET_TUTORIAL: |     case GET_TUTORIAL: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         tutorials: [action.payload] |         tutorials: [action.payload], | ||||||
|       } |       }; | ||||||
|     case TUTORIAL_SUCCESS: |     case TUTORIAL_SUCCESS: | ||||||
|     case TUTORIAL_ERROR: |     case TUTORIAL_ERROR: | ||||||
|     case TUTORIAL_XML: |     case TUTORIAL_XML: | ||||||
| @ -46,23 +50,23 @@ export default function foo(state = initialState, action) { | |||||||
|       // and 'TUTORIAL_XML' the function 'updateStatus' is called
 |       // and 'TUTORIAL_XML' the function 'updateStatus' is called
 | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         status: action.payload |         status: action.payload, | ||||||
|       }; |       }; | ||||||
|     case GET_STATUS: |     case GET_STATUS: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         status: action.payload |         status: action.payload, | ||||||
|       }; |       }; | ||||||
|     case TUTORIAL_CHANGE: |     case TUTORIAL_CHANGE: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         change: state.change += 1 |         change: (state.change += 1), | ||||||
|       } |       }; | ||||||
|     case TUTORIAL_STEP: |     case TUTORIAL_STEP: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         activeStep: action.payload |         activeStep: action.payload, | ||||||
|       } |       }; | ||||||
|     default: |     default: | ||||||
|       return state; |       return state; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { createStore, applyMiddleware, compose } from 'redux'; | import { createStore, applyMiddleware, compose } from "redux"; | ||||||
| import thunk from 'redux-thunk'; | import thunk from "redux-thunk"; | ||||||
| import rootReducer from './reducers'; | import rootReducer from "./reducers"; | ||||||
| 
 | 
 | ||||||
| const initialState = {}; | const initialState = {}; | ||||||
| 
 | 
 | ||||||
| @ -11,7 +11,7 @@ const store = createStore( | |||||||
|   initialState, |   initialState, | ||||||
|   compose( |   compose( | ||||||
|     applyMiddleware(...middleware), |     applyMiddleware(...middleware), | ||||||
|     // window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
 |     window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() | ||||||
|   ) |   ) | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user