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 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"; import * as Blockly from "blockly"; 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); 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 (