import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { checkError, readJSON, jsonString, progress, tutorialId, resetTutorial as resetTutorialBuilder} from '../../../actions/tutorialBuilderActions'; import { getTutorials, resetTutorial, deleteTutorial, tutorialProgress } from '../../../actions/tutorialActions'; import { clearMessages } from '../../../actions/messageActions'; import axios from 'axios'; import { withRouter } from 'react-router-dom'; import { saveAs } from 'file-saver'; import { detectWhitespacesAndReturnReadableResult } from '../../../helpers/whitespace'; import Breadcrumbs from '../../Breadcrumbs'; import Textfield from './Textfield'; import Step from './Step'; import Dialog from '../../Dialog'; import Snackbar from '../../Snackbar'; import { withStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; 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: { zIndex: theme.zIndex.drawer + 1, color: '#fff', }, errorColor: { color: theme.palette.error.dark }, errorButton: { marginTop: '5px', height: '40px', backgroundColor: theme.palette.error.dark, '&:hover':{ backgroundColor: theme.palette.error.dark } } }); class Builder extends Component { constructor(props) { super(props); this.state = { tutorial: 'new', open: false, title: '', content: '', string: false, snackbar: false, key: '', message: '' }; this.inputRef = React.createRef(); } componentDidMount() { this.props.tutorialProgress(); // retrieve tutorials only if a potential user is loaded - authentication // is finished (success or failed) if(!this.props.authProgress){ this.props.getTutorials(); } } componentDidUpdate(props, state) { if(props.authProgress !== this.props.authProgress && !this.props.authProgress){ // authentication is completed this.props.getTutorials(); } if(props.message !== this.props.message){ if(this.props.message.id === 'GET_TUTORIALS_FAIL'){ // alert(this.props.message.msg); this.props.clearMessages(); } else if(this.props.message.id === 'TUTORIAL_DELETE_SUCCESS'){ this.onChange('new'); this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich gelöscht.`, type: 'success' }); } else if(this.props.message.id === 'TUTORIAL_DELETE_FAIL'){ this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Löschen des Tutorials. Versuche es noch einmal.`, type: 'error' }); } } } componentWillUnmount() { this.resetFull(); this.props.resetTutorial(); if(this.props.message.msg){ this.props.clearMessages(); } } uploadJsonFile = (jsonFile) => { this.props.progress(true); if (jsonFile.type !== 'application/json') { this.props.progress(false); this.setState({ open: true, string: false, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entspricht nicht dem geforderten Format. Es sind nur JSON-Dateien zulässig.' }); } else { var reader = new FileReader(); reader.readAsText(jsonFile); reader.onloadend = () => { this.readJson(reader.result, true); }; } } uploadJsonString = () => { this.setState({ open: true, string: true, title: 'JSON-String einfügen', content: '' }); } readJson = (jsonString, isFile) => { try { var result = JSON.parse(jsonString); if (!this.checkSteps(result.steps)) { result.steps = [{}]; } this.props.readJSON(result); this.setState({ snackbar: true, key: Date.now(), message: `${isFile ? 'Die übergebene JSON-Datei' : 'Der übergebene JSON-String'} wurde erfolgreich übernommen.`, type: 'success' }); } catch (err) { this.props.progress(false); this.props.jsonString(''); this.setState({ open: true, string: false, title: 'Ungültiges JSON-Format', content: `${isFile ? 'Die übergebene Datei' : 'Der übergebene String'} enthält nicht valides JSON. Bitte überprüfe ${isFile ? 'die JSON-Datei' : 'den JSON-String'} und versuche es erneut.` }); } } checkSteps = (steps) => { if (!(steps && steps.length > 0)) { return false; } return true; } toggle = () => { this.setState({ open: !this.state }); } onChange = (value) => { this.props.resetTutorialBuilder(); this.props.tutorialId(''); this.setState({tutorial: value}); } onChangeId = (value) => { this.props.tutorialId(value); if(this.state.tutorial === 'change'){ 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); newTutorial.append('badge', this.props.badge); steps.forEach((step, i) => { if(step._id){ newTutorial.append(`steps[${i}][_id]`, step._id); } 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){ const config = { success: res => { var tutorial = res.data.tutorial; this.props.history.push(`/tutorial/${tutorial._id}`); }, error: err => { this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Erstellen des Tutorials. Versuche es noch einmal.`, type: 'error' }); window.scrollTo(0, 0); } }; axios.post(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/`, newTutorial, config) .then(res => { res.config.success(res); }) .catch(err => { err.config.error(err); }); } } submitUpdate = () => { var updatedTutorial = this.submit(); if(updatedTutorial){ const config = { success: res => { var tutorial = res.data.tutorial; this.props.history.push(`/tutorial/${tutorial._id}`); }, error: err => { this.setState({ snackbar: true, key: Date.now(), message: `Fehler beim Ändern des Tutorials. Versuche es noch einmal.`, type: 'error' }); window.scrollTo(0, 0); } }; axios.put(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/${this.props.id}`, updatedTutorial, config) .then(res => { res.config.success(res); }) .catch(err => { err.config.error(err); }); } } render() { var filteredTutorials = this.props.tutorials.filter(tutorial => tutorial.creator === this.props.user.email); return (

Tutorial-Builder

this.onChange(e.target.value)}> } label="neues Tutorial erstellen" labelPlacement="end" /> {filteredTutorials.length > 0 ?
} label="bestehendes Tutorial ändern" labelPlacement="end" /> } label="bestehendes Tutorial löschen" labelPlacement="end" />
: null}
{this.state.tutorial === 'new' ? /*upload JSON*/
{ this.uploadJsonFile(e.target.files[0]) }} id="open-json" type="file" />
: Tutorial } {this.state.tutorial === 'new' || (this.state.tutorial === 'change' && this.props.id !== '') ? /*Tutorial-Builder-Form*/
{this.props.error.type ? {`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`} : null} {/* */} {this.props.steps.map((step, i) => )} {/*submit or reset*/} {this.state.tutorial === 'new' ?
:
}
: null} {this.state.tutorial === 'delete' && this.props.id !== '' ? : null}
: null } > {this.state.string ? : null} ); }; } Builder.propTypes = { getTutorials: PropTypes.func.isRequired, resetTutorial: PropTypes.func.isRequired, clearMessages: PropTypes.func.isRequired, tutorialId: PropTypes.func.isRequired, checkError: PropTypes.func.isRequired, readJSON: PropTypes.func.isRequired, jsonString: PropTypes.func.isRequired, progress: PropTypes.func.isRequired, deleteTutorial: PropTypes.func.isRequired, resetTutorialBuilder: PropTypes.func.isRequired, tutorialProgress: PropTypes.func.isRequired, title: PropTypes.string.isRequired, badge: PropTypes.string.isRequired, id: PropTypes.string.isRequired, steps: PropTypes.array.isRequired, change: PropTypes.number.isRequired, error: PropTypes.object.isRequired, json: PropTypes.string.isRequired, badge: PropTypes.string.isRequired, isProgress: PropTypes.bool.isRequired, tutorials: PropTypes.array.isRequired, message: PropTypes.object.isRequired, user: PropTypes.object.isRequired, authProgress: PropTypes.bool.isRequired }; const mapStateToProps = state => ({ title: state.builder.title, badge: state.builder.badge, id: state.builder.id, steps: state.builder.steps, change: state.builder.change, error: state.builder.error, json: state.builder.json, isProgress: state.builder.progress, tutorials: state.tutorial.tutorials, message: state.message, user: state.auth.user, authProgress: state.auth.progress }); export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, tutorialId, resetTutorialBuilder, getTutorials, resetTutorial, tutorialProgress, clearMessages, deleteTutorial })(withStyles(styles, { withTheme: true })(withRouter(Builder)));