diff --git a/src/actions/tutorialBuilderActions.js b/src/actions/tutorialBuilderActions.js index ec61f1a..3d3effa 100644 --- a/src/actions/tutorialBuilderActions.js +++ b/src/actions/tutorialBuilderActions.js @@ -85,10 +85,18 @@ export const removeErrorStep = (index) => (dispatch, getState) => { }); }; -export const changeContent = (index, property, content) => (dispatch, getState) => { +export const changeContent = (content, index, property1, property2) => (dispatch, getState) => { var steps = getState().builder.steps; var step = steps[index]; - step[property] = content; + if(property2){ + if(step[property1] && step[property1][property2]){ + step[property1][property2] = content; + } else { + step[property1] = {[property2]: content}; + } + } else { + step[property1] = content; + } dispatch({ type: BUILDER_CHANGE_STEP, payload: steps @@ -96,10 +104,16 @@ export const changeContent = (index, property, content) => (dispatch, getState) dispatch(changeTutorialBuilder()); }; -export const deleteProperty = (index, property) => (dispatch, getState) => { +export const deleteProperty = (index, property1, property2) => (dispatch, getState) => { var steps = getState().builder.steps; var step = steps[index]; - delete step[property]; + if(property2){ + if(step[property1] && step[property1][property2]){ + delete step[property1][property2]; + } + } else { + delete step[property1]; + } dispatch({ type: BUILDER_DELETE_PROPERTY, payload: steps @@ -170,14 +184,14 @@ export const setSubmitError = () => (dispatch, getState) => { dispatch(setError(undefined, 'title')); } var type = builder.steps.map((step, i) => { - // picture and xml are directly checked for errors in their components and + // media and xml are directly checked for errors in their components and // therefore do not have to be checked again step.id = i+1; if(i === 0){ if(step.requirements && step.requirements.length > 0){ var requirements = step.requirements.filter(requirement => typeof(requirement)==='number'); if(requirements.length < step.requirements.length){ - dispatch(changeContent(i, 'requirements', requirements)); + dispatch(changeContent(requirements, i, 'requirements')); } } if(step.hardware === undefined || step.hardware.length < 1){ @@ -187,7 +201,7 @@ export const setSubmitError = () => (dispatch, getState) => { var hardwareIds = data.map(hardware => hardware.id); var hardware = step.hardware.filter(hardware => hardwareIds.includes(hardware)); if(hardware.length < step.hardware.length){ - dispatch(changeContent(i, 'hardware', hardware)); + dispatch(changeContent(hardware, i, 'hardware')); } } } @@ -272,8 +286,14 @@ export const readJSON = (json) => (dispatch, getState) => { if(step.xml){ object.xml = step.xml; } - if(step.picture && step.type === 'instruction'){ - object.picture = step.picture; + if(step.media && step.type === 'instruction'){ + object.media = {}; + if(step.media.picture){ + object.media.picture = step.media.picture; + } + else if(step.media.youtube){ + object.media.youtube = step.media.youtube; + } } return object; }); diff --git a/src/components/Tutorial/Builder/BlocklyExample.js b/src/components/Tutorial/Builder/BlocklyExample.js index 217e1db..01f52f4 100644 --- a/src/components/Tutorial/Builder/BlocklyExample.js +++ b/src/components/Tutorial/Builder/BlocklyExample.js @@ -107,7 +107,7 @@ class BlocklyExample extends Component { setXml = () => { var xml = this.props.xml; - this.props.changeContent(this.props.index, 'xml', xml); + this.props.changeContent(xml, this.props.index, 'xml'); this.setState({input: moment(Date.now()).format('LTS')}); } @@ -134,7 +134,7 @@ class BlocklyExample extends Component { {this.state.checked && !this.props.task ? Anmerkung: Man kann den initialen Setup()- bzw. Endlosschleifen()-Block löschen. Zusätzlich ist es möglich u.a. nur einen beliebigen Block auszuwählen, ohne dass dieser als deaktiviert dargestellt wird. : null} - // ensure that the correct xml-file is displayed in the workspace + {/* ensure that the correct xml-file is displayed in the workspace */} {this.state.checked && this.state.xml? (() => { return(
diff --git a/src/components/Tutorial/Builder/Hardware.js b/src/components/Tutorial/Builder/Hardware.js index f469851..770cc8c 100644 --- a/src/components/Tutorial/Builder/Hardware.js +++ b/src/components/Tutorial/Builder/Hardware.js @@ -56,7 +56,7 @@ class Requirements extends Component { this.props.deleteError(this.props.index, 'hardware'); } } - this.props.changeContent(this.props.index, 'hardware', hardwareArray); + this.props.changeContent(hardwareArray, this.props.index, 'hardware'); if(hardwareArray.length === 0){ this.props.setError(this.props.index, 'hardware'); } diff --git a/src/components/Tutorial/Builder/Media.js b/src/components/Tutorial/Builder/Media.js new file mode 100644 index 0000000..3f1896b --- /dev/null +++ b/src/components/Tutorial/Builder/Media.js @@ -0,0 +1,178 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { changeContent, deleteProperty, setError, deleteError } from '../../../actions/tutorialBuilderActions'; + +import Textfield from './Textfield'; + +import { withStyles } from '@material-ui/core/styles'; +import Switch from '@material-ui/core/Switch'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Radio from '@material-ui/core/Radio'; +import RadioGroup from '@material-ui/core/RadioGroup'; +import Button from '@material-ui/core/Button'; + +const styles = (theme) => ({ + errorColor: { + color: theme.palette.error.dark + }, + errorBorder: { + border: `1px solid ${theme.palette.error.dark}` + }, + errorButton: { + marginTop: '5px', + height: '40px', + backgroundColor: theme.palette.error.dark, + '&:hover':{ + backgroundColor: theme.palette.error.dark + } + } +}); + +class Media extends Component { + + constructor(props){ + super(props); + this.state={ + checked: props.value ? true : false, + error: false, + radioValue: !props.picture && !props.youtube ? 'picture' : props.picture ? 'picture' : 'youtube' + }; + } + + componentDidUpdate(props){ + if(props.value !== this.props.value){ + this.setState({ checked: this.props.value ? true : false }); + } + } + + onChangeSwitch = (value) => { + var oldValue = this.state.checked; + this.setState({checked: value}); + if(oldValue !== value){ + if(value){ + this.props.setError(this.props.index, 'media'); + } else { + this.props.deleteError(this.props.index, 'media'); + this.props.deleteProperty(this.props.index, 'media'); + this.props.deleteProperty(this.props.index, 'url'); + this.setState({ error: false}); + } + } + } + + onChangeRadio = (value) => { + this.props.setError(this.props.index, 'media'); + var oldValue = this.state.radioValue; + this.setState({radioValue: value, error: false}); + // delete property 'oldValue', so that all old media files are reset + this.props.deleteProperty(this.props.index, 'media', oldValue); + if(oldValue === 'picture'){ + this.props.deleteProperty(this.props.index, 'url'); + } + } + + uploadPicture = (pic) => { + if(!(/^image\/.*/.test(pic.type))){ + this.props.setError(this.props.index, 'media'); + this.setState({ error: true }); + this.props.deleteProperty(this.props.index, 'url'); + } + else { + this.props.deleteError(this.props.index, 'media'); + this.setState({ error: false }); + this.props.changeContent(URL.createObjectURL(pic), this.props.index, 'url'); + } + this.props.changeContent(pic.name, this.props.index, 'media', 'picture'); + } + + render() { + return ( +
+ this.onChangeSwitch(e.target.checked)} + color="primary" + /> + } + /> + {this.state.checked ? +
+ {this.onChangeRadio(e.target.value);}}> + } + label="Bild" + labelPlacement="end" + /> + } + label="Youtube-Video" + labelPlacement="end" + /> + + {this.state.radioValue === 'picture' ? +
+ {!this.props.error ? +
+ {`Beachte, dass das Foto zusätzlich in den Ordner public/media/tutorial unter dem Namen '${this.props.picture}' abgespeichert werden muss.`} + {this.props.url +
+ :
+ {this.state.error ? + {`Die übergebene Datei entspricht nicht dem geforderten Bild-Format. Überprüfe, ob es sich um ein Bild handelt und versuche es nochmal.`} + : {`Wähle ein Bild aus.`} + } +
} + {/*upload picture*/} +
+ {this.uploadPicture(e.target.files[0]);}} + id={`picture ${this.props.index}`} + type="file" + /> + +
+
+ : + /*youtube-video*/ +
+ + {this.props.youtube && !this.props.error ? +
+ {`Stelle sicher, dass das unten angezeigte Youtube-Video funktioniert, andernfalls überprüfe die Youtube-ID.`} +
+