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'));
|
dispatch(setError(undefined, 'title'));
|
||||||
}
|
}
|
||||||
var type = builder.steps.map((step, i) => {
|
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;
|
step.id = i+1;
|
||||||
if(i === 0){
|
if(i === 0){
|
||||||
if(step.requirements && step.requirements.length > 0){
|
if(step.requirements && step.requirements.length > 0){
|
||||||
@ -255,9 +257,29 @@ export const readJSON = (json) => (dispatch, getState) => {
|
|||||||
steps: json.steps.map(() => {return {};})
|
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(tutorialTitle(json.title));
|
||||||
dispatch(tutorialId(json.id));
|
dispatch(tutorialId(json.id));
|
||||||
dispatch(tutorialSteps(json.steps));
|
dispatch(tutorialSteps(steps));
|
||||||
dispatch(setSubmitError());
|
dispatch(setSubmitError());
|
||||||
dispatch(progress(false));
|
dispatch(progress(false));
|
||||||
};
|
};
|
||||||
|
@ -58,10 +58,17 @@ class Builder extends Component {
|
|||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
// export steps without attribute 'url'
|
||||||
|
var steps = this.props.steps.map(step => {
|
||||||
|
if(step.url){
|
||||||
|
delete step.url;
|
||||||
|
}
|
||||||
|
return step;
|
||||||
|
});
|
||||||
var tutorial = {
|
var tutorial = {
|
||||||
id: this.props.id,
|
id: this.props.id,
|
||||||
title: this.props.title,
|
title: this.props.title,
|
||||||
steps: this.props.steps
|
steps: steps
|
||||||
}
|
}
|
||||||
var blob = new Blob([JSON.stringify(tutorial)], { type: 'text/json' });
|
var blob = new Blob([JSON.stringify(tutorial)], { type: 'text/json' });
|
||||||
saveAs(blob, `${detectWhitespacesAndReturnReadableResult(tutorial.title)}.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 BlocklyExample from './BlocklyExample';
|
||||||
import Requirements from './Requirements';
|
import Requirements from './Requirements';
|
||||||
import Hardware from './Hardware';
|
import Hardware from './Hardware';
|
||||||
|
import Picture from './Picture';
|
||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from '@material-ui/core/styles';
|
||||||
import Typography from '@material-ui/core/Typography';
|
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}/>
|
<Hardware value={this.props.step.hardware ? this.props.step.hardware : []} index={index} error={this.props.error.steps[index].hardware}/>
|
||||||
</div>
|
</div>
|
||||||
: null}
|
: 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}/>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,8 +63,8 @@
|
|||||||
"id": 3,
|
"id": 3,
|
||||||
"type": "instruction",
|
"type": "instruction",
|
||||||
"headline": "Block richtig einbinden",
|
"headline": "Block richtig einbinden",
|
||||||
"text": "",
|
"text": "Dies ist ein Test.",
|
||||||
"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>"
|
"picture": "block_en.svg"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user