possibility to add pictures to the instructions
This commit is contained in:
parent
3a92a6a08c
commit
8991e2b40b
1
public/media/tutorial/block_en.svg
Normal file
1
public/media/tutorial/block_en.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 9.4 KiB |
@ -170,6 +170,8 @@ 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
|
||||
// therefore do not have to be checked again
|
||||
step.id = i+1;
|
||||
if(i === 0){
|
||||
if(step.requirements && step.requirements.length > 0){
|
||||
@ -255,9 +257,29 @@ export const readJSON = (json) => (dispatch, getState) => {
|
||||
steps: json.steps.map(() => {return {};})
|
||||
}
|
||||
});
|
||||
// accept only valid attributes
|
||||
var steps = json.steps.map((step, i) => {
|
||||
var object = {
|
||||
id: step.id,
|
||||
type: step.type,
|
||||
headline: step.headline,
|
||||
text: step.text
|
||||
};
|
||||
if(i === 0){
|
||||
object.hardware = step.hardware;
|
||||
object.requirements = step.requirements;
|
||||
}
|
||||
if(step.xml){
|
||||
object.xml = step.xml;
|
||||
}
|
||||
if(step.picture && step.type === 'instruction'){
|
||||
object.picture = step.picture;
|
||||
}
|
||||
return object;
|
||||
});
|
||||
dispatch(tutorialTitle(json.title));
|
||||
dispatch(tutorialId(json.id));
|
||||
dispatch(tutorialSteps(json.steps));
|
||||
dispatch(tutorialSteps(steps));
|
||||
dispatch(setSubmitError());
|
||||
dispatch(progress(false));
|
||||
};
|
||||
|
@ -58,10 +58,17 @@ class Builder extends Component {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
else{
|
||||
// export steps without attribute 'url'
|
||||
var steps = this.props.steps.map(step => {
|
||||
if(step.url){
|
||||
delete step.url;
|
||||
}
|
||||
return step;
|
||||
});
|
||||
var tutorial = {
|
||||
id: this.props.id,
|
||||
title: this.props.title,
|
||||
steps: this.props.steps
|
||||
steps: steps
|
||||
}
|
||||
var blob = new Blob([JSON.stringify(tutorial)], { type: 'text/json' });
|
||||
saveAs(blob, `${detectWhitespacesAndReturnReadableResult(tutorial.title)}.json`);
|
||||
|
132
src/components/Tutorial/Builder/Picture.js
Normal file
132
src/components/Tutorial/Builder/Picture.js
Normal file
@ -0,0 +1,132 @@
|
||||
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 (
|
||||
<div style={{marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)'}}>
|
||||
<FormControlLabel
|
||||
labelPlacement="end"
|
||||
label={"Bild"}
|
||||
control={
|
||||
<Switch
|
||||
checked={this.state.checked}
|
||||
onChange={(e) => this.onChange(e.target.checked)}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{this.state.checked ?
|
||||
<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.value}' abgespeichert werden muss.`}</FormHelperText>
|
||||
<img src={this.props.url ? this.props.url : `/media/tutorial/${this.props.value}`} alt={this.props.url ? '' : `Das Bild '${this.props.value}' konnte nicht im Ordner public/media/tutorial gefunden werden und kann daher nicht angezeigt werden.`} style={{maxHeight: '180px', maxWidth: '360px', marginBottom: '5px'}}/>
|
||||
</div>
|
||||
: <div
|
||||
style={{height: '150px', maxWidth: '250px', marginBottom: '5px', justifyContent: "center", alignItems: "center", display:"flex", padding: '20px'}}
|
||||
className={this.props.error ? this.props.classes.errorBorder : null} >
|
||||
{this.props.error ?
|
||||
this.state.error ?
|
||||
<FormHelperText style={{lineHeight: 'initial', textAlign: 'center'}} className={this.props.classes.errorColor}>{`Die übergebene Datei entspricht nicht dem geforderten Bild-Format. Überprüfe, ob es sich um ein Bild handelt und versuche es nochmal.`}</FormHelperText>
|
||||
: <FormHelperText style={{lineHeight: 'initial', textAlign: 'center'}} className={this.props.classes.errorColor}>{`Wähle ein Bild aus.`}</FormHelperText>
|
||||
: null}
|
||||
</div>}
|
||||
{/*upload picture*/}
|
||||
<div ref={this.inputRef}>
|
||||
<input
|
||||
style={{display: 'none'}}
|
||||
accept="image/*"
|
||||
onChange={(e) => {this.uploadPicture(e.target.files[0])}}
|
||||
id="picture"
|
||||
type="file"
|
||||
/>
|
||||
<label htmlFor="picture">
|
||||
<Button component="span" className={this.props.error ? this.props.classes.errorButton : null} style={{marginRight: '10px', marginBottom: '10px'}} variant='contained' color='primary'>Bild auswählen</Button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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));
|
@ -10,6 +10,7 @@ import StepType from './StepType';
|
||||
import BlocklyExample from './BlocklyExample';
|
||||
import Requirements from './Requirements';
|
||||
import Hardware from './Hardware';
|
||||
import Picture from './Picture';
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
@ -103,6 +104,9 @@ class Step extends Component {
|
||||
<Hardware value={this.props.step.hardware ? this.props.step.hardware : []} index={index} error={this.props.error.steps[index].hardware}/>
|
||||
</div>
|
||||
: null}
|
||||
{this.props.step.type === 'instruction' ?
|
||||
<Picture value={this.props.step.picture} url={this.props.step.url} index={index} error={this.props.error.steps[index].picture} />
|
||||
: null}
|
||||
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} error={this.props.error.steps[index].xml ? true : false}/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,8 +63,8 @@
|
||||
"id": 3,
|
||||
"type": "instruction",
|
||||
"headline": "Block richtig einbinden",
|
||||
"text": "",
|
||||
"xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'><statement name='SETUP_FUNC'><block type='sensebox_wifi' id='W}P2Y^g,muH@]|@anou}'><field name='SSID'>SSID</field><field name='Password'>Password</field></block></statement></block></xml>"
|
||||
"text": "Dies ist ein Test.",
|
||||
"picture": "block_en.svg"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
|
Loading…
x
Reference in New Issue
Block a user