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.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"
+ />
+
+ Bild auswählen
+
+
+
+ :
+ /*youtube-video*/
+
+
+ {this.props.youtube && !this.props.error ?
+
+
{`Stelle sicher, dass das unten angezeigte Youtube-Video funktioniert, andernfalls überprüfe die Youtube-ID.`}
+
+ VIDEO
+
+
+ : null}
+
+ }
+
+ : null}
+
+ );
+ };
+}
+
+Media.propTypes = {
+ changeContent: PropTypes.func.isRequired,
+ deleteProperty: PropTypes.func.isRequired,
+ setError: PropTypes.func.isRequired,
+ deleteError: PropTypes.func.isRequired,
+};
+
+
+export default connect(null, { changeContent, deleteProperty, setError, deleteError })(withStyles(styles, {withTheme: true})(Media));
diff --git a/src/components/Tutorial/Builder/Picture.js b/src/components/Tutorial/Builder/Picture.js
deleted file mode 100644
index 6c23f30..0000000
--- a/src/components/Tutorial/Builder/Picture.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { changeContent, deleteProperty, setError, deleteError } from '../../../actions/tutorialBuilderActions';
-
-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 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 Picture extends Component {
-
- constructor(props){
- super(props);
- this.state={
- checked: props.value ? true : false,
- error: false
- };
- }
-
- componentDidUpdate(props){
- if(props.value !== this.props.value){
- this.setState({ checked: this.props.value ? true : false });
- }
- }
-
- onChange = (value) => {
- var oldValue = this.state.checked;
- this.setState({checked: value});
- if(oldValue !== value){
- if(value){
- this.props.setError(this.props.index, 'picture');
- } else {
- this.props.deleteError(this.props.index, 'picture');
- this.props.deleteProperty(this.props.index, 'picture');
- this.props.deleteProperty(this.props.index, 'url');
- this.setState({ error: false});
- }
- }
- }
-
- uploadPicture = (pic) => {
- if(!(/^image\/.*/.test(pic.type))){
- this.props.setError(this.props.index, 'picture');
- this.setState({ error: true });
- this.props.deleteProperty(this.props.index, 'url');
- }
- else {
- this.props.deleteError(this.props.index, 'picture');
- this.setState({ error: false });
- this.props.changeContent(this.props.index, 'url', URL.createObjectURL(pic));
- }
- this.props.changeContent(this.props.index, 'picture', pic.name);
- }
-
- render() {
- return (
-
-
this.onChange(e.target.checked)}
- color="primary"
- />
- }
- />
- {this.state.checked ?
-
- {!this.props.error ?
-
-
{`Beachte, dass das Foto zusätzlich in den Ordner public/media/tutorial unter dem Namen '${this.props.value}' abgespeichert werden muss.`}
-
-
- :
- {this.props.error ?
- 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.`}
- : null}
-
}
- {/*upload picture*/}
-
- {this.uploadPicture(e.target.files[0])}}
- id="picture"
- type="file"
- />
-
- Bild auswählen
-
-
-
- : null}
-
- );
- };
-}
-
-Picture.propTypes = {
- changeContent: PropTypes.func.isRequired,
- deleteProperty: PropTypes.func.isRequired,
- setError: PropTypes.func.isRequired,
- deleteError: PropTypes.func.isRequired,
-};
-
-
-export default connect(null, { changeContent, deleteProperty, setError, deleteError })(withStyles(styles, {withTheme: true})(Picture));
diff --git a/src/components/Tutorial/Builder/Requirements.js b/src/components/Tutorial/Builder/Requirements.js
index d06c62f..2867325 100644
--- a/src/components/Tutorial/Builder/Requirements.js
+++ b/src/components/Tutorial/Builder/Requirements.js
@@ -23,7 +23,7 @@ class Requirements extends Component {
else {
requirements = requirements.filter(requirement => requirement !== value);
}
- this.props.changeContent(this.props.index, 'requirements', requirements);
+ this.props.changeContent(requirements, this.props.index, 'requirements');
}
render() {
diff --git a/src/components/Tutorial/Builder/Step.js b/src/components/Tutorial/Builder/Step.js
index 33b9c42..ba0e18c 100644
--- a/src/components/Tutorial/Builder/Step.js
+++ b/src/components/Tutorial/Builder/Step.js
@@ -10,7 +10,7 @@ import StepType from './StepType';
import BlocklyExample from './BlocklyExample';
import Requirements from './Requirements';
import Hardware from './Hardware';
-import Picture from './Picture';
+import Media from './Media';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
@@ -105,7 +105,7 @@ class Step extends Component {
: null}
{this.props.step.type === 'instruction' ?
-
+
: null}
diff --git a/src/components/Tutorial/Builder/StepType.js b/src/components/Tutorial/Builder/StepType.js
index cfc2253..971660c 100644
--- a/src/components/Tutorial/Builder/StepType.js
+++ b/src/components/Tutorial/Builder/StepType.js
@@ -10,7 +10,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel';
class StepType extends Component {
onChange = (value) => {
- this.props.changeContent(this.props.index, 'type', value);
+ this.props.changeContent(value, this.props.index, 'type');
// delete property 'xml', so that all used blocks are reset
this.props.deleteProperty(this.props.index, 'xml');
if(value === 'task'){
diff --git a/src/components/Tutorial/Builder/Textfield.js b/src/components/Tutorial/Builder/Textfield.js
index d601784..fa39547 100644
--- a/src/components/Tutorial/Builder/Textfield.js
+++ b/src/components/Tutorial/Builder/Textfield.js
@@ -28,7 +28,9 @@ class Textfield extends Component {
componentDidMount(){
if(this.props.error){
- this.props.deleteError(this.props.index, this.props.property);
+ if(this.props.property !== 'media'){
+ this.props.deleteError(this.props.index, this.props.property);
+ }
}
}
@@ -41,7 +43,7 @@ class Textfield extends Component {
this.props.jsonString(value);
}
else {
- this.props.changeContent(this.props.index, this.props.property, value);
+ this.props.changeContent(value, this.props.index, this.props.property, this.props.property2);
}
if(value.replace(/\s/g,'') === ''){
this.props.setError(this.props.index, this.props.property);
diff --git a/src/data/tutorials.json b/src/data/tutorials.json
index 103f62a..25972a4 100644
--- a/src/data/tutorials.json
+++ b/src/data/tutorials.json
@@ -57,6 +57,9 @@
"type": "instruction",
"headline": "Programmierung",
"text": "Man benötigt folgenden Block:",
+ "media": {
+ "picture": "block_en.svg"
+ },
"xml": "SSID Password "
},
{
@@ -64,7 +67,9 @@
"type": "instruction",
"headline": "Block richtig einbinden",
"text": "Dies ist ein Test.",
- "picture": "block_en.svg"
+ "media": {
+ "youtube": "Q41MPMBCPto"
+ }
},
{
"id": 4,