upload JSON file

This commit is contained in:
Delucse 2020-09-19 23:21:43 +02:00
parent 28ced177bd
commit 7579be52c9
7 changed files with 212 additions and 35 deletions

View File

@ -1,4 +1,4 @@
import { BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP, BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from './types'; import { PROGRESS, BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP, BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from './types';
export const changeTutorialBuilder = () => (dispatch) => { export const changeTutorialBuilder = () => (dispatch) => {
dispatch({ dispatch({
@ -14,6 +14,14 @@ export const tutorialTitle = (title) => (dispatch) => {
dispatch(changeTutorialBuilder()); dispatch(changeTutorialBuilder());
}; };
export const tutorialSteps = (steps) => (dispatch) => {
dispatch({
type: BUILDER_ADD_STEP,
payload: steps
});
dispatch(changeTutorialBuilder());
};
export const tutorialId = (id) => (dispatch) => { export const tutorialId = (id) => (dispatch) => {
dispatch({ dispatch({
type: BUILDER_ID, type: BUILDER_ID,
@ -116,7 +124,6 @@ export const changeErrorStepIndex = (fromIndex, toIndex) => (dispatch, getState)
export const setError = (index, property) => (dispatch, getState) => { export const setError = (index, property) => (dispatch, getState) => {
var error = getState().builder.error; var error = getState().builder.error;
console.log(index);
if(index !== undefined){ if(index !== undefined){
error.steps[index][property] = true; error.steps[index][property] = true;
} }
@ -147,21 +154,21 @@ export const deleteError = (index, property) => (dispatch, getState) => {
export const setSubmitError = () => (dispatch, getState) => { export const setSubmitError = () => (dispatch, getState) => {
var builder = getState().builder; var builder = getState().builder;
if(builder.id === ''){ if(builder.id === undefined || builder.id === ''){
dispatch(setError(undefined, 'id')); dispatch(setError(undefined, 'id'));
} }
if(builder.title === ''){ if(builder.id === undefined || builder.title === ''){
dispatch(setError(undefined, 'title')); dispatch(setError(undefined, 'title'));
} }
for(var i = 0; i < builder.steps.length; i++){ for(var i = 0; i < builder.steps.length; i++){
builder.steps[i].id = i+1; builder.steps[i].id = i+1;
if(i === 0 && builder.steps[i].hardware.length < 1){ if(i === 0 && (builder.steps[i].hardware === undefined || builder.steps[i].hardware.length < 1)){
dispatch(setError(i, 'hardware')); dispatch(setError(i, 'hardware'));
} }
if(builder.steps[i].headline === ''){ if(builder.steps[i].headline === undefined || builder.steps[i].headline === ''){
dispatch(setError(i, 'headline')); dispatch(setError(i, 'headline'));
} }
if(builder.steps[i].text === ''){ if(builder.steps[i].text === undefined || builder.steps[i].text === ''){
dispatch(setError(i, 'text')); dispatch(setError(i, 'text'));
} }
} }
@ -180,3 +187,45 @@ export const checkError = () => (dispatch, getState) => {
} }
return false; return false;
} }
export const progress = (inProgress) => (dispatch) => {
dispatch({
type: PROGRESS,
payload: inProgress
})
};
export const resetTutorial = () => (dispatch, getState) => {
dispatch(tutorialTitle(''));
dispatch(tutorialId(''));
var steps = [
{
id: 1,
type: 'instruction',
headline: '',
text: '',
hardware: [],
requirements: []
}
];
dispatch(tutorialSteps(steps));
dispatch({
type: BUILDER_ERROR,
payload: {
steps: [{}]
}
});
};
export const readJSON = (json) => (dispatch, getState) => {
dispatch(resetTutorial());
dispatch({
type: BUILDER_ERROR,
payload: {steps: [{},{}]}
});
dispatch(tutorialTitle(json.title));
dispatch(tutorialId(json.id));
dispatch(tutorialSteps(json.steps));
dispatch(setSubmitError());
dispatch(progress(false));
};

View File

@ -25,3 +25,4 @@ export const BUILDER_CHANGE_STEP = 'BUILDER_CHANGE_STEP';
export const BUILDER_CHANGE_ORDER = 'BUILDER_CHANGE_ORDER'; export const BUILDER_CHANGE_ORDER = 'BUILDER_CHANGE_ORDER';
export const BUILDER_DELETE_PROPERTY = 'BUILDER_DELETE_PROPERTY'; export const BUILDER_DELETE_PROPERTY = 'BUILDER_DELETE_PROPERTY';
export const BUILDER_ERROR = 'BUILDER_ERROR'; export const BUILDER_ERROR = 'BUILDER_ERROR';
export const PROGRESS = 'PROGRESS';

View File

@ -5,7 +5,9 @@ import { changeContent, deleteProperty, setError, deleteError } from '../../../a
import moment from 'moment'; import moment from 'moment';
import localization from 'moment/locale/de'; import localization from 'moment/locale/de';
import * as Blockly from 'blockly/core';
import { parseXml } from '../../../helpers/compareXml';
import BlocklyWindow from '../../Blockly/BlocklyWindow'; import BlocklyWindow from '../../Blockly/BlocklyWindow';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
@ -94,27 +96,38 @@ class BlocklyExample extends Component {
/> />
} }
/> />
{this.state.checked ? !this.props.value ? {this.state.checked ? !this.props.value || this.props.error.steps[this.props.index].xml ?
<FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}} className={this.props.classes.errorColor}>Reiche deine Blöcke ein, indem du auf den rot gefärbten Button klickst.</FormHelperText> <FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}} className={this.props.classes.errorColor}>Reiche deine Blöcke ein, indem du auf den rot gefärbten Button klickst.</FormHelperText>
: <FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}}>Die letzte Einreichung erfolgte um {this.state.input} Uhr.</FormHelperText> : <FormHelperText style={{lineHeight: 'initial', marginBottom: '10px'}}>Die letzte Einreichung erfolgte um {this.state.input} Uhr.</FormHelperText>
: null} : null}
{this.state.checked ? {this.state.checked ? () => {
<div> var initialXml = this.props.value;
<Grid container className={!this.props.value ? this.props.classes.errorBorder : null}> // check if value is valid xml;
<Grid item xs={12}> try{
<BlocklyWindow initialXml={this.props.value}/> Blockly.Xml.textToDom(initialXml);
}
catch(err){
initialXml = null;
this.props.setError(this.props.index, 'xml');
}
return(
<div>
<Grid container className={!this.props.value ? this.props.classes.errorBorder : null}>
<Grid item xs={12}>
<BlocklyWindow initialXml={initialXml}/>
</Grid>
</Grid> </Grid>
</Grid> <Button
<Button className={!this.props.value || this.props.error.steps[this.props.index].xml ? this.props.classes.errorButton : null }
className={!this.props.value ? this.props.classes.errorButton : null } style={{marginTop: '5px', height: '40px'}}
style={{marginTop: '5px', height: '40px'}} variant='contained'
variant='contained' color='primary'
color='primary' onClick={() => {this.props.changeContent(this.props.index, 'xml', this.props.xml); this.setState({input: moment(Date.now()).format('LTS')})}}
onClick={() => {this.props.changeContent(this.props.index, 'xml', this.props.xml); this.setState({input: moment(Date.now()).format('LTS')})}} >
> {this.props.task ? 'Musterlösung einreichen' : 'Beispiel einreichen'}
{this.props.task ? 'Musterlösung einreichen' : 'Beispiel einreichen'} </Button>
</Button> </div>
</div> )}
: null} : null}
</div> </div>
); );

View File

@ -1,10 +1,11 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { checkError } from '../../../actions/tutorialBuilderActions'; import { checkError, readJSON, progress, resetTutorial } from '../../../actions/tutorialBuilderActions';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import data from '../../../data/hardware.json';
import { detectWhitespacesAndReturnReadableResult } from '../../../helpers/whitespace'; import { detectWhitespacesAndReturnReadableResult } from '../../../helpers/whitespace';
import Breadcrumbs from '../../Breadcrumbs'; import Breadcrumbs from '../../Breadcrumbs';
@ -12,14 +13,30 @@ import Id from './Id';
import Title from './Textfield'; import Title from './Textfield';
import Step from './Step'; import Step from './Step';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button'; 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';
const styles = (theme) => ({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: '#fff',
}
});
class Builder extends Component { class Builder extends Component {
constructor(props){
super(props);
this.inputRef = React.createRef();
}
submit = () => { submit = () => {
var isError = this.props.checkError(); var isError = this.props.checkError();
if(isError){ if(isError){
alert('Error'); window.scrollTo(0, 0);
} }
else{ else{
var tutorial = { var tutorial = {
@ -32,6 +49,74 @@ class Builder extends Component {
} }
} }
reset = () => {
this.props.resetTutorial();
window.scrollTo(0, 0);
}
uploadJsonFile = (jsonFile) => {
this.props.progress(true);
if(jsonFile.type !== 'application/json'){
alert('falscher Dateityp');
this.props.progress(false);
this.setState({ open: true, file: false, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entsprach nicht dem geforderten Format. Es sind nur JSON-Dateien zulässig.' });
}
else {
var reader = new FileReader();
reader.readAsText(jsonFile);
reader.onloadend = () => {
try {
var result = JSON.parse(reader.result);
if(this.checkSteps(result.steps)){
alert('Hier');
this.props.readJSON(result);
}
else{
this.props.progress(false);
alert('die JSON-Datei hat nicht die richtige Form');
}
} catch(err){
this.props.progress(false);
alert('ungültige JSON-Datei');
this.setState({ open: true, file: false, title: 'Ungültige XML', content: 'Die XML-Datei konnte nicht in Blöcke zerlegt werden. Bitte überprüfe den XML-Code und versuche es erneut.' });
}
};
}
}
checkSteps = (steps) => {
if(!(steps && steps.length > 0)){
alert(1);
return false;
}
steps.map((step, i) => {
if(i === 0){
if(!(step.requirements &&
step.requirements.length > 0 &&
step.requirements.filter(requirement => typeof(requirement) === 'number').length === step.requirements.length)){
alert(3);
return false;
}
var hardwareIds = data.map(hardware => hardware.id);
if(!(step.hardware &&
step.hardware.length > 0 &&
step.hardware.filter(hardware => typeof(hardware) === 'string' && hardwareIds.includes(hardware)).length === step.hardware.length)){
alert(4);
return false;
}
}
if(!(step.headline && typeof(step.headline)==='string')){
alert(5);
return false;
}
if(!(step.text && typeof(step.text)==='string')){
alert(6);
return false;
}
});
return true;
}
render() { render() {
return ( return (
@ -40,6 +125,20 @@ class Builder extends Component {
<h1>Tutorial-Builder</h1> <h1>Tutorial-Builder</h1>
<div ref={this.inputRef}>
<input
style={{display: 'none'}}
accept="application/json"
onChange={(e) => {this.uploadJsonFile(e.target.files[0])}}
id="open-json"
type="file"
/>
<label htmlFor="open-json">
<Button component="span" style={{marginRight: '10px', marginBottom: '10px'}} variant='contained' color='primary'>Datei laden</Button>
</label>
</div>
<Divider variant='fullWidth' style={{margin: '10px 0 30px 0'}}/>
<Id error={this.props.error} value={this.props.id}/> <Id error={this.props.error} value={this.props.id}/>
<Title value={this.props.title} property={'title'} label={'Titel'} error={this.props.error}/> <Title value={this.props.title} property={'title'} label={'Titel'} error={this.props.error}/>
@ -49,7 +148,11 @@ class Builder extends Component {
)} )}
<Button variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button> <Button style={{marginRight: '10px'}} variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button>
<Button variant='contained' onClick={() => this.reset()}>Zurücksetzen</Button>
<Backdrop className={this.props.classes.backdrop} open={this.props.isProgress}>
<CircularProgress color="inherit" />
</Backdrop>
</div> </div>
@ -63,10 +166,14 @@ class Builder extends Component {
Builder.propTypes = { Builder.propTypes = {
checkError: PropTypes.func.isRequired, checkError: PropTypes.func.isRequired,
readJSON: PropTypes.func.isRequired,
progress: PropTypes.func.isRequired,
resetTutorial: PropTypes.func.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
steps: PropTypes.array.isRequired, steps: PropTypes.array.isRequired,
change: PropTypes.number.isRequired, change: PropTypes.number.isRequired,
error: PropTypes.object.isRequired error: PropTypes.object.isRequired,
isProgress: PropTypes.bool.isRequired
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
@ -74,7 +181,8 @@ const mapStateToProps = state => ({
id: state.builder.id, id: state.builder.id,
steps: state.builder.steps, steps: state.builder.steps,
change: state.builder.change, change: state.builder.change,
error: state.builder.error error: state.builder.error,
isProgress: state.builder.progress
}); });
export default connect(mapStateToProps, { checkError })(Builder); export default connect(mapStateToProps, { checkError, readJSON, progress, resetTutorial })(withStyles(styles, {withTheme: true})(Builder));

View File

@ -89,11 +89,11 @@ class Step extends Component {
<Textfield value={this.props.step.text} property={'text'} label={this.props.step.type === 'task' ? 'Aufgabenstellung' : 'Instruktionen'} index={index} multiline error={this.props.error} errorText={`Gib Instruktionen für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`}/> <Textfield value={this.props.step.text} property={'text'} label={this.props.step.type === 'task' ? 'Aufgabenstellung' : 'Instruktionen'} index={index} multiline error={this.props.error} errorText={`Gib Instruktionen für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`}/>
{index === 0 ? {index === 0 ?
<div> <div>
<Requirements value={this.props.step.requirements} index={index}/> <Requirements value={this.props.step.requirements ? this.props.step.requirements : []} index={index}/>
<Hardware value={this.props.step.hardware} index={index} error={this.props.error}/> <Hardware value={this.props.step.hardware ? this.props.step.hardware : []} index={index} error={this.props.error}/>
</div> </div>
: null} : null}
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} /> <BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} error={this.props.error}/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -11,7 +11,7 @@ class StepType extends Component {
render() { render() {
return ( return (
<RadioGroup row value={this.props.value} onChange={(e) => {this.props.changeContent(this.props.index, 'type', e.target.value)}}> <RadioGroup row value={this.props.value === 'task' ? 'task' : 'instruction'} onChange={(e) => {this.props.changeContent(this.props.index, 'type', e.target.value)}}>
<FormControlLabel style={{color: 'black'}} <FormControlLabel style={{color: 'black'}}
value="instruction" value="instruction"
control={<Radio color="primary" />} control={<Radio color="primary" />}

View File

@ -1,7 +1,8 @@
import { BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP,BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from '../actions/types'; import { PROGRESS, BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP,BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from '../actions/types';
const initialState = { const initialState = {
change: 0, change: 0,
progress: false,
title: '', title: '',
id: '', id: '',
steps: [ steps: [
@ -50,6 +51,11 @@ export default function(state = initialState, action){
...state, ...state,
error: action.payload error: action.payload
} }
case PROGRESS:
return {
...state,
progress: action.payload
}
default: default:
return state; return state;
} }