change existing tutorial with Tutorial-Builder
This commit is contained in:
		
							parent
							
								
									c8f01f8273
								
							
						
					
					
						commit
						d0dda4038e
					
				| @ -180,7 +180,7 @@ export const setSubmitError = () => (dispatch, getState) => { | ||||
|   // if(builder.id === undefined || builder.id === ''){
 | ||||
|   //   dispatch(setError(undefined, 'id'));
 | ||||
|   // }
 | ||||
|   if (builder.id === undefined || builder.title === '') { | ||||
|   if (builder.title === '') { | ||||
|     dispatch(setError(undefined, 'title')); | ||||
|   } | ||||
|   var type = builder.steps.map((step, i) => { | ||||
| @ -243,7 +243,7 @@ export const progress = (inProgress) => (dispatch) => { | ||||
| export const resetTutorial = () => (dispatch, getState) => { | ||||
|   dispatch(jsonString('')); | ||||
|   dispatch(tutorialTitle('')); | ||||
|   dispatch(tutorialId('')); | ||||
|   // dispatch(tutorialId(''));
 | ||||
|   var steps = [ | ||||
|     { | ||||
|       id: 1, | ||||
| @ -274,7 +274,7 @@ export const readJSON = (json) => (dispatch, getState) => { | ||||
|   // accept only valid attributes
 | ||||
|   var steps = json.steps.map((step, i) => { | ||||
|     var object = { | ||||
|       id: step.id, | ||||
|       // id: step.id,
 | ||||
|       type: step.type, | ||||
|       headline: step.headline, | ||||
|       text: step.text | ||||
| @ -298,7 +298,7 @@ export const readJSON = (json) => (dispatch, getState) => { | ||||
|     return object; | ||||
|   }); | ||||
|   dispatch(tutorialTitle(json.title)); | ||||
|   dispatch(tutorialId(json.id)); | ||||
|   // dispatch(tutorialId(json.id));
 | ||||
|   dispatch(tutorialSteps(steps)); | ||||
|   dispatch(setSubmitError()); | ||||
|   dispatch(progress(false)); | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { checkError, readJSON, jsonString, progress, resetTutorial } from '../../../actions/tutorialBuilderActions'; | ||||
| import { checkError, readJSON, jsonString, progress, tutorialId, resetTutorial as resetTutorialBuilder} from '../../../actions/tutorialBuilderActions'; | ||||
| import { getTutorials, resetTutorial} from '../../../actions/tutorialActions'; | ||||
| import { clearMessages } from '../../../actions/messageActions'; | ||||
| 
 | ||||
| import axios from 'axios'; | ||||
| 
 | ||||
| @ -20,6 +22,13 @@ import Backdrop from '@material-ui/core/Backdrop'; | ||||
| import CircularProgress from '@material-ui/core/CircularProgress'; | ||||
| import Divider from '@material-ui/core/Divider'; | ||||
| import FormHelperText from '@material-ui/core/FormHelperText'; | ||||
| import Radio from '@material-ui/core/Radio'; | ||||
| import RadioGroup from '@material-ui/core/RadioGroup'; | ||||
| import FormControlLabel from '@material-ui/core/FormControlLabel'; | ||||
| import InputLabel from '@material-ui/core/InputLabel'; | ||||
| import MenuItem from '@material-ui/core/MenuItem'; | ||||
| import FormControl from '@material-ui/core/FormControl'; | ||||
| import Select from '@material-ui/core/Select'; | ||||
| 
 | ||||
| const styles = (theme) => ({ | ||||
|   backdrop: { | ||||
| @ -36,6 +45,7 @@ class Builder extends Component { | ||||
|   constructor(props) { | ||||
|     super(props); | ||||
|     this.state = { | ||||
|       tutorial: 'new', | ||||
|       open: false, | ||||
|       title: '', | ||||
|       content: '', | ||||
| @ -47,65 +57,23 @@ class Builder extends Component { | ||||
|     this.inputRef = React.createRef(); | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this.props.getTutorials(); | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate(props, state) { | ||||
|     if(this.props.message.id === 'GET_TUTORIALS_FAIL'){ | ||||
|       alert(this.props.message.msg); | ||||
|       this.props.clearMessages(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount() { | ||||
|     this.reset(); | ||||
|   } | ||||
| 
 | ||||
|   submit = () => { | ||||
|     var isError = this.props.checkError(); | ||||
|     if (isError) { | ||||
|       this.setState({ snackbar: true, key: Date.now(), message: `Die Angaben für das Tutorial sind nicht vollständig.`, type: 'error' }); | ||||
|       window.scrollTo(0, 0); | ||||
|     } | ||||
|     else { | ||||
|       // export steps without attribute 'url'
 | ||||
|       var steps = this.props.steps; | ||||
|       var newTutorial = new FormData(); | ||||
|       newTutorial.append('title', this.props.title); | ||||
|       steps.forEach((step, i) => { | ||||
|         newTutorial.append(`steps[${i}][type]`, step.type); | ||||
|         newTutorial.append(`steps[${i}][headline]`, step.headline); | ||||
|         newTutorial.append(`steps[${i}][text]`, step.text); | ||||
|         if(i === 0 && step.type === 'instruction'){ | ||||
|           if(step.requirements){ // optional
 | ||||
|             step.requirements.forEach((requirement, j) => { | ||||
|               newTutorial.append(`steps[${i}][requirements][${j}]`, requirement); | ||||
|             }); | ||||
|           } | ||||
|           step.hardware.forEach((hardware, j) => { | ||||
|             newTutorial.append(`steps[${i}][hardware][${j}]`, hardware); | ||||
|           }); | ||||
|         } | ||||
|         if(step.xml){ // optional
 | ||||
|           newTutorial.append(`steps[${i}][xml]`, step.xml); | ||||
|         } | ||||
|         if(step.media){ // optional
 | ||||
|           if(step.media.youtube){ | ||||
|             newTutorial.append(`steps[${i}][media][youtube]`, step.media.youtube); | ||||
|           } | ||||
|           if(step.media.picture){ | ||||
|             newTutorial.append(`steps[${i}][media][picture]`, step.media.picture); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|       axios.post(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/`, newTutorial) | ||||
|         .then(res => { | ||||
|           var tutorial = res.data.tutorial; | ||||
|           this.props.history.push(`/tutorial/${tutorial._id}`); | ||||
|         }) | ||||
|         .catch(err => { | ||||
|           this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Erstellen des Tutorials. Versuche es noch einmal.`, type: 'error' }); | ||||
|           window.scrollTo(0, 0); | ||||
|         }); | ||||
|       // var blob = new Blob([JSON.stringify(tutorial)], { type: 'text/json' });
 | ||||
|       // saveAs(blob, `${detectWhitespacesAndReturnReadableResult(tutorial.title)}.json`);
 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   reset = () => { | ||||
|     this.resetFull(); | ||||
|     this.props.resetTutorial(); | ||||
|     this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich zurückgesetzt.`, type: 'success' }); | ||||
|     window.scrollTo(0, 0); | ||||
|     if(this.props.message.msg){ | ||||
|       this.props.clearMessages(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   uploadJsonFile = (jsonFile) => { | ||||
| @ -153,6 +121,104 @@ class Builder extends Component { | ||||
|     this.setState({ open: !this.state }); | ||||
|   } | ||||
| 
 | ||||
|   onChange = (value) => { | ||||
|     this.props.resetTutorialBuilder(); | ||||
|     this.props.tutorialId(''); | ||||
|     this.setState({tutorial: value}); | ||||
|   } | ||||
| 
 | ||||
|   onChangeId = (value) => { | ||||
|     this.props.tutorialId(value); | ||||
|     this.props.progress(true); | ||||
|     var tutorial = this.props.tutorials.filter(tutorial => tutorial._id === value)[0]; | ||||
|     this.props.readJSON(tutorial); | ||||
|     this.setState({ snackbar: true, key: Date.now(), message: `Das ausgewählte Tutorial "${tutorial.title}" wurde erfolgreich übernommen.`, type: 'success' }); | ||||
|   } | ||||
| 
 | ||||
|   resetFull = () => { | ||||
|     this.props.resetTutorialBuilder(); | ||||
|     this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich zurückgesetzt.`, type: 'success' }); | ||||
|     window.scrollTo(0, 0); | ||||
|   } | ||||
| 
 | ||||
|   resetTutorial = () => { | ||||
|     var tutorial = this.props.tutorials.filter(tutorial => tutorial._id === this.props.id)[0]; | ||||
|     this.props.readJSON(tutorial); | ||||
|     this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial ${tutorial.title} wurde erfolgreich auf den ursprünglichen Stand zurückgesetzt.`, type: 'success' }); | ||||
|     window.scrollTo(0, 0); | ||||
|   } | ||||
| 
 | ||||
|   submit = () => { | ||||
|     var isError = this.props.checkError(); | ||||
|     if (isError) { | ||||
|       this.setState({ snackbar: true, key: Date.now(), message: `Die Angaben für das Tutorial sind nicht vollständig.`, type: 'error' }); | ||||
|       window.scrollTo(0, 0); | ||||
|       return false; | ||||
|     } | ||||
|     else { | ||||
|       // export steps without attribute 'url'
 | ||||
|       var steps = this.props.steps; | ||||
|       var newTutorial = new FormData(); | ||||
|       newTutorial.append('title', this.props.title); | ||||
|       steps.forEach((step, i) => { | ||||
|         newTutorial.append(`steps[${i}][type]`, step.type); | ||||
|         newTutorial.append(`steps[${i}][headline]`, step.headline); | ||||
|         newTutorial.append(`steps[${i}][text]`, step.text); | ||||
|         if(i === 0 && step.type === 'instruction'){ | ||||
|           if(step.requirements){ // optional
 | ||||
|             step.requirements.forEach((requirement, j) => { | ||||
|               newTutorial.append(`steps[${i}][requirements][${j}]`, requirement); | ||||
|             }); | ||||
|           } | ||||
|           step.hardware.forEach((hardware, j) => { | ||||
|             newTutorial.append(`steps[${i}][hardware][${j}]`, hardware); | ||||
|           }); | ||||
|         } | ||||
|         if(step.xml){ // optional
 | ||||
|           newTutorial.append(`steps[${i}][xml]`, step.xml); | ||||
|         } | ||||
|         if(step.media){ // optional
 | ||||
|           if(step.media.youtube){ | ||||
|             newTutorial.append(`steps[${i}][media][youtube]`, step.media.youtube); | ||||
|           } | ||||
|           if(step.media.picture){ | ||||
|             newTutorial.append(`steps[${i}][media][picture]`, step.media.picture); | ||||
|           } | ||||
|         } | ||||
|       }); | ||||
|       return newTutorial; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   submitNew = () => { | ||||
|     var newTutorial = this.submit(); | ||||
|     if(newTutorial){ | ||||
|       axios.post(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/`, newTutorial) | ||||
|         .then(res => { | ||||
|           var tutorial = res.data.tutorial; | ||||
|           this.props.history.push(`/tutorial/${tutorial._id}`); | ||||
|         }) | ||||
|         .catch(err => { | ||||
|           this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Erstellen des Tutorials. Versuche es noch einmal.`, type: 'error' }); | ||||
|           window.scrollTo(0, 0); | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   submitUpdate = () => { | ||||
|     var updatedTutorial = this.submit(); | ||||
|     if(updatedTutorial){ | ||||
|       axios.put(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/${this.props.id}`, updatedTutorial) | ||||
|         .then(res => { | ||||
|           var tutorial = res.data.tutorial; | ||||
|           this.props.history.push(`/tutorial/${tutorial._id}`); | ||||
|         }) | ||||
|         .catch(err => { | ||||
|           this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Ändern des Tutorials. Versuche es noch einmal.`, type: 'error' }); | ||||
|           window.scrollTo(0, 0); | ||||
|         }); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render() { | ||||
|     return ( | ||||
| @ -161,7 +227,26 @@ class Builder extends Component { | ||||
| 
 | ||||
|         <h1>Tutorial-Builder</h1> | ||||
| 
 | ||||
|         {/*upload JSON*/} | ||||
|         <RadioGroup row value={this.state.tutorial} onChange={(e) => this.onChange(e.target.value)}> | ||||
|           <FormControlLabel style={{color: 'black'}} | ||||
|             value="new" | ||||
|             control={<Radio color="primary" />} | ||||
|             label="neues Tutorial erstellen" | ||||
|             labelPlacement="end" | ||||
|           /> | ||||
|           <FormControlLabel style={{color: 'black'}} | ||||
|             disabled={this.props.index === 0} | ||||
|             value="change" | ||||
|             control={<Radio color="primary" />} | ||||
|             label="bestehendes Tutorial ändern" | ||||
|             labelPlacement="end" | ||||
|           /> | ||||
|         </RadioGroup> | ||||
| 
 | ||||
|         <Divider variant='fullWidth' style={{ margin: '10px 0 15px 0' }} /> | ||||
| 
 | ||||
|         {this.state.tutorial === 'new' ? | ||||
|           /*upload JSON*/ | ||||
|           <div ref={this.inputRef}> | ||||
|             <input | ||||
|               style={{ display: 'none' }} | ||||
| @ -175,9 +260,27 @@ class Builder extends Component { | ||||
|             </label> | ||||
|             <Button style={{ marginRight: '10px', marginBottom: '10px' }} variant='contained' color='primary' onClick={() => this.uploadJsonString()}>String laden</Button> | ||||
|           </div> | ||||
|         : <FormControl variant="outlined" style={{width: '100%'}}> | ||||
|             <InputLabel id="select-outlined-label">Tutorial</InputLabel> | ||||
|             <Select | ||||
|               color='primary' | ||||
|               labelId="select-outlined-label" | ||||
|               defaultValue={this.props.id} | ||||
|               onChange={(e) => this.onChangeId(e.target.value)} | ||||
|               label="Tutorial" | ||||
|             > | ||||
|               {this.props.tutorials.map(tutorial => | ||||
|                 <MenuItem value={tutorial._id}>{tutorial.title}</MenuItem> | ||||
|               )} | ||||
|             </Select> | ||||
|           </FormControl> | ||||
|         } | ||||
| 
 | ||||
|         <Divider variant='fullWidth' style={{ margin: '10px 0 15px 0' }} /> | ||||
| 
 | ||||
|         {/*Tutorial-Builder-Form*/} | ||||
|         {this.state.tutorial === 'new' || (this.state.tutorial === 'change' && this.props.id !== '') ? | ||||
|         /*Tutorial-Builder-Form*/ | ||||
|         <div> | ||||
|           {this.props.error.type ? | ||||
|             <FormHelperText style={{ lineHeight: 'initial' }} className={this.props.classes.errorColor}>{`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`}</FormHelperText> | ||||
|             : null} | ||||
| @ -190,12 +293,22 @@ class Builder extends Component { | ||||
| 
 | ||||
|           {/*submit or reset*/} | ||||
|           <Divider variant='fullWidth' style={{ margin: '30px 0 10px 0' }} /> | ||||
|         <Button style={{ marginRight: '10px', marginTop: '10px' }} variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button> | ||||
|         <Button style={{ marginTop: '10px' }} variant='contained' onClick={() => this.reset()}>Zurücksetzen</Button> | ||||
|           {this.state.tutorial === 'new' ? | ||||
|             <div> | ||||
|               <Button style={{ marginRight: '10px', marginTop: '10px' }} variant='contained' color='primary' onClick={() => this.submitNew()}>Tutorial erstellen</Button> | ||||
|               <Button style={{ marginTop: '10px' }} variant='contained' onClick={() => this.resetFull()}>Zurücksetzen</Button> | ||||
|             </div> | ||||
|           : <div> | ||||
|             <Button style={{ marginRight: '10px', marginTop: '10px' }} variant='contained' color='primary' onClick={() => this.submitUpdate()}>Tutorial ändern</Button> | ||||
|             <Button style={{ marginTop: '10px' }} variant='contained' onClick={() => this.resetTutorial()}>Zurücksetzen</Button> | ||||
|           </div> | ||||
|           } | ||||
| 
 | ||||
|           <Backdrop className={this.props.classes.backdrop} open={this.props.isProgress}> | ||||
|             <CircularProgress color="inherit" /> | ||||
|           </Backdrop> | ||||
|         </div> | ||||
|         : null} | ||||
| 
 | ||||
|         <Dialog | ||||
|           open={this.state.open} | ||||
| @ -233,17 +346,24 @@ class Builder extends Component { | ||||
| } | ||||
| 
 | ||||
| Builder.propTypes = { | ||||
|   getTutorials: PropTypes.func.isRequired, | ||||
|   resetTutorial: PropTypes.func.isRequired, | ||||
|   clearMessages: PropTypes.func.isRequired, | ||||
|   checkError: PropTypes.func.isRequired, | ||||
|   tutorialId: PropTypes.func.isRequired, | ||||
|   readJSON: PropTypes.func.isRequired, | ||||
|   jsonString: PropTypes.func.isRequired, | ||||
|   progress: PropTypes.func.isRequired, | ||||
|   resetTutorial: PropTypes.func.isRequired, | ||||
|   resetTutorialBuilder: PropTypes.func.isRequired, | ||||
|   title: PropTypes.string.isRequired, | ||||
|   steps: PropTypes.array.isRequired, | ||||
|   change: PropTypes.number.isRequired, | ||||
|   error: PropTypes.object.isRequired, | ||||
|   json: PropTypes.string.isRequired, | ||||
|   isProgress: PropTypes.bool.isRequired | ||||
|   id: PropTypes.string.isRequired, | ||||
|   isProgress: PropTypes.bool.isRequired, | ||||
|   tutorials: PropTypes.array.isRequired, | ||||
|   message: PropTypes.object.isRequired | ||||
| }; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
| @ -253,7 +373,9 @@ const mapStateToProps = state => ({ | ||||
|   change: state.builder.change, | ||||
|   error: state.builder.error, | ||||
|   json: state.builder.json, | ||||
|   isProgress: state.builder.progress | ||||
|   isProgress: state.builder.progress, | ||||
|   tutorials: state.tutorial.tutorials, | ||||
|   message: state.message | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, { withTheme: true })(Builder)); | ||||
| export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, tutorialId, resetTutorialBuilder, getTutorials, resetTutorial, clearMessages })(withStyles(styles, { withTheme: true })(Builder)); | ||||
|  | ||||
| @ -121,8 +121,7 @@ class Media extends Component { | ||||
|               <div> | ||||
|                 {!this.props.error ? | ||||
|                   <div> | ||||
|                     <FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}}>{`Beachte, dass das Foto zusätzlich in den Ordner public/media/tutorial unter dem Namen '${this.props.picture}' abgespeichert werden muss.`}</FormHelperText> | ||||
|                     <img src={this.props.url ? this.props.url : `/media/tutorial/${this.props.picture}`} alt={this.props.url ? '' : `Das Bild '${this.props.picture}' konnte nicht im Ordner public/media/tutorial gefunden werden und kann daher nicht angezeigt werden.`} style={{maxHeight: '180px', maxWidth: '360px', marginBottom: '5px'}}/> | ||||
|                     <img src={this.props.url ? this.props.url : this.props.picture ? `${process.env.REACT_APP_BLOCKLY_API}/media/${this.props.picture.path}` : ''} alt={''} style={{maxHeight: '180px', maxWidth: '360px', marginBottom: '5px'}}/> | ||||
|                   </div> | ||||
|                 : <div | ||||
|                     style={{height: '150px', maxWidth: '250px', marginBottom: '5px', justifyContent: "center", alignItems: "center", display:"flex", padding: '20px'}} | ||||
|  | ||||
| @ -14,24 +14,6 @@ import FormControl from '@material-ui/core/FormControl'; | ||||
| 
 | ||||
| class Requirements extends Component { | ||||
| 
 | ||||
|   componentDidMount() { | ||||
|     this.props.getTutorials(); | ||||
|   } | ||||
| 
 | ||||
|   componentDidUpdate(props, state) { | ||||
|     if(this.props.message.id === 'GET_TUTORIALS_FAIL'){ | ||||
|       alert(this.props.message.msg); | ||||
|       this.props.clearMessages(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount() { | ||||
|     this.props.resetTutorial(); | ||||
|     if(this.props.message.msg){ | ||||
|       this.props.clearMessages(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   onChange = (e) => { | ||||
|     var requirements = this.props.value; | ||||
|     var value = e.target.value; | ||||
| @ -50,7 +32,7 @@ class Requirements extends Component { | ||||
|         <FormLabel style={{ color: 'black' }}>Voraussetzungen</FormLabel> | ||||
|         <FormHelperText style={{ marginTop: '5px' }}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormHelperText> | ||||
|         <FormGroup> | ||||
|           {this.props.tutorials.map((tutorial, i) => | ||||
|           {this.props.tutorials.filter(tutorial => tutorial._id !== this.props.id).map((tutorial, i) => | ||||
|             <FormControlLabel | ||||
|               key={i} | ||||
|               control={ | ||||
| @ -72,16 +54,15 @@ class Requirements extends Component { | ||||
| } | ||||
| 
 | ||||
| Requirements.propTypes = { | ||||
|   getTutorials: PropTypes.func.isRequired, | ||||
|   resetTutorial: PropTypes.func.isRequired, | ||||
|   clearMessages: PropTypes.func.isRequired, | ||||
|   changeContent: PropTypes.func.isRequired | ||||
|   changeContent: PropTypes.func.isRequired, | ||||
|   id: PropTypes.string.isRequired, | ||||
|   tutorials: PropTypes.array.isRequired | ||||
| }; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   change: state.builder.change, | ||||
|   tutorials: state.tutorial.tutorials, | ||||
|   message: state.message | ||||
|   id: state.builder.id, | ||||
|   tutorials: state.tutorial.tutorials | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, { changeContent, getTutorials, resetTutorial, clearMessages })(Requirements); | ||||
| export default connect(mapStateToProps, { changeContent })(Requirements); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user